diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 00000000..0188efd3 --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,30 @@ +name: build + +runs: + using: composite + + steps: + - name: install poetry + uses: snok/install-poetry@v1 + with: + version: 1.8.2 + + - name: install python + id: setup-python + uses: actions/setup-python@v5 + with: + cache: poetry + python-version-file: pyproject.toml + + - name: make sure poetry lockfile is up to date + run: poetry check --lock + shell: bash + + - name: save python version to the env + run: echo "python-version=${{ steps.setup-python.outputs.python-version }}" >> $GITHUB_ENV + shell: bash + + - name: install deps + if: steps.setup-python.outputs.cache-hit != true + run: poetry install --no-interaction --no-root + shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 54539ffd..759cbcb5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ version: 2 updates: - package-ecosystem: pip - directory: "/{{cookiecutter.project_slug}}" + directory: "/{{ cookiecutter.name }}" schedule: interval: daily time: "02:00" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bc2e98e..11d94223 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,97 @@ ---- name: CI -on: push + +on: + push: + branches: + - master + pull_request: jobs: - build: + lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: checkout + uses: actions/checkout@v4 - - name: Install gettext for correct manage.py compilemessages - run: sudo apt-get update && sudo apt-get --no-install-recommends install -y locales-all gettext + - name: build + uses: ./.github/actions/build - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - cache: 'pip' + - name: lint + run: make lint - - name: Install cookiecutter - run: pip install cookiecutter + bootstrap: + needs: lint + runs-on: ubuntu-latest + services: + postgres: + env: + POSTGRES_PASSWORD: secret + image: postgres:16.2-alpine + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-retries 5 + --health-timeout 5s + ports: + - 5432:5432 + + steps: + - name: checkout + uses: actions/checkout@v4 - - name: Bootstrap the project - run: make test + - name: build + uses: ./.github/actions/build + + - name: bootstrap + run: make bootstrap + env: + DATABASE_URL: postgres://postgres:secret@localhost:5432/postgres + + - name: lint generated project (and its template) + run: poetry install && make lint + working-directory: testproject + + build-docker-image: + needs: bootstrap + runs-on: ubuntu-latest + services: + postgres: + env: + POSTGRES_PASSWORD: secret + image: postgres:16.2-alpine + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-retries 5 + --health-timeout 5s + ports: + - 5432:5432 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build + uses: ./.github/actions/build + + - name: bootstrap + run: make bootstrap + env: + DATABASE_URL: postgres://postgres:secret@localhost:5432/postgres + + - name: setup qemu + uses: docker/setup-qemu-action@v2 + + - name: setup buildx + uses: docker/setup-buildx-action@v3 + + - name: make sure docker image is buildable + uses: docker/build-push-action@v5 + with: + build-args: | + PYTHON_VERSION=${{ env.python-version }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: testproject + push: false diff --git a/.gitignore b/.gitignore index f8170519..fe20e44b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ -venv -testproject -.vscode +.git .idea +.venv +.vscode + +**/.DS_Store + +{{ cookiecutter.name }}/poetry.lock + +testproject diff --git a/Makefile b/Makefile index cf65296f..87aaec37 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,11 @@ -VENV=cd testproject/django/src && ../venv/bin/python +bootstrap: + rm -rf testproject -test: bootstrap - $(VENV) ./manage.py makemigrations --check - $(VENV) ./manage.py startapp test_app + poetry run cookiecutter --no-input ./ -bootstrap: - rm -Rf testproject - mkdir -p testproject - cd testproject && cookiecutter --no-input ../ +fmt: + poetry run toml-sort pyproject.toml -coverage: - $(VENV) -m pip install pytest-cov - $(VENV) -m pytest --cov-report=xml --cov=app --cov=users --cov=a12n --cov=sepulkas +lint: + poetry run toml-sort pyproject.toml --check + poetry run pymarkdown scan README.md diff --git a/README.md b/README.md index 9a5ddf13..695ece3b 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,37 @@ -# My personal (very) opinionated django template +# fands.dev django template -![Shields.io](https://img.shields.io/github/last-commit/fandsdev/django?style=flat-square) [![Maintainability](https://api.codeclimate.com/v1/badges/2b9800b10414a4ad2622/maintainability)](https://codeclimate.com/github/f213/django/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/2b9800b10414a4ad2622/test_coverage)](https://codeclimate.com/github/f213/django/test_coverage) +![Shields.io](https://img.shields.io/github/last-commit/fandsdev/django?style=flat-square) ![Easy peasy](https://user-images.githubusercontent.com/1592663/79918184-93bca100-8434-11ea-9902-0ff726a864a3.gif) - ## What is in the box -* API-only django (checkout [this post](https://t.me/pmdaily/257) in Russian) based on Django REST Framework with JWT support -* [pip-tools](https://github.com/jazzband/pip-tools) with separate development-time dependencies -* Strict type checking with mypy, [django-stubs](https://github.com/typeddjango/django-stubs) and [djangorestframework-stubs](https://github.com/typeddjango/djangorestframework-stubs) -* flake8 with ton of plugins (contact me if you know more) -* [black](https://github.com/psf/black) as uncompromising code formatter -* Starter CI configuration on GitHub Actions -* pytest with useful stuff like freezegun, pytest-mock and super convinient [DRF test client](https://github.com/f213/django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/src/app/tests/tests_health.py#L9) -* Custom [user model](https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#specifying-a-custom-user-model) -* [drf-spectacular](https://github.com/tfranzel/drf-spectacular) for API Schema generation -* [django-axes](https://github.com/jazzband/django-axes) for additional security -* [Whitenoise](http://whitenoise.evans.io) for effortless static files hosting -* cloudflare-ready with [django-ipware](https://github.com/un33k/django-ipware) +* API-only django (checkout [this post](https://t.me/pmdaily/257) in Russian) based on Django REST Framework with JWT support. +* [poetry](https://python-poetry.org) with separate development-time dependencies. +* Strict type checking with mypy, [django-stubs](https://github.com/typeddjango/django-stubs) and [djangorestframework-stubs](https://github.com/typeddjango/djangorestframework-stubs). +* tons of linters and formatters (contact me if something interesting not included, see `Makefile` `fmt`, `lint` commands). +* Starter CI configuration on GitHub Actions. +* `pytest` with useful stuff like `freezegun`, `pytest-mock` and super convinient [DRF test client](https://github.com/fandsdev/django/blob/master/%7B%7Bcookiecutter.name%7D%7D/src/app/tests_health.py#L9) +* Custom [user model](https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#specifying-a-custom-user-model). +* [drf-spectacular](https://github.com/tfranzel/drf-spectacular) for API Schema generation. +* [django-axes](https://github.com/jazzband/django-axes) for additional security. +* [Whitenoise](http://whitenoise.evans.io) for effortless static files hosting. +* cloudflare-ready with [django-ipware](https://github.com/un33k/django-ipware). * Sentry. Set `SENTRY_DSN` env var if you need it. -* Postgres ready. Set `DATABASE_URL` env var to something like `DATABASE_URL=postgres://postgres@localhost:5432/postgres` - +* Postgres. -## Optional next steps -You definetely should consider this steps after installation: -* Install [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) if you plan to grow beyond 500 unittests -* If you are into docker, check out this [docker-compose.yml](https://github.com/f213/django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/docker-compose.yml) to run on your machine +## Installation +First, make sure you have `PostgreSQL` up and running (check the `{{ cookiecutter.name }}/src/app/.env.ci` `DATABASE_URL` and `{{ cookiecutter.name }}/docker-compose.yml` for configuration). After that: -## Installation +```bash +poetry install -``` -$ pip install --upgrade cookiecutter -$ cookiecutter gh:fandsdev/django +poetry run cookiecutter gh:fandsdev/django ``` ## FAQ -### I have got an error «'random_ascii_string' is undefined» - -You should upgrade cookiecutter to the latest version: `pip install --upgrade cookiecutter` - ### I wanna hack this! Thank you so much! Check out our [build pipeline](https://github.com/fandsdev/django/blob/master/Makefile) and pick any free [issue](https://github.com/fandsdev/django/issues). diff --git a/cookiecutter.json b/cookiecutter.json index 0e2243ca..11589da8 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,9 +1,11 @@ { - "project_slug": "django", - "email": "", - "project_version": "0.0.0-dev", + "author": "Fedor Borshev", + "description": "", + "email": "fedor@borshev.com", + "name": "testproject", "_copy_without_render": [ - "*.py-tpl", - ".github/workflows/ci.yml" + ".github/actions/build/action.yml", + ".github/workflows/ci.yml", + "src/.django-app-template" ] } diff --git a/hooks/post_gen_project.sh b/hooks/post_gen_project.sh index 926215f4..e89239cd 100644 --- a/hooks/post_gen_project.sh +++ b/hooks/post_gen_project.sh @@ -1,33 +1,15 @@ #!/bin/bash -e -echo -ne "Running with " +cp src/app/.env.ci src/app/.env -python --version +poetry install -echo Creating and populating virtualenv.. +poetry run python src/manage.py collectstatic +poetry run python src/manage.py startapp some_app +poetry run python src/manage.py makemigrations -n "initial" -python -m venv venv -. venv/bin/activate +poetry run isort src/users/migrations/0001_initial.py -pip install --upgrade pip pip-tools wheel -make +poetry run python src/manage.py migrate -cd src - -echo Collecting static assets... -./manage.py collectstatic - -echo Running initial migrations... -./manage.py migrate - -cd ../ -echo Apply formatting.. -make fmt - -echo Running flake8.. -make lint - -echo Running pytest... make test - -echo Done diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..1fd9fefe --- /dev/null +++ b/poetry.lock @@ -0,0 +1,627 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "application-properties" +version = "0.8.1" +description = "A simple, easy to use, unified manner of accessing program properties." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "application_properties-0.8.1-py3-none-any.whl", hash = "sha256:f442e403ab7c8f97b048cf0ab92845057d595989653f65ced48579ad7cf35607"}, + {file = "application_properties-0.8.1.tar.gz", hash = "sha256:101667125383940651e72a2a9b3aa32225f359050c3be45b4ef4b51009930bb4"}, +] + +[package.dependencies] +pyyaml = ">=6.0" +tomli = ">=2.0.1" +typing-extensions = ">=4.5.0" + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + +[[package]] +name = "binaryornot" +version = "0.4.4" +description = "Ultra-lightweight pure Python package to check if a file is binary or text." +optional = false +python-versions = "*" +files = [ + {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, + {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, +] + +[package.dependencies] +chardet = ">=3.0.2" + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "columnar" +version = "1.4.1" +description = "A tool for printing data in a columnar format." +optional = false +python-versions = "*" +files = [ + {file = "Columnar-1.4.1-py3-none-any.whl", hash = "sha256:8efb692a7e6ca07dcc8f4ea889960421331a5dffa8e5af81f0a67ad8ea1fc798"}, + {file = "Columnar-1.4.1.tar.gz", hash = "sha256:c3cb57273333b2ff9cfaafc86f09307419330c97faa88dcfe23df05e6fbb9c72"}, +] + +[package.dependencies] +toolz = "*" +wcwidth = "*" + +[[package]] +name = "cookiecutter" +version = "2.5.0" +description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cookiecutter-2.5.0-py3-none-any.whl", hash = "sha256:8aa2f12ed11bc05628651e9dc4353a10571dd9908aaaaeec959a2b9ea465a5d2"}, + {file = "cookiecutter-2.5.0.tar.gz", hash = "sha256:e61e9034748e3f41b8bd2c11f00d030784b48711c4d5c42363c50989a65331ec"}, +] + +[package.dependencies] +arrow = "*" +binaryornot = ">=0.4.4" +click = ">=7.0,<9.0.0" +Jinja2 = ">=2.7,<4.0.0" +python-slugify = ">=4.0.0" +pyyaml = ">=5.3.1" +requests = ">=2.23.0" +rich = "*" + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pymarkdownlnt" +version = "0.9.14" +description = "A GitHub Flavored Markdown compliant Markdown linter." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pymarkdownlnt-0.9.14-py3-none-any.whl", hash = "sha256:7db466fe9170b8144a4101362171d111233dbd5c7710f896c525456e27559efb"}, + {file = "pymarkdownlnt-0.9.14.tar.gz", hash = "sha256:3f75977a812a14b305773167a8774b6930ff30528cd7f75da8c4e865c19a504f"}, +] + +[package.dependencies] +application-properties = ">=0.8.1" +columnar = ">=1.4.0" +typing-extensions = ">=4.7.0" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-slugify" +version = "8.0.1" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, + {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + +[[package]] +name = "toml-sort" +version = "0.23.1" +description = "Toml sorting library" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "toml_sort-0.23.1-py3-none-any.whl", hash = "sha256:69ae60de9c4d67478533697eb4119092e2b30ddffe5ca09bbad3912905c935a0"}, + {file = "toml_sort-0.23.1.tar.gz", hash = "sha256:833728c48b0f8d509aecd9ae8347768ca3a9332977b32c9fd2002932f0eb9c90"}, +] + +[package.dependencies] +tomlkit = ">=0.11.2" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.3" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, +] + +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.5" +files = [ + {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, + {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.8.19.14" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = "*" +files = [ + {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, + {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, +] + +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + +[[package]] +name = "urllib3" +version = "2.1.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.12" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "~3.11" +content-hash = "f2d4bf92c0c7a1e19ee869b62c4c7cb88fa7c88863a3f9ab953c6adc69cc6624" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b20f5181 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.poetry] +authors = ["Fedor Borshev "] +description = "" +name = "django-fandsdev" +package-mode = false +readme = "README.md" +version = "2023.11.29" + +[tool.poetry.dependencies] +cookiecutter = "^2.5.0" +python = "~3.11" + +[tool.poetry.group.dev.dependencies] +pymarkdownlnt = "^0.9.14" +toml-sort = "^0.23.1" + +[tool.pymarkdown.plugins.md013] +enabled = false + +[tool.pymarkdown.plugins.md026] +enabled = false + +[tool.pymarkdown.plugins.md045] +enabled = false + +[tool.tomlsort] +all = true +in_place = true +sort_first = ["tool.poetry"] +spaces_before_inline_comment = 2 +spaces_indent_inline_array = 4 +trailing_comma_inline_array = true diff --git a/{{cookiecutter.project_slug}}/.dockerignore b/{{ cookiecutter.name }}/.dockerignore similarity index 100% rename from {{cookiecutter.project_slug}}/.dockerignore rename to {{ cookiecutter.name }}/.dockerignore diff --git a/{{cookiecutter.project_slug}}/.editorconfig b/{{ cookiecutter.name }}/.editorconfig similarity index 100% rename from {{cookiecutter.project_slug}}/.editorconfig rename to {{ cookiecutter.name }}/.editorconfig diff --git a/{{ cookiecutter.name }}/.github/actions/build/action.yml b/{{ cookiecutter.name }}/.github/actions/build/action.yml new file mode 100644 index 00000000..41a6ed4e --- /dev/null +++ b/{{ cookiecutter.name }}/.github/actions/build/action.yml @@ -0,0 +1,42 @@ +name: build + +runs: + using: composite + + steps: + - name: load cached poetry installation + id: cached-poetry + uses: actions/cache@v4 + with: + path: ~/.local + key: poetry-v1-${{ hashFiles('poetry.lock') }} + + - name: install poetry + if: steps.cached-poetry.outputs.cache-hit != true + uses: snok/install-poetry@v1 + with: + version: 1.8.2 + + - name: install python + id: setup-python + uses: actions/setup-python@v5 + with: + cache: poetry + python-version-file: pyproject.toml + + - name: make sure poetry lockfile is up to date + run: poetry check --lock + shell: bash + + - name: save python version to the env + run: echo 'python-version=${{ steps.setup-python.outputs.python-version }}' >> $GITHUB_ENV + shell: bash + + - name: install deps + if: steps.setup-python.outputs.cache-hit != true + run: poetry install --no-interaction --no-root + shell: bash + + - name: restore default environment + run: cp src/app/.env.ci src/app/.env + shell: bash diff --git a/{{ cookiecutter.name }}/.github/workflows/ci.yml b/{{ cookiecutter.name }}/.github/workflows/ci.yml new file mode 100644 index 00000000..f29d0931 --- /dev/null +++ b/{{ cookiecutter.name }}/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build + uses: ./.github/actions/build + + - name: lint + run: make lint + + test: + needs: lint + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16.1-alpine + env: + POSTGRES_PASSWORD: secret + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7.2.3-alpine + ports: + - 6379:6379 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build + uses: ./.github/actions/build + + - name: install locale stuff + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: locales-all gettext + version: 1 + + - name: get number of cpu cores + uses: SimenB/github-actions-cpu-cores@v2.0.0 + id: cpu-cores + + - name: test + env: + DATABASE_URL: postgres://postgres:secret@localhost:5432/postgres + run: make test -e SIMULTANEOS_TEST_JOBS=${{ steps.cpu-cores.outputs.count }} + + build-docker-image: + needs: test + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: build + uses: ./.github/actions/build + + - name: setup qemu + uses: docker/setup-qemu-action@v3 + + - name: setup buildx + uses: docker/setup-buildx-action@v3 + + - name: make sure docker image is buildable + uses: docker/build-push-action@v5 + with: + build-args: | + PYTHON_VERSION=${{ env.python-version }} + cache-from: type=gha + cache-to: type=gha,mode=max + context: . + # make sure you log to the container registry before pushing + # see https://github.com/docker/login-action + push: false diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{ cookiecutter.name }}/.gitignore similarity index 99% rename from {{cookiecutter.project_slug}}/.gitignore rename to {{ cookiecutter.name }}/.gitignore index 21fba42a..97a35bae 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{ cookiecutter.name }}/.gitignore @@ -6,13 +6,13 @@ __pycache__/ *.py[cod] *$py.class +**/.DS_Store # C extensions *.so # Distribution / packaging .Python -build/ develop-eggs/ dist/ downloads/ diff --git a/{{ cookiecutter.name }}/Dockerfile b/{{ cookiecutter.name }}/Dockerfile new file mode 100644 index 00000000..ae69f989 --- /dev/null +++ b/{{ cookiecutter.name }}/Dockerfile @@ -0,0 +1,54 @@ +ARG PYTHON_VERSION + +# +# Compile custom uwsgi, cuz debian's one is weird +# +FROM python:${PYTHON_VERSION}-slim-bookworm as uwsgi-compile +ENV _UWSGI_VERSION 2.0.24 +RUN apt-get update && apt-get --no-install-recommends install -y build-essential wget && rm -rf /var/lib/apt/lists/* +RUN wget -O uwsgi-${_UWSGI_VERSION}.tar.gz https://github.com/unbit/uwsgi/archive/${_UWSGI_VERSION}.tar.gz \ + && tar zxvf uwsgi-*.tar.gz \ + && UWSGI_BIN_NAME=/uwsgi make -C uwsgi-${_UWSGI_VERSION} \ + && rm -Rf uwsgi-* + +# +# Build poetry and export compiled dependecines as plain requirements.txt +# +FROM python:${PYTHON_VERSION}-slim-bookworm as deps-compile + +WORKDIR / +COPY poetry.lock pyproject.toml / + +# Version is taken from poetry.lock, assuming it is generated with up-to-date version of poetry +RUN pip install --no-cache-dir poetry==$(cat poetry.lock |head -n1|awk -v FS='(Poetry |and)' '{print $2}') +RUN poetry export --format=requirements.txt > requirements.txt + +# +# Base image with django dependecines +# +FROM python:${PYTHON_VERSION}-slim-bookworm as base +LABEL maintainer="{{ cookiecutter.email }}" +LABEL com.datadoghq.ad.logs='[{"service": "django", "source": "uwsgi"}]' + +ENV DEBIAN_FRONTEND noninteractive +ENV PYTHONUNBUFFERED 1 +ENV STATIC_ROOT /var/lib/django-static + +RUN apt-get update \ + && apt-get --no-install-recommends install -y gettext locales-all tzdata git wait-for-it wget \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=uwsgi-compile /uwsgi /usr/local/bin/ +COPY --from=deps-compile /requirements.txt / + +RUN pip install --no-cache-dir --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt + +WORKDIR /src +COPY src /src + +RUN ./manage.py compilemessages +RUN ./manage.py collectstatic --noinput + +FROM base as web +CMD ./manage.py migrate && uwsgi --master --http :8000 --module app.wsgi --workers 2 --threads 2 --harakiri 25 --max-requests 1000 --log-x-forwarded-for diff --git a/{{ cookiecutter.name }}/Makefile b/{{ cookiecutter.name }}/Makefile new file mode 100644 index 00000000..c804626b --- /dev/null +++ b/{{ cookiecutter.name }}/Makefile @@ -0,0 +1,25 @@ +SIMULTANEOS_TEST_JOBS = 4 + +manage = poetry run python src/manage.py + +fmt: + poetry run autoflake --in-place --remove-all-unused-imports --recursive src + poetry run isort src + poetry run black src + poetry run toml-sort pyproject.toml + +lint: + $(manage) check + $(manage) makemigrations --check --dry-run --no-input + + poetry run isort --check-only src + poetry run black --check src + poetry run flake8 src + poetry run mypy src + poetry run toml-sort pyproject.toml --check + poetry run pymarkdown scan README.md + poetry run dotenv-linter src/app/.env.ci + +test: + poetry run pytest --dead-fixtures + poetry run pytest --create-db --exitfirst --numprocesses ${SIMULTANEOS_TEST_JOBS} diff --git a/{{cookiecutter.project_slug}}/README.md b/{{ cookiecutter.name }}/README.md similarity index 72% rename from {{cookiecutter.project_slug}}/README.md rename to {{ cookiecutter.name }}/README.md index 48002eb6..b70090a9 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{ cookiecutter.name }}/README.md @@ -5,57 +5,50 @@ This project is bootstrapped using [fandsdev/django](http://github.com/fandsdev/ ## Project structure The main django app is called `app`. It contains `.env` file for django-environ. For examples see `src/app/.env.ci`. Here are some usefull app-wide tools: -* `app.admin` — app-wide django-admin customizations (empty yet), check out usage [examples](https://github.com/f213/django/tree/master/%7B%7Bcookiecutter.project_slug%7D%7D/src/app/admin) -* `app.test.api_client` (available as `api` and `anon` fixtures within pytest) — a [convinient DRF test client](https://github.com/f213/django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/src/users/tests/tests_whoami.py#L6-L16). + +* `app.admin` — app-wide django-admin customizations (empty yet), check out usage [examples](https://github.com/fandsdev/django/tree/master/%7B%7Bcookiecutter.name%7D%7D/src/app/admin) +* `app.test.api_client` (available as `api` and `anon` fixtures within pytest) — a [convinient DRF test client](https://github.com/fandsdev/django/blob/master/%7B%7Bcookiecutter.name%7D%7D/src/users/tests/tests_whoami.py#L6-L16). Django user model is located in the separate `users` app. Also, feel free to add as much django apps as you want. ## Installing on a local machine -This project requires python 3.10. Deps are managed by [pip-tools](https://github.com/jazzband/pip-tools) + +This project requires python 3.11. Deps are managed by [poetry](https://python-poetry.org). Install requirements: ```bash -$ pip install --upgrade pip pip-tools -$ make +poetry install ``` Run the server: ```bash -$ cd src && cp app/.env.ci app/.env # default environment variables -$ ./manage.py migrate -$ ./manage.py createsuperuser -$ ./manage.py runserver +poetry run python src/manage.py migrate +poetry run python src/manage.py createsuperuser +poetry run python src/manage.py runserver ``` -Testing: -```bash -# run lint -$ make lint - -# run unit tests -$ make test -``` - -Development servers: +Useful commands ```bash -# run django dev server -$ ./manage.py runserver +make fmt # run code formatters + +make lint # run code quality checks +make test # run tests ``` -## Backend Code requirements +## Backend code requirements ### Style * Obey [django's style guide](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style). -* Configure your IDE to use [flake8](https://pypi.python.org/pypi/flake8) for checking your python code. To run our linters manualy, do `make lint` +* Configure your IDE to use [flake8](https://pypi.python.org/pypi/flake8) for checking your python code. To run our linters manualy, do `make lint`. * Prefer English over your native language in comments and commit messages. -* Commit messages should contain the unique id of issue they are linked to (refs #100500) +* Commit messages should contain the unique id of issue they are linked to (refs #100500). * Every model, service and model method should have a docstring. ### Code organisation diff --git a/{{cookiecutter.project_slug}}/src/conftest.py b/{{ cookiecutter.name }}/conftest.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/conftest.py rename to {{ cookiecutter.name }}/conftest.py diff --git a/{{cookiecutter.project_slug}}/docker-compose.yml b/{{ cookiecutter.name }}/docker-compose.yml similarity index 100% rename from {{cookiecutter.project_slug}}/docker-compose.yml rename to {{ cookiecutter.name }}/docker-compose.yml diff --git a/{{cookiecutter.project_slug}}/src/mypy.ini b/{{ cookiecutter.name }}/mypy.ini similarity index 96% rename from {{cookiecutter.project_slug}}/src/mypy.ini rename to {{ cookiecutter.name }}/mypy.ini index bca1220b..605405cd 100644 --- a/{{cookiecutter.project_slug}}/src/mypy.ini +++ b/{{ cookiecutter.name }}/mypy.ini @@ -1,6 +1,6 @@ [mypy] -python_version = 3.10 -files = . +python_version = 3.11 +mypy_path = src namespace_packages = on explicit_package_bases = on warn_no_return = off diff --git a/{{ cookiecutter.name }}/pyproject.toml b/{{ cookiecutter.name }}/pyproject.toml new file mode 100644 index 00000000..e96048ef --- /dev/null +++ b/{{ cookiecutter.name }}/pyproject.toml @@ -0,0 +1,126 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.poetry] +authors = ["{{ cookiecutter.author }} <{{ cookiecutter.email }}>"] +description = "{{ cookiecutter.description }}" +name = "{{ cookiecutter.name }}" +package-mode = false +readme = "README.md" +version = "0.0.0-dev" + +[tool.poetry.dependencies] +bcrypt = "^4.0.1" +django = "^4.2.7" +django-axes = "^6.1.1" +django-behaviors = "^0.5.1" +django-environ = "^0.11.2" +django-filter = "^23.4" +django-healthchecks = "^1.5.0" +django-ipware = "^6.0.0" +django-split-settings = "^1.2.0" +django-storages = "^1.14.2" +djangorestframework = "^3.14.0" +djangorestframework-camel-case = "^1.4.2" +drf-jwt = "^1.19.2" +drf-spectacular = {extras = ["sidecar"], version = "^0.26.5"} +pillow = "^10.1.0" +psycopg2-binary = "^2.9.9" +python = "~3.11" +redis = "^5.0.1" +sentry-sdk = "^1.37.0" +whitenoise = "^6.6.0" + +[tool.poetry.group.dev.dependencies] +autoflake = "^2.2.1" +black = "^23.11.0" +django-stubs = "^4.2.6" +djangorestframework-stubs = "^3.14.4" +dotenv-linter = "^0.4.0" +flake8-absolute-import = "^1.0.0.2" +flake8-bugbear = "^23.9.16" +flake8-cognitive-complexity = "^0.1.0" +flake8-django = "^1.4" +flake8-eradicate = "^1.5.0" +flake8-fixme = "^1.1.1" +flake8-pep3101 = "^2.1.0" +flake8-pie = "^0.16.0" +flake8-print = "^5.0.0" +flake8-printf-formatting = "^1.1.2" +flake8-pyproject = "^1.2.3" +flake8-variables-names = "^0.0.6" +flake8-walrus = "^1.2.0" +freezegun = "^1.2.2" +ipython = "^8.18.0" +isort = "^5.12.0" +jedi = "^0.19.1" +mixer = {extras = ["django"], version = "^7.2.2"} +mypy = "^1.7.1" +pymarkdownlnt = "^0.9.14" +pytest-deadfixtures = "^2.2.1" +pytest-django = "^4.7.0" +pytest-env = "^1.1.1" +pytest-freezegun = "^0.4.2" +pytest-mock = "^3.12.0" +pytest-randomly = "^3.15.0" +pytest-xdist = "^3.5.0" +toml-sort = "^0.23.1" +types-freezegun = "^1.1.10" +types-pillow = "^10.1.0.2" + +[tool.black] +line_length = 160 + +[tool.flake8] +exclude = ["__pycache__", "migrations"] +ignore = [ + "E203", # whitespace before ':' + "E265", # block comment should start with '#' + "E501", # line too long ({} > {} characters) + "F811", # redefinition of unused name from line {} + "PT001", # use @pytest.fixture() over @pytest.fixture + "SIM102", # use a single if-statement instead of nested if-statements + "SIM113", # use enumerate instead of manually incrementing a counter +] +inline-quotes = '"' + +[tool.isort] +include_trailing_comma = true +line_length = 160 +multi_line_output = 3 +src_paths = ["src", "tests"] +use_parentheses = true + +[tool.pymarkdown.plugins.md013] +enabled = false + +[tool.pymarkdown.plugins.md045] +enabled = false + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "app.settings" +addopts = ["--reuse-db"] +env = [ + "AXES_ENABLED = False", + "CELERY_ALWAYS_EAGER = True", + "CI = 1", + "DISABLE_THROTTLING = True", +] +filterwarnings = [ + "ignore:.*'rest_framework_jwt.blacklist' defines default_app_config.*You can remove default_app_config.::django", + "ignore:distutils Version classes are deprecated. Use packaging.version instead.:DeprecationWarning:pytest_freezegun:17", +] +markers = [ + "freeze_time: freezing time marker (pytest-freezegun does not register it)", +] +python_files = ["test*.py"] +pythonpath = ". src" + +[tool.tomlsort] +all = true +in_place = true +sort_first = ["tool.poetry"] +spaces_before_inline_comment = 2 +spaces_indent_inline_array = 4 +trailing_comma_inline_array = true diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/__init__.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/.django-app-template/__init__.py-tpl rename to {{ cookiecutter.name }}/src/.django-app-template/__init__.py-tpl diff --git a/{{ cookiecutter.name }}/src/.django-app-template/admin.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/admin.py-tpl new file mode 100644 index 00000000..b1853f8a --- /dev/null +++ b/{{ cookiecutter.name }}/src/.django-app-template/admin.py-tpl @@ -0,0 +1 @@ +from app.admin import ModelAdmin, admin # noqa: F401 diff --git a/{{ cookiecutter.name }}/src/.django-app-template/admin/__init__.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/admin/__init__.py-tpl new file mode 100644 index 00000000..4a790127 --- /dev/null +++ b/{{ cookiecutter.name }}/src/.django-app-template/admin/__init__.py-tpl @@ -0,0 +1,6 @@ +from app.admin import ModelAdmin, admin + +__all__ = [ + "admin", + "ModelAdmin", +] diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/migrations/__init__.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/api/v1/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/.django-app-template/migrations/__init__.py-tpl rename to {{ cookiecutter.name }}/src/.django-app-template/api/v1/__init__.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/a12n/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/api/v1/serializers/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/api/v1/serializers/__init__.py-tpl diff --git a/{{ cookiecutter.name }}/src/.django-app-template/api/v1/urls.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/api/v1/urls.py-tpl new file mode 100644 index 00000000..b0df5123 --- /dev/null +++ b/{{ cookiecutter.name }}/src/.django-app-template/api/v1/urls.py-tpl @@ -0,0 +1,8 @@ +from django.urls import include, path +from rest_framework.routers import SimpleRouter + +router = SimpleRouter() + +urlpatterns = [ + path("", include(router.urls)), +] diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/api/serializers/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/api/v1/views/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/.django-app-template/api/serializers/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/api/v1/views/__init__.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/apps.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/apps.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/.django-app-template/apps.py-tpl rename to {{ cookiecutter.name }}/src/.django-app-template/apps.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/a12n/migrations/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/factory.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/migrations/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/factory.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/app/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/fixtures.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/fixtures.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/app/locale/.gitkeep b/{{ cookiecutter.name }}/src/.django-app-template/migrations/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/locale/.gitkeep rename to {{ cookiecutter.name }}/src/.django-app-template/migrations/__init__.py-tpl diff --git a/{{ cookiecutter.name }}/src/.django-app-template/models/__init__.py-tpl b/{{ cookiecutter.name }}/src/.django-app-template/models/__init__.py-tpl new file mode 100644 index 00000000..0d31b3d8 --- /dev/null +++ b/{{ cookiecutter.name }}/src/.django-app-template/models/__init__.py-tpl @@ -0,0 +1,7 @@ +from app.models import DefaultModel, TimestampedModel, models + +__all__ = [ + "models", + "DefaultModel", + "TimestampedModel", +] diff --git a/{{cookiecutter.project_slug}}/src/app/tests/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/tests/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/tests/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/tests/__init__.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/app/tests/testing/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/tests/api/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/tests/testing/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/tests/api/__init__.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/app/tests/testing/factory/__init__.py b/{{ cookiecutter.name }}/src/.django-app-template/tests/api/v1/__init__.py-tpl similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/tests/testing/factory/__init__.py rename to {{ cookiecutter.name }}/src/.django-app-template/tests/api/v1/__init__.py-tpl diff --git a/{{cookiecutter.project_slug}}/src/users/__init__.py b/{{ cookiecutter.name }}/src/a12n/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/__init__.py rename to {{ cookiecutter.name }}/src/a12n/__init__.py diff --git a/{{cookiecutter.project_slug}}/src/a12n/api/throttling.py b/{{ cookiecutter.name }}/src/a12n/api/throttling.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/api/throttling.py rename to {{ cookiecutter.name }}/src/a12n/api/throttling.py diff --git a/{{cookiecutter.project_slug}}/src/a12n/api/urls.py b/{{ cookiecutter.name }}/src/a12n/api/urls.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/a12n/api/urls.py rename to {{ cookiecutter.name }}/src/a12n/api/urls.py index 955c2c52..00c6ac72 100644 --- a/{{cookiecutter.project_slug}}/src/a12n/api/urls.py +++ b/{{ cookiecutter.name }}/src/a12n/api/urls.py @@ -3,6 +3,7 @@ from a12n.api import views app_name = "a12n" + urlpatterns = [ path("token/", views.ObtainJSONWebTokenView.as_view()), path("token/refresh/", views.RefreshJSONWebTokenView.as_view()), diff --git a/{{cookiecutter.project_slug}}/src/a12n/api/views.py b/{{ cookiecutter.name }}/src/a12n/api/views.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/api/views.py rename to {{ cookiecutter.name }}/src/a12n/api/views.py diff --git a/{{cookiecutter.project_slug}}/src/users/migrations/__init__.py b/{{ cookiecutter.name }}/src/a12n/migrations/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/migrations/__init__.py rename to {{ cookiecutter.name }}/src/a12n/migrations/__init__.py diff --git a/{{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py b/{{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py rename to {{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py index ec75c499..329dd3d6 100644 --- a/{{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py +++ b/{{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_obtain_jwt_view.py @@ -1,6 +1,6 @@ import json -import pytest +import pytest from axes.models import AccessAttempt pytestmark = pytest.mark.django_db diff --git a/{{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py b/{{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py rename to {{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py index f9d66cbe..a0129a34 100644 --- a/{{cookiecutter.project_slug}}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py +++ b/{{ cookiecutter.name }}/src/a12n/tests/jwt_views/test_refresh_jwt_token.py @@ -1,5 +1,4 @@ import pytest - from freezegun import freeze_time from a12n.utils import get_jwt @@ -57,7 +56,6 @@ def test_refresh_token_fails_with_incorrect_previous_token(refresh_token): def test_token_is_not_allowed_to_refresh_if_expired(initial_token, refresh_token): with freeze_time("2049-02-05"): - result = refresh_token(initial_token, expected_status=400) assert "expired" in result["nonFieldErrors"][0] diff --git a/{{cookiecutter.project_slug}}/src/a12n/utils.py b/{{ cookiecutter.name }}/src/a12n/utils.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/a12n/utils.py rename to {{ cookiecutter.name }}/src/a12n/utils.py diff --git a/{{ cookiecutter.name }}/src/app/.env.ci b/{{ cookiecutter.name }}/src/app/.env.ci new file mode 100644 index 00000000..eb1625da --- /dev/null +++ b/{{ cookiecutter.name }}/src/app/.env.ci @@ -0,0 +1,3 @@ +DATABASE_URL=postgres://postgres:@localhost:5432/postgres +DEBUG=off +SECRET_KEY={{ random_ascii_string(48, punctuation=True) }} diff --git a/{{ cookiecutter.name }}/src/app/__init__.py b/{{ cookiecutter.name }}/src/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/app/admin/__init__.py b/{{ cookiecutter.name }}/src/app/admin/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/admin/__init__.py rename to {{ cookiecutter.name }}/src/app/admin/__init__.py diff --git a/{{cookiecutter.project_slug}}/src/app/admin/model_admin.py b/{{ cookiecutter.name }}/src/app/admin/model_admin.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/admin/model_admin.py rename to {{ cookiecutter.name }}/src/app/admin/model_admin.py diff --git a/{{cookiecutter.project_slug}}/src/app/api/pagination.py b/{{ cookiecutter.name }}/src/app/api/pagination.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/app/api/pagination.py rename to {{ cookiecutter.name }}/src/app/api/pagination.py index 85f200fd..c8b88cad 100644 --- a/{{cookiecutter.project_slug}}/src/app/api/pagination.py +++ b/{{ cookiecutter.name }}/src/app/api/pagination.py @@ -1,6 +1,5 @@ -from rest_framework.pagination import PageNumberPagination - from django.conf import settings +from rest_framework.pagination import PageNumberPagination class AppPagination(PageNumberPagination): diff --git a/{{cookiecutter.project_slug}}/src/app/api/renderers.py b/{{ cookiecutter.name }}/src/app/api/renderers.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/api/renderers.py rename to {{ cookiecutter.name }}/src/app/api/renderers.py diff --git a/{{cookiecutter.project_slug}}/src/app/api/throttling.py b/{{ cookiecutter.name }}/src/app/api/throttling.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/app/api/throttling.py rename to {{ cookiecutter.name }}/src/app/api/throttling.py index 46028513..99dd51c6 100644 --- a/{{cookiecutter.project_slug}}/src/app/api/throttling.py +++ b/{{ cookiecutter.name }}/src/app/api/throttling.py @@ -1,10 +1,9 @@ from typing import Protocol +from django.conf import settings from rest_framework.request import Request from rest_framework.views import APIView -from django.conf import settings - class BaseThrottle(Protocol): def allow_request(self, request: Request, view: APIView) -> bool: diff --git a/{{cookiecutter.project_slug}}/src/app/api/viewsets.py b/{{ cookiecutter.name }}/src/app/api/viewsets.py similarity index 96% rename from {{cookiecutter.project_slug}}/src/app/api/viewsets.py rename to {{ cookiecutter.name }}/src/app/api/viewsets.py index f64616d1..a5f60b91 100644 --- a/{{cookiecutter.project_slug}}/src/app/api/viewsets.py +++ b/{{ cookiecutter.name }}/src/app/api/viewsets.py @@ -1,9 +1,7 @@ from typing import Any, Optional, Protocol, Type -from rest_framework import mixins -from rest_framework import status -from rest_framework.mixins import CreateModelMixin -from rest_framework.mixins import UpdateModelMixin +from rest_framework import mixins, status +from rest_framework.mixins import CreateModelMixin, UpdateModelMixin from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import BaseSerializer diff --git a/{{cookiecutter.project_slug}}/src/app/base_config.py b/{{ cookiecutter.name }}/src/app/base_config.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/base_config.py rename to {{ cookiecutter.name }}/src/app/base_config.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/api.py b/{{ cookiecutter.name }}/src/app/conf/api.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/api.py rename to {{ cookiecutter.name }}/src/app/conf/api.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/auth.py b/{{ cookiecutter.name }}/src/app/conf/auth.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/auth.py rename to {{ cookiecutter.name }}/src/app/conf/auth.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/boilerplate.py b/{{ cookiecutter.name }}/src/app/conf/boilerplate.py similarity index 71% rename from {{cookiecutter.project_slug}}/src/app/conf/boilerplate.py rename to {{ cookiecutter.name }}/src/app/conf/boilerplate.py index 7e88e28c..f16ff2a1 100644 --- a/{{cookiecutter.project_slug}}/src/app/conf/boilerplate.py +++ b/{{ cookiecutter.name }}/src/app/conf/boilerplate.py @@ -1,6 +1,6 @@ -import os.path +from pathlib import Path -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR = Path(__file__).resolve().parent.parent ROOT_URLCONF = "app.urls" diff --git a/{{cookiecutter.project_slug}}/src/app/conf/db.py b/{{ cookiecutter.name }}/src/app/conf/db.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/db.py rename to {{ cookiecutter.name }}/src/app/conf/db.py diff --git a/{{ cookiecutter.name }}/src/app/conf/environ.py b/{{ cookiecutter.name }}/src/app/conf/environ.py new file mode 100644 index 00000000..ad55b162 --- /dev/null +++ b/{{ cookiecutter.name }}/src/app/conf/environ.py @@ -0,0 +1,17 @@ +import environ # type: ignore[import-untyped] + +from app.conf.boilerplate import BASE_DIR + +env = environ.Env( + DEBUG=(bool, False), + CI=(bool, False), +) + +envpath = BASE_DIR / ".env" + +if envpath.exists(): + env.read_env(envpath) + +__all__ = [ + "env", +] diff --git a/{{cookiecutter.project_slug}}/src/app/conf/healthchecks.py b/{{ cookiecutter.name }}/src/app/conf/healthchecks.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/healthchecks.py rename to {{ cookiecutter.name }}/src/app/conf/healthchecks.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/http.py b/{{ cookiecutter.name }}/src/app/conf/http.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/http.py rename to {{ cookiecutter.name }}/src/app/conf/http.py diff --git a/{{ cookiecutter.name }}/src/app/conf/i18n.py b/{{ cookiecutter.name }}/src/app/conf/i18n.py new file mode 100644 index 00000000..6318ccc6 --- /dev/null +++ b/{{ cookiecutter.name }}/src/app/conf/i18n.py @@ -0,0 +1,9 @@ +from pathlib import Path + +from app.conf.boilerplate import BASE_DIR + +LANGUAGE_CODE = "ru" + +LOCALE_PATHS = [Path(BASE_DIR).parent / "locale"] + +USE_i18N = True diff --git a/{{cookiecutter.project_slug}}/src/app/conf/installed_apps.py b/{{ cookiecutter.name }}/src/app/conf/installed_apps.py similarity index 97% rename from {{cookiecutter.project_slug}}/src/app/conf/installed_apps.py rename to {{ cookiecutter.name }}/src/app/conf/installed_apps.py index efab9b26..0bc0e14f 100644 --- a/{{cookiecutter.project_slug}}/src/app/conf/installed_apps.py +++ b/{{ cookiecutter.name }}/src/app/conf/installed_apps.py @@ -1,7 +1,6 @@ # Application definition APPS = [ - "app", "a12n", "users", ] diff --git a/{{cookiecutter.project_slug}}/src/app/conf/media.py b/{{ cookiecutter.name }}/src/app/conf/media.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/media.py rename to {{ cookiecutter.name }}/src/app/conf/media.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/middleware.py b/{{ cookiecutter.name }}/src/app/conf/middleware.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/middleware.py rename to {{ cookiecutter.name }}/src/app/conf/middleware.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/sentry.py b/{{ cookiecutter.name }}/src/app/conf/sentry.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/sentry.py rename to {{ cookiecutter.name }}/src/app/conf/sentry.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/static.py b/{{ cookiecutter.name }}/src/app/conf/static.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/static.py rename to {{ cookiecutter.name }}/src/app/conf/static.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/storage.py b/{{ cookiecutter.name }}/src/app/conf/storage.py similarity index 57% rename from {{cookiecutter.project_slug}}/src/app/conf/storage.py rename to {{ cookiecutter.name }}/src/app/conf/storage.py index 761bdf2b..d76063f7 100644 --- a/{{cookiecutter.project_slug}}/src/app/conf/storage.py +++ b/{{ cookiecutter.name }}/src/app/conf/storage.py @@ -1,6 +1,17 @@ from app.conf.environ import env -DEFAULT_FILE_STORAGE = env("DEFAULT_FILE_STORAGE", cast=str, default="django.core.files.storage.FileSystemStorage") +STORAGES = { + "default": { + "BACKEND": env( + "FILE_STORAGE", + cast=str, + default="django.core.files.storage.FileSystemStorage", + ), + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default="") AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY", default="") diff --git a/{{cookiecutter.project_slug}}/src/app/conf/templates.py b/{{ cookiecutter.name }}/src/app/conf/templates.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/templates.py rename to {{ cookiecutter.name }}/src/app/conf/templates.py diff --git a/{{cookiecutter.project_slug}}/src/app/conf/timezone.py b/{{ cookiecutter.name }}/src/app/conf/timezone.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/conf/timezone.py rename to {{ cookiecutter.name }}/src/app/conf/timezone.py diff --git a/{{cookiecutter.project_slug}}/src/app/factory.py b/{{ cookiecutter.name }}/src/app/factory.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/app/factory.py rename to {{ cookiecutter.name }}/src/app/factory.py index 036810d3..cd7338a4 100644 --- a/{{cookiecutter.project_slug}}/src/app/factory.py +++ b/{{ cookiecutter.name }}/src/app/factory.py @@ -1,6 +1,5 @@ -from faker import Faker - from django.core.files.uploadedfile import SimpleUploadedFile +from faker import Faker from app.testing import register from app.testing.types import FactoryProtocol diff --git a/{{cookiecutter.project_slug}}/src/app/fixtures/__init__.py b/{{ cookiecutter.name }}/src/app/fixtures/__init__.py similarity index 57% rename from {{cookiecutter.project_slug}}/src/app/fixtures/__init__.py rename to {{ cookiecutter.name }}/src/app/fixtures/__init__.py index 8e7e465e..24efa781 100644 --- a/{{cookiecutter.project_slug}}/src/app/fixtures/__init__.py +++ b/{{ cookiecutter.name }}/src/app/fixtures/__init__.py @@ -1,5 +1,4 @@ -from app.fixtures.api import as_anon -from app.fixtures.api import as_user +from app.fixtures.api import as_anon, as_user from app.fixtures.factory import factory __all__ = [ diff --git a/{{cookiecutter.project_slug}}/src/app/fixtures/api.py b/{{ cookiecutter.name }}/src/app/fixtures/api.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/fixtures/api.py rename to {{ cookiecutter.name }}/src/app/fixtures/api.py diff --git a/{{cookiecutter.project_slug}}/src/app/fixtures/factory.py b/{{ cookiecutter.name }}/src/app/fixtures/factory.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/fixtures/factory.py rename to {{ cookiecutter.name }}/src/app/fixtures/factory.py diff --git a/{{cookiecutter.project_slug}}/src/app/management/commands/makemigrations.py b/{{ cookiecutter.name }}/src/app/management/commands/makemigrations.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/management/commands/makemigrations.py rename to {{ cookiecutter.name }}/src/app/management/commands/makemigrations.py diff --git a/{{cookiecutter.project_slug}}/src/app/management/commands/startapp.py b/{{ cookiecutter.name }}/src/app/management/commands/startapp.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/management/commands/startapp.py rename to {{ cookiecutter.name }}/src/app/management/commands/startapp.py diff --git a/{{cookiecutter.project_slug}}/src/app/middleware/real_ip.py b/{{ cookiecutter.name }}/src/app/middleware/real_ip.py similarity index 89% rename from {{cookiecutter.project_slug}}/src/app/middleware/real_ip.py rename to {{ cookiecutter.name }}/src/app/middleware/real_ip.py index a8b536a9..5fa01841 100644 --- a/{{cookiecutter.project_slug}}/src/app/middleware/real_ip.py +++ b/{{ cookiecutter.name }}/src/app/middleware/real_ip.py @@ -1,10 +1,8 @@ from typing import Callable +from django.http import HttpRequest, HttpResponse from ipware import get_client_ip -from django.http import HttpRequest -from django.http import HttpResponse - def real_ip_middleware(get_response: Callable) -> Callable: """Set request.META["REMOTE_ADDR"] to ip guessed by django-ipware. diff --git a/{{cookiecutter.project_slug}}/src/app/models.py b/{{ cookiecutter.name }}/src/app/models.py similarity index 96% rename from {{cookiecutter.project_slug}}/src/app/models.py rename to {{ cookiecutter.name }}/src/app/models.py index cec342ce..5f9c12b9 100644 --- a/{{cookiecutter.project_slug}}/src/app/models.py +++ b/{{ cookiecutter.name }}/src/app/models.py @@ -1,7 +1,6 @@ from typing import Any from behaviors.behaviors import Timestamped # type: ignore - from django.contrib.contenttypes.models import ContentType from django.db import models @@ -30,7 +29,7 @@ def get_contenttype(cls) -> ContentType: def update_from_kwargs(self, **kwargs: dict[str, Any]) -> None: """A shortcut method to update model instance from the kwargs.""" - for (key, value) in kwargs.items(): + for key, value in kwargs.items(): setattr(self, key, value) def setattr_and_save(self, key: str, value: Any) -> None: diff --git a/{{cookiecutter.project_slug}}/src/app/services.py b/{{ cookiecutter.name }}/src/app/services.py similarity index 96% rename from {{cookiecutter.project_slug}}/src/app/services.py rename to {{ cookiecutter.name }}/src/app/services.py index 61988855..f2de05c1 100644 --- a/{{cookiecutter.project_slug}}/src/app/services.py +++ b/{{ cookiecutter.name }}/src/app/services.py @@ -1,5 +1,4 @@ -from abc import ABCMeta -from abc import abstractmethod +from abc import ABCMeta, abstractmethod from typing import Any, Callable diff --git a/{{cookiecutter.project_slug}}/src/app/settings.py b/{{ cookiecutter.name }}/src/app/settings.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/settings.py rename to {{ cookiecutter.name }}/src/app/settings.py diff --git a/{{cookiecutter.project_slug}}/src/app/testing/__init__.py b/{{ cookiecutter.name }}/src/app/testing/__init__.py similarity index 55% rename from {{cookiecutter.project_slug}}/src/app/testing/__init__.py rename to {{ cookiecutter.name }}/src/app/testing/__init__.py index 89377995..7652f55f 100644 --- a/{{cookiecutter.project_slug}}/src/app/testing/__init__.py +++ b/{{ cookiecutter.name }}/src/app/testing/__init__.py @@ -1,6 +1,5 @@ from app.testing.api import ApiClient -from app.testing.factory import FixtureFactory -from app.testing.factory import register +from app.testing.factory import FixtureFactory, register __all__ = [ "ApiClient", diff --git a/{{cookiecutter.project_slug}}/src/app/testing/api.py b/{{ cookiecutter.name }}/src/app/testing/api.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/testing/api.py rename to {{ cookiecutter.name }}/src/app/testing/api.py index bc183534..dd1f8452 100644 --- a/{{cookiecutter.project_slug}}/src/app/testing/api.py +++ b/{{ cookiecutter.name }}/src/app/testing/api.py @@ -4,8 +4,8 @@ from typing import Optional from rest_framework.authtoken.models import Token -from rest_framework.test import APIClient as DRFAPIClient from rest_framework.response import Response +from rest_framework.test import APIClient as DRFAPIClient from users.models import User diff --git a/{{cookiecutter.project_slug}}/src/app/testing/factory.py b/{{ cookiecutter.name }}/src/app/testing/factory.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/testing/factory.py rename to {{ cookiecutter.name }}/src/app/testing/factory.py diff --git a/{{cookiecutter.project_slug}}/src/app/testing/mixer.py b/{{ cookiecutter.name }}/src/app/testing/mixer.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/testing/mixer.py rename to {{ cookiecutter.name }}/src/app/testing/mixer.py diff --git a/{{cookiecutter.project_slug}}/src/app/testing/runner.py b/{{ cookiecutter.name }}/src/app/testing/runner.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/testing/runner.py rename to {{ cookiecutter.name }}/src/app/testing/runner.py diff --git a/{{cookiecutter.project_slug}}/src/app/testing/types.py b/{{ cookiecutter.name }}/src/app/testing/types.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/testing/types.py rename to {{ cookiecutter.name }}/src/app/testing/types.py diff --git a/{{ cookiecutter.name }}/src/app/tests/__init__.py b/{{ cookiecutter.name }}/src/app/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/app/tests/test_health.py b/{{ cookiecutter.name }}/src/app/tests/test_health.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/app/tests/test_health.py rename to {{ cookiecutter.name }}/src/app/tests/test_health.py diff --git a/{{cookiecutter.project_slug}}/src/app/tests/test_remote_addr_midlleware.py b/{{ cookiecutter.name }}/src/app/tests/test_remote_addr_midlleware.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/app/tests/test_remote_addr_midlleware.py rename to {{ cookiecutter.name }}/src/app/tests/test_remote_addr_midlleware.py index 44952417..7aefd56d 100644 --- a/{{cookiecutter.project_slug}}/src/app/tests/test_remote_addr_midlleware.py +++ b/{{ cookiecutter.name }}/src/app/tests/test_remote_addr_midlleware.py @@ -1,5 +1,4 @@ import pytest - from django.apps import apps from app.testing.api import ApiClient diff --git a/{{ cookiecutter.name }}/src/app/tests/testing/__init__.py b/{{ cookiecutter.name }}/src/app/tests/testing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{ cookiecutter.name }}/src/app/tests/testing/factory/__init__.py b/{{ cookiecutter.name }}/src/app/tests/testing/factory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_factory.py b/{{ cookiecutter.name }}/src/app/tests/testing/factory/test_factory.py similarity index 94% rename from {{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_factory.py rename to {{ cookiecutter.name }}/src/app/tests/testing/factory/test_factory.py index 858df883..465bd1c2 100644 --- a/{{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_factory.py +++ b/{{ cookiecutter.name }}/src/app/tests/testing/factory/test_factory.py @@ -1,7 +1,6 @@ import pytest -from app.testing import FixtureFactory -from app.testing import register +from app.testing import FixtureFactory, register @pytest.fixture diff --git a/{{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_registry.py b/{{ cookiecutter.name }}/src/app/tests/testing/factory/test_registry.py similarity index 86% rename from {{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_registry.py rename to {{ cookiecutter.name }}/src/app/tests/testing/factory/test_registry.py index 0dcfc793..389b7e52 100644 --- a/{{cookiecutter.project_slug}}/src/app/tests/testing/factory/test_registry.py +++ b/{{ cookiecutter.name }}/src/app/tests/testing/factory/test_registry.py @@ -1,7 +1,6 @@ import pytest -from app.testing.factory import FixtureRegistry -from app.testing.factory import register +from app.testing.factory import FixtureRegistry, register @pytest.fixture diff --git a/{{cookiecutter.project_slug}}/src/app/urls/__init__.py b/{{ cookiecutter.name }}/src/app/urls/__init__.py similarity index 75% rename from {{cookiecutter.project_slug}}/src/app/urls/__init__.py rename to {{ cookiecutter.name }}/src/app/urls/__init__.py index 319d5e65..659b1bc8 100644 --- a/{{cookiecutter.project_slug}}/src/app/urls/__init__.py +++ b/{{ cookiecutter.name }}/src/app/urls/__init__.py @@ -1,6 +1,5 @@ from django.contrib import admin -from django.urls import include -from django.urls import path +from django.urls import include, path api = [ path("v1/", include("app.urls.v1", namespace="v1")), diff --git a/{{cookiecutter.project_slug}}/src/app/urls/v1.py b/{{ cookiecutter.name }}/src/app/urls/v1.py similarity index 66% rename from {{cookiecutter.project_slug}}/src/app/urls/v1.py rename to {{ cookiecutter.name }}/src/app/urls/v1.py index c49cc038..dfd8d18e 100644 --- a/{{cookiecutter.project_slug}}/src/app/urls/v1.py +++ b/{{ cookiecutter.name }}/src/app/urls/v1.py @@ -1,10 +1,8 @@ -from drf_spectacular.views import SpectacularAPIView -from drf_spectacular.views import SpectacularSwaggerView - -from django.urls import include -from django.urls import path +from django.urls import include, path +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView app_name = "api_v1" + urlpatterns = [ path("auth/", include("a12n.api.urls")), path("users/", include("users.api.urls")), diff --git a/{{ cookiecutter.name }}/src/app/wsgi.py b/{{ cookiecutter.name }}/src/app/wsgi.py new file mode 100644 index 00000000..d468f8da --- /dev/null +++ b/{{ cookiecutter.name }}/src/app/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + +application = get_wsgi_application() diff --git a/{{ cookiecutter.name }}/src/locale/.gitkeep b/{{ cookiecutter.name }}/src/locale/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/manage.py b/{{ cookiecutter.name }}/src/manage.py similarity index 90% rename from {{cookiecutter.project_slug}}/src/manage.py rename to {{ cookiecutter.name }}/src/manage.py index 661e27f5..69525626 100755 --- a/{{cookiecutter.project_slug}}/src/manage.py +++ b/{{ cookiecutter.name }}/src/manage.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" import os import sys def main() -> None: os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -14,6 +14,7 @@ def main() -> None: "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?", ) from exc + execute_from_command_line(sys.argv) diff --git a/{{ cookiecutter.name }}/src/users/__init__.py b/{{ cookiecutter.name }}/src/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/users/admin.py b/{{ cookiecutter.name }}/src/users/admin.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/admin.py rename to {{ cookiecutter.name }}/src/users/admin.py diff --git a/{{cookiecutter.project_slug}}/src/users/api/serializers.py b/{{ cookiecutter.name }}/src/users/api/serializers.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/api/serializers.py rename to {{ cookiecutter.name }}/src/users/api/serializers.py diff --git a/{{cookiecutter.project_slug}}/src/users/api/urls.py b/{{ cookiecutter.name }}/src/users/api/urls.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/users/api/urls.py rename to {{ cookiecutter.name }}/src/users/api/urls.py index cf6239c9..4871b5e7 100644 --- a/{{cookiecutter.project_slug}}/src/users/api/urls.py +++ b/{{ cookiecutter.name }}/src/users/api/urls.py @@ -3,6 +3,7 @@ from users.api import viewsets app_name = "users" + urlpatterns = [ path("me/", viewsets.SelfView.as_view()), ] diff --git a/{{cookiecutter.project_slug}}/src/users/api/viewsets.py b/{{ cookiecutter.name }}/src/users/api/viewsets.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/users/api/viewsets.py rename to {{ cookiecutter.name }}/src/users/api/viewsets.py index d3268e50..3ac03e50 100644 --- a/{{cookiecutter.project_slug}}/src/users/api/viewsets.py +++ b/{{ cookiecutter.name }}/src/users/api/viewsets.py @@ -1,10 +1,9 @@ +from django.db.models import QuerySet from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response -from django.db.models import QuerySet - from users.api.serializers import UserSerializer from users.models import User diff --git a/{{cookiecutter.project_slug}}/src/users/factory.py b/{{ cookiecutter.name }}/src/users/factory.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/factory.py rename to {{ cookiecutter.name }}/src/users/factory.py diff --git a/{{cookiecutter.project_slug}}/src/users/fixtures.py b/{{ cookiecutter.name }}/src/users/fixtures.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/users/fixtures.py rename to {{ cookiecutter.name }}/src/users/fixtures.py index b8d2b207..d63e4b8b 100644 --- a/{{cookiecutter.project_slug}}/src/users/fixtures.py +++ b/{{ cookiecutter.name }}/src/users/fixtures.py @@ -1,6 +1,7 @@ -import pytest from typing import TYPE_CHECKING +import pytest + from users.models import User if TYPE_CHECKING: diff --git a/{{ cookiecutter.name }}/src/users/migrations/__init__.py b/{{ cookiecutter.name }}/src/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.project_slug}}/src/users/models.py b/{{ cookiecutter.name }}/src/users/models.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/users/models.py rename to {{ cookiecutter.name }}/src/users/models.py index 5c821928..d1c036c5 100644 --- a/{{cookiecutter.project_slug}}/src/users/models.py +++ b/{{ cookiecutter.name }}/src/users/models.py @@ -1,6 +1,7 @@ +from typing import ClassVar + from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import UserManager as _UserManager -from typing import ClassVar class User(AbstractUser): # noqa diff --git a/{{cookiecutter.project_slug}}/src/users/tests/test_password_hashing.py b/{{ cookiecutter.name }}/src/users/tests/test_password_hashing.py similarity index 99% rename from {{cookiecutter.project_slug}}/src/users/tests/test_password_hashing.py rename to {{ cookiecutter.name }}/src/users/tests/test_password_hashing.py index 39418069..85c12260 100644 --- a/{{cookiecutter.project_slug}}/src/users/tests/test_password_hashing.py +++ b/{{ cookiecutter.name }}/src/users/tests/test_password_hashing.py @@ -1,6 +1,7 @@ -import pytest import uuid +import pytest + from users.models import User pytestmark = [pytest.mark.django_db] diff --git a/{{cookiecutter.project_slug}}/src/users/tests/test_whoami.py b/{{ cookiecutter.name }}/src/users/tests/test_whoami.py similarity index 100% rename from {{cookiecutter.project_slug}}/src/users/tests/test_whoami.py rename to {{ cookiecutter.name }}/src/users/tests/test_whoami.py diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml deleted file mode 100644 index 95458b84..00000000 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ /dev/null @@ -1,77 +0,0 @@ ---- -name: CI -on: push - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - id: setup-python - with: - python-version-file: '.python-version' - - - uses: actions/cache@v3 - id: cache-dependencies - with: - path: | - venv - key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/*requirements.txt') }} - - - name: Install dependencies - if: steps.cache-dependencies.outputs.cache-hit != 'true' - run: | - python -m venv venv - . venv/bin/activate - pip install --upgrade pip pip-tools - pip-sync requirements.txt dev-requirements.txt - - - name: Run the linter - run: | - . venv/bin/activate - cp src/app/.env.ci src/app/.env - make lint - - test: - needs: build - runs-on: ubuntu-latest - services: - postgres: - image: postgres:13.3-alpine - env: - POSTGRES_PASSWORD: secret - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - id: setup-python - with: - python-version-file: '.python-version' - - - uses: actions/cache@v3 - with: - path: | - venv - key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/*requirements.txt') }} - - - name: Install locale stuff - run: sudo apt-get update && sudo apt-get --no-install-recommends install -y locales-all gettext - - - name: Run the tests - env: - DATABASE_URL: postgres://postgres:secret@localhost:5432/postgres - - run: | - . venv/bin/activate - cp src/app/.env.ci src/app/.env - make test diff --git a/{{cookiecutter.project_slug}}/.python-version b/{{cookiecutter.project_slug}}/.python-version deleted file mode 100644 index 375f5cab..00000000 --- a/{{cookiecutter.project_slug}}/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11.6 diff --git a/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/Dockerfile deleted file mode 100644 index 6b277cb1..00000000 --- a/{{cookiecutter.project_slug}}/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -ARG PYTHON_VERSION -FROM python:${PYTHON_VERSION}-slim-bookworm -LABEL maintainer="fedor@borshev.com" - -LABEL com.datadoghq.ad.logs='[{"source": "uwsgi", "service": "django"}]' - -ENV PYTHONUNBUFFERED 1 -ENV DEBIAN_FRONTEND noninteractive - -ENV STATIC_ROOT /static - -ENV _UWSGI_VERSION 2.0.23 - -RUN echo deb http://deb.debian.org/debian bookworm contrib non-free > /etc/apt/sources.list.d/debian-contrib.list \ - && apt update \ - && apt --no-install-recommends install -y gettext locales-all wget \ - imagemagick tzdata wait-for-it build-essential \ - libxml2-dev libxslt1-dev libjpeg62-turbo-dev libjpeg-dev libfreetype6-dev \ - libtiff5-dev liblcms2-dev libwebp-dev tk8.6-dev \ - libffi-dev libcgraph6 libgraphviz-dev libmagic-dev libpq-dev \ - default-mysql-client default-libmysqlclient-dev \ - && rm -rf /var/lib/apt/lists/* - -RUN wget -O uwsgi-${_UWSGI_VERSION}.tar.gz https://github.com/unbit/uwsgi/archive/${_UWSGI_VERSION}.tar.gz \ - && tar zxvf uwsgi-*.tar.gz \ - && UWSGI_BIN_NAME=/usr/local/bin/uwsgi make -C uwsgi-${_UWSGI_VERSION} \ - && rm -Rf uwsgi-* - -RUN pip install --upgrade pip - -ADD requirements.txt / -RUN pip install --no-cache-dir -r /requirements.txt - -WORKDIR /src -ADD src /src - -RUN ./manage.py compilemessages -RUN ./manage.py collectstatic --noinput - -ARG RELEASE=dev-untagged -ENV SENTRY_RELEASE ${RELEASE} -ENV DD_VERSION ${RELEASE} - -USER nobody - -CMD uwsgi --master --http :8000 --module app.wsgi --workers 2 --threads 2 --harakiri 25 --max-requests 1000 --log-x-forwarded-for --buffer-size 32000 diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile deleted file mode 100644 index 0e04bbac..00000000 --- a/{{cookiecutter.project_slug}}/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -install-dev-deps: dev-deps - pip-sync requirements.txt dev-requirements.txt - -install-deps: deps - pip-sync requirements.txt - -deps: - pip-compile --resolver=backtracking --output-file=requirements.txt pyproject.toml - -dev-deps: deps - pip-compile --resolver=backtracking --extra=dev --output-file=dev-requirements.txt pyproject.toml - -fmt: - cd src && autoflake --in-place --remove-all-unused-imports --recursive . - cd src && isort . - cd src && black . - -lint: - dotenv-linter src/app/.env.ci - cd src && ./manage.py check - flake8 src - cd src && mypy - -test: - mkdir -p src/static - cd src && ./manage.py makemigrations --dry-run --no-input --check - cd src && ./manage.py compilemessages - cd src && pytest --dead-fixtures - cd src && pytest -x - -pr: fmt lint test diff --git a/{{cookiecutter.project_slug}}/dev-requirements.txt b/{{cookiecutter.project_slug}}/dev-requirements.txt deleted file mode 100644 index 201c8d2b..00000000 --- a/{{cookiecutter.project_slug}}/dev-requirements.txt +++ /dev/null @@ -1,342 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --extra=dev --output-file=dev-requirements.txt pyproject.toml -# -appnope==0.1.3 - # via ipython -asgiref==3.7.2 - # via django -astor==0.8.1 - # via flake8-simplify -astroid==2.15.8 - # via flake8-django -asttokens==2.4.1 - # via stack-data -attrs==23.1.0 - # via - # dotenv-linter - # flake8-bugbear - # flake8-eradicate - # jsonschema - # referencing -autoflake==1.7.0 - # via django (pyproject.toml) -bcrypt==4.0.1 - # via django (pyproject.toml) -black==23.11.0 - # via - # django (pyproject.toml) - # flake8-black -certifi==2023.11.17 - # via - # django-healthchecks - # requests - # sentry-sdk -cffi==1.16.0 - # via cryptography -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via - # black - # click-default-group - # dotenv-linter -click-default-group==1.2.4 - # via dotenv-linter -cognitive-complexity==1.3.0 - # via flake8-cognitive-complexity -cryptography==41.0.5 - # via pyjwt -decorator==5.1.1 - # via ipython -django==4.2.7 - # via - # django (pyproject.toml) - # django-axes - # django-filter - # django-healthchecks - # django-storages - # django-stubs - # django-stubs-ext - # djangorestframework - # drf-jwt - # drf-spectacular - # drf-spectacular-sidecar -django-axes==6.1.1 - # via django (pyproject.toml) -django-behaviors==0.5.1 - # via django (pyproject.toml) -django-environ==0.11.2 - # via django (pyproject.toml) -django-filter==23.4 - # via django (pyproject.toml) -django-healthchecks==1.5.0 - # via django (pyproject.toml) -django-ipware==5.0.2 - # via django (pyproject.toml) -django-split-settings==1.2.0 - # via django (pyproject.toml) -django-storages==1.14.2 - # via django (pyproject.toml) -django-stubs==4.2.6 - # via - # django (pyproject.toml) - # djangorestframework-stubs -django-stubs-ext==4.2.5 - # via django-stubs -djangorestframework==3.14.0 - # via - # django (pyproject.toml) - # drf-jwt - # drf-spectacular -djangorestframework-camel-case==1.4.2 - # via django (pyproject.toml) -djangorestframework-stubs==3.14.4 - # via django (pyproject.toml) -dotenv-linter==0.4.0 - # via django (pyproject.toml) -drf-jwt==1.19.2 - # via django (pyproject.toml) -drf-spectacular[sidecar]==0.26.5 - # via django (pyproject.toml) -drf-spectacular-sidecar==2023.10.1 - # via drf-spectacular -eradicate==2.3.0 - # via flake8-eradicate -executing==2.0.1 - # via stack-data -faker==12.0.1 - # via mixer -flake8==6.1.0 - # via - # flake8-absolute-import - # flake8-black - # flake8-bugbear - # flake8-django - # flake8-eradicate - # flake8-isort - # flake8-pep3101 - # flake8-print - # flake8-printf-formatting - # flake8-pyproject - # flake8-pytest - # flake8-simplify - # flake8-use-fstring - # flake8-walrus -flake8-absolute-import==1.0.0.2 - # via django (pyproject.toml) -flake8-black==0.3.6 - # via django (pyproject.toml) -flake8-bugbear==23.9.16 - # via django (pyproject.toml) -flake8-cognitive-complexity==0.1.0 - # via django (pyproject.toml) -flake8-django==1.4 - # via django (pyproject.toml) -flake8-eradicate==1.5.0 - # via django (pyproject.toml) -flake8-fixme==1.1.1 - # via django (pyproject.toml) -flake8-isort==6.1.1 - # via django (pyproject.toml) -flake8-pep3101==2.1.0 - # via django (pyproject.toml) -flake8-pie==0.16.0 - # via django (pyproject.toml) -flake8-plugin-utils==1.3.3 - # via flake8-pytest-style -flake8-print==5.0.0 - # via django (pyproject.toml) -flake8-printf-formatting==1.1.2 - # via django (pyproject.toml) -flake8-pyproject==1.2.3 - # via django (pyproject.toml) -flake8-pytest==1.4 - # via django (pyproject.toml) -flake8-pytest-style==1.7.2 - # via django (pyproject.toml) -flake8-simplify==0.21.0 - # via django (pyproject.toml) -flake8-todo==0.7 - # via django (pyproject.toml) -flake8-use-fstring==1.4 - # via django (pyproject.toml) -flake8-variables-names==0.0.6 - # via django (pyproject.toml) -flake8-walrus==1.2.0 - # via django (pyproject.toml) -freezegun==1.2.2 - # via - # django (pyproject.toml) - # pytest-freezegun -idna==3.4 - # via requests -inflection==0.5.1 - # via drf-spectacular -iniconfig==2.0.0 - # via pytest -ipython==8.17.2 - # via django (pyproject.toml) -isort==5.12.0 - # via flake8-isort -jedi==0.19.1 - # via - # django (pyproject.toml) - # ipython -jsonschema==4.20.0 - # via drf-spectacular -jsonschema-specifications==2023.11.1 - # via jsonschema -lazy-object-proxy==1.9.0 - # via astroid -matplotlib-inline==0.1.6 - # via ipython -mccabe==0.7.0 - # via flake8 -mixer==7.2.2 - # via django (pyproject.toml) -mypy==1.7.0 - # via - # django (pyproject.toml) - # djangorestframework-stubs -mypy-extensions==1.0.0 - # via - # black - # mypy -packaging==23.2 - # via - # black - # pytest -parso==0.8.3 - # via jedi -pathspec==0.11.2 - # via black -pexpect==4.8.0 - # via ipython -pillow==10.1.0 - # via django (pyproject.toml) -platformdirs==4.0.0 - # via black -pluggy==1.3.0 - # via pytest -ply==3.11 - # via dotenv-linter -prompt-toolkit==3.0.41 - # via ipython -psycopg2-binary==2.9.9 - # via django (pyproject.toml) -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.2 - # via stack-data -pycodestyle==2.11.1 - # via - # flake8 - # flake8-print - # flake8-todo -pycparser==2.21 - # via cffi -pyflakes==3.1.0 - # via - # autoflake - # flake8 -pygments==2.17.1 - # via ipython -pyjwt[crypto]==2.8.0 - # via - # drf-jwt - # pyjwt -pytest==7.4.3 - # via - # pytest-deadfixtures - # pytest-django - # pytest-env - # pytest-freezegun - # pytest-mock - # pytest-randomly -pytest-deadfixtures==2.2.1 - # via django (pyproject.toml) -pytest-django==4.7.0 - # via django (pyproject.toml) -pytest-env==1.1.1 - # via django (pyproject.toml) -pytest-freezegun==0.4.2 - # via django (pyproject.toml) -pytest-mock==3.12.0 - # via django (pyproject.toml) -pytest-randomly==3.15.0 - # via django (pyproject.toml) -python-dateutil==2.8.2 - # via - # faker - # freezegun -pytz==2023.3.post1 - # via djangorestframework -pyyaml==6.0.1 - # via drf-spectacular -redis==5.0.1 - # via django (pyproject.toml) -referencing==0.31.0 - # via - # jsonschema - # jsonschema-specifications -requests==2.31.0 - # via - # django-healthchecks - # djangorestframework-stubs -rpds-py==0.13.1 - # via - # jsonschema - # referencing -sentry-sdk==1.35.0 - # via django (pyproject.toml) -six==1.16.0 - # via - # asttokens - # python-dateutil -sqlparse==0.4.4 - # via django -stack-data==0.6.3 - # via ipython -traitlets==5.13.0 - # via - # ipython - # matplotlib-inline -types-freezegun==1.1.10 - # via django (pyproject.toml) -types-pillow==10.1.0.2 - # via django (pyproject.toml) -types-pytz==2023.3.1.1 - # via django-stubs -types-pyyaml==6.0.12.12 - # via - # django-stubs - # djangorestframework-stubs -types-requests==2.31.0.10 - # via djangorestframework-stubs -typing-extensions==4.8.0 - # via - # django-stubs - # django-stubs-ext - # djangorestframework-stubs - # dotenv-linter - # flake8-pie - # mypy -uritemplate==4.1.1 - # via drf-spectacular -urllib3==2.1.0 - # via - # requests - # sentry-sdk - # types-requests -wcwidth==0.2.11 - # via prompt-toolkit -whitenoise==6.6.0 - # via django (pyproject.toml) -wrapt==1.16.0 - # via astroid - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml deleted file mode 100644 index 8d7f7da3..00000000 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ /dev/null @@ -1,135 +0,0 @@ -[project] -name = "{{cookiecutter.project_slug}}" -version = "{{cookiecutter.project_version}}" -dependencies = [ - "Django<4.3", - "bcrypt", - "django-behaviors", - "django-environ", - "django-ipware", - "django-axes", - "whitenoise", - "django-storages", - "djangorestframework", - "djangorestframework-camel-case", - "drf-jwt", - "drf-spectacular[sidecar]", - "django-filter", - "django-split-settings", - "django-healthchecks", - "redis", - "sentry-sdk", - "Pillow", - "psycopg2-binary", -] - - -[project.optional-dependencies] -dev = [ - "ipython", - - "pytest-django>=3.9", - "pytest-deadfixtures", - "pytest-env", - "pytest-freezegun", - "pytest-mock", - "pytest-randomly", - - "black", - - "autoflake==1.7.0", - - "dotenv-linter", - - "freezegun", - "mixer", - - "jedi", - "flake8-absolute-import", - "flake8-black", - "flake8-bugbear", - "flake8-cognitive-complexity", - "flake8-django", - "flake8-eradicate", - "flake8-isort>=4.0.0", - "flake8-fixme", - "flake8-pep3101", - "flake8-pie", - "flake8-print", - "flake8-printf-formatting", - "flake8-pytest", - "flake8-pytest-style", - "flake8-simplify", - "flake8-todo", - "flake8-use-fstring", - "flake8-variables-names", - "flake8-walrus", - "flake8-pyproject", - - "mypy", - "django-stubs", - "djangorestframework-stubs", - "types-freezegun", - "types-Pillow", -] - - -[tool.flake8] -max-line-length = 160 -inline-quotes = "\"" -ignore = [ - "DJ05", # URLs include() should set a namespace - "E501", # Line too long - "E265", # Block comments should have one space before the pound sign (#) and the comment itself - "F811", # Redefinition of unused name from line n - "PT001", # Use @pytest.fixture() over @pytest.fixture - "SIM102", # Use a single if-statement instead of nested if-statements - "SIM113", # Use enumerate instead of manually incrementing a counter - "E203", # whitespace before ':', disabled for black purposes https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices -] -exclude = [ - "static", - "migrations", - "frontend", - ".git", - "__pycache__", -] - - -[tool.isort] -profile = "google" -line_length = 160 -extra_standard_library = ["pytest"] -known_django = ["django", "restframework"] -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "DJANGO", "FIRSTPARTY", "LOCALFOLDER"] -use_parentheses = true -include_trailing_comma = true -multi_line_output = 3 - - -[tool.black] -exclude = ''' -/( - | migrations -)/ -''' -line_length = 160 - - -[tool.pytest.ini_options] - DJANGO_SETTINGS_MODULE = "app.settings" - python_files = ["test*.py"] - addopts = ["--reuse-db"] - markers = [ - "freeze_time: freezing time marker (pytest-freezegun does not register it)", - ] - filterwarnings = [ # Pattern: `action:message:category:module:line` (https://docs.python.org/3/library/warnings.html#describing-warning-filters) - "ignore:.*'rest_framework_jwt.blacklist' defines default_app_config.*You can remove default_app_config.::django", - "ignore:distutils Version classes are deprecated. Use packaging.version instead.:DeprecationWarning:pytest_freezegun:17", - ] - env = [ - "CI=1", - "CELERY_ALWAYS_EAGER=True", - "DISABLE_THROTTLING=True", - "AXES_ENABLED=False", - ] diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt deleted file mode 100644 index 89a2fb0b..00000000 --- a/{{cookiecutter.project_slug}}/requirements.txt +++ /dev/null @@ -1,114 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --output-file=requirements.txt pyproject.toml -# -asgiref==3.7.2 - # via django -attrs==23.1.0 - # via - # jsonschema - # referencing -bcrypt==4.0.1 - # via django (pyproject.toml) -certifi==2023.11.17 - # via - # django-healthchecks - # requests - # sentry-sdk -cffi==1.16.0 - # via cryptography -charset-normalizer==3.3.2 - # via requests -cryptography==41.0.5 - # via pyjwt -django==4.2.7 - # via - # django (pyproject.toml) - # django-axes - # django-filter - # django-healthchecks - # django-storages - # djangorestframework - # drf-jwt - # drf-spectacular - # drf-spectacular-sidecar -django-axes==6.1.1 - # via django (pyproject.toml) -django-behaviors==0.5.1 - # via django (pyproject.toml) -django-environ==0.11.2 - # via django (pyproject.toml) -django-filter==23.4 - # via django (pyproject.toml) -django-healthchecks==1.5.0 - # via django (pyproject.toml) -django-ipware==5.0.2 - # via django (pyproject.toml) -django-split-settings==1.2.0 - # via django (pyproject.toml) -django-storages==1.14.2 - # via django (pyproject.toml) -djangorestframework==3.14.0 - # via - # django (pyproject.toml) - # drf-jwt - # drf-spectacular -djangorestframework-camel-case==1.4.2 - # via django (pyproject.toml) -drf-jwt==1.19.2 - # via django (pyproject.toml) -drf-spectacular[sidecar]==0.26.5 - # via django (pyproject.toml) -drf-spectacular-sidecar==2023.10.1 - # via drf-spectacular -idna==3.4 - # via requests -inflection==0.5.1 - # via drf-spectacular -jsonschema==4.20.0 - # via drf-spectacular -jsonschema-specifications==2023.11.1 - # via jsonschema -pillow==10.1.0 - # via django (pyproject.toml) -psycopg2-binary==2.9.9 - # via django (pyproject.toml) -pycparser==2.21 - # via cffi -pyjwt[crypto]==2.8.0 - # via - # drf-jwt - # pyjwt -pytz==2023.3.post1 - # via djangorestframework -pyyaml==6.0.1 - # via drf-spectacular -redis==5.0.1 - # via django (pyproject.toml) -referencing==0.31.0 - # via - # jsonschema - # jsonschema-specifications -requests==2.31.0 - # via django-healthchecks -rpds-py==0.13.1 - # via - # jsonschema - # referencing -sentry-sdk==1.35.0 - # via django (pyproject.toml) -sqlparse==0.4.4 - # via django -uritemplate==4.1.1 - # via drf-spectacular -urllib3==2.1.0 - # via - # requests - # sentry-sdk -whitenoise==6.6.0 - # via django (pyproject.toml) - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/admin.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/admin.py-tpl deleted file mode 100644 index 86c4c274..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/admin.py-tpl +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from app.admin import ModelAdmin - -# Register your models here. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/api/serializers/app_name.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/api/serializers/app_name.py-tpl deleted file mode 100644 index 3cfd0eef..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/api/serializers/app_name.py-tpl +++ /dev/null @@ -1,3 +0,0 @@ -from rest_framework import serializers - -# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/api/urls.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/api/urls.py-tpl deleted file mode 100644 index 9d561b38..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/api/urls.py-tpl +++ /dev/null @@ -1,12 +0,0 @@ -from rest_framework.routers import SimpleRouter - -from django.urls import include -from django.urls import path - -from {{ app_name }}.api import viewsets - -router = SimpleRouter() - -urlpatterns = [ - path('', include(router.urls)), -] diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/__init__.py b/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/__init__.py deleted file mode 100644 index 44f0b885..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = [ - "", -] diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/app_name.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/app_name.py-tpl deleted file mode 100644 index 999e02e2..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/api/views/app_name.py-tpl +++ /dev/null @@ -1,3 +0,0 @@ -from app.api.viewsets import DefaultModelViewSet - -# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/factory.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/factory.py-tpl deleted file mode 100644 index f729a781..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/factory.py-tpl +++ /dev/null @@ -1,4 +0,0 @@ -from app.testing import register - -# Register your factory methods here. -# Add this file to root conftest pytest_plugins. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/fixtures.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/fixtures.py-tpl deleted file mode 100644 index a2fe13af..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/fixtures.py-tpl +++ /dev/null @@ -1,4 +0,0 @@ -import pytest - -# Register your project-wide fixtures here. -# Add this file to root conftest pytest_plugins. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/models/__init__.py b/{{cookiecutter.project_slug}}/src/.django-app-template/models/__init__.py deleted file mode 100644 index 44f0b885..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = [ - "", -] diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/models/app_name.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/models/app_name.py-tpl deleted file mode 100644 index 0ca24cdc..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/models/app_name.py-tpl +++ /dev/null @@ -1,5 +0,0 @@ -from django.db import models - -from app.models import DefaultModel, TimestampedModel - -# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py. diff --git a/{{cookiecutter.project_slug}}/src/.django-app-template/tests/conftest.py-tpl b/{{cookiecutter.project_slug}}/src/.django-app-template/tests/conftest.py-tpl deleted file mode 100644 index 61258b76..00000000 --- a/{{cookiecutter.project_slug}}/src/.django-app-template/tests/conftest.py-tpl +++ /dev/null @@ -1,3 +0,0 @@ -import pytest - -# Register your app-wide fixtures here. diff --git a/{{cookiecutter.project_slug}}/src/a12n/api/serializers.py b/{{cookiecutter.project_slug}}/src/a12n/api/serializers.py deleted file mode 100644 index 5754b069..00000000 --- a/{{cookiecutter.project_slug}}/src/a12n/api/serializers.py +++ /dev/null @@ -1,2 +0,0 @@ -# Create your DRF serializers here -# https://www.django-rest-framework.org/tutorial/1-serialization/#creating-a-serializer-class diff --git a/{{cookiecutter.project_slug}}/src/app/.env b/{{cookiecutter.project_slug}}/src/app/.env deleted file mode 100644 index a8c5339c..00000000 --- a/{{cookiecutter.project_slug}}/src/app/.env +++ /dev/null @@ -1,3 +0,0 @@ -DEBUG=On -SECRET_KEY="{{ random_ascii_string(48, punctuation=False) }}" -DATABASE_URL=sqlite:///db.sqlite diff --git a/{{cookiecutter.project_slug}}/src/app/.env.ci b/{{cookiecutter.project_slug}}/src/app/.env.ci deleted file mode 100644 index 48e70354..00000000 --- a/{{cookiecutter.project_slug}}/src/app/.env.ci +++ /dev/null @@ -1,3 +0,0 @@ -DEBUG=Off -SECRET_KEY={{ random_ascii_string(48, punctuation=True) }} -DATABASE_URL=sqlite:///db.sqlite diff --git a/{{cookiecutter.project_slug}}/src/app/admin/README.md b/{{cookiecutter.project_slug}}/src/app/admin/README.md deleted file mode 100644 index ed2902db..00000000 --- a/{{cookiecutter.project_slug}}/src/app/admin/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## App-wide admin customizations - -This is a place for app-wide django-admin customizations. To make your admin interface customizable, scaffold your admin modules like this: - -```python -from app.admin import ModelAdmin, admin -from books.models import Book - - -@admin.register(Book) -class BookAdmin(ModelAdmin): - fields = [ - "name", - ] -``` \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/src/app/asgi.py b/{{cookiecutter.project_slug}}/src/app/asgi.py deleted file mode 100644 index 8b0b6000..00000000 --- a/{{cookiecutter.project_slug}}/src/app/asgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -ASGI config for app project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ -""" - -import os - -from django.core.asgi import get_asgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - -application = get_asgi_application() diff --git a/{{cookiecutter.project_slug}}/src/app/conf/environ.py b/{{cookiecutter.project_slug}}/src/app/conf/environ.py deleted file mode 100644 index d706412c..00000000 --- a/{{cookiecutter.project_slug}}/src/app/conf/environ.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Read .env file""" -import environ # type: ignore - -env = environ.Env( - DEBUG=(bool, False), - CI=(bool, False), -) - -environ.Env.read_env("app/.env") # reading .env file - -__all__ = [ - env, -] diff --git a/{{cookiecutter.project_slug}}/src/app/conf/i18n.py b/{{cookiecutter.project_slug}}/src/app/conf/i18n.py deleted file mode 100644 index 45a29d36..00000000 --- a/{{cookiecutter.project_slug}}/src/app/conf/i18n.py +++ /dev/null @@ -1,7 +0,0 @@ -# Internationalization -# https://docs.djangoproject.com/en/3.0/topics/i18n/ - -LANGUAGE_CODE = "en-us" -LOCALE_PATHS = ["locale"] -USE_L10N = True -USE_I18N = True diff --git a/{{cookiecutter.project_slug}}/src/app/wsgi.py b/{{cookiecutter.project_slug}}/src/app/wsgi.py deleted file mode 100644 index 1c508c03..00000000 --- a/{{cookiecutter.project_slug}}/src/app/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for app project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - -application = get_wsgi_application() diff --git a/{{cookiecutter.project_slug}}/src/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/src/users/migrations/0001_initial.py deleted file mode 100644 index 6d6f56db..00000000 --- a/{{cookiecutter.project_slug}}/src/users/migrations/0001_initial.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.2.6 on 2019-10-07 16:27 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations -from django.db import models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ]