From 0a15bb06c10fd34607c7d0b97899a8b3a9950d28 Mon Sep 17 00:00:00 2001 From: Alexandre Busquets Triola Date: Tue, 2 Jan 2024 09:05:23 +0100 Subject: [PATCH] feat: add minimal project --- .github/workflows/main.yml | 53 + .gitignore | 140 + .pre-commit-config.yaml | 57 + Dockerfile | 43 + ER.png | Bin 0 -> 280466 bytes README.md | 56 +- data/northwind_data.sql | 3537 +++++++++++++++++ data/northwind_start.sql | 3 + docker-compose.yml | 85 + docker/entrypoint.sh | 21 + env-api-dev-example | 4 + mypy.ini | 27 + poetry.lock | 2354 +++++++++++ poetry.toml | 5 + pyproject.toml | 168 + src/.coveragerc | 3 + src/alembic.ini | 105 + src/app/__init__.py | 0 src/app/app_container.py | 28 + src/app/asgi.py | 65 + src/app/exceptions.py | 7 + src/app/schemas.py | 20 + src/app/session_deps.py | 77 + src/app/setup_logging.py | 19 + src/auth/__init__.py | 0 src/auth/adapters/__init__.py | 0 src/auth/adapters/api/__init__.py | 0 src/auth/adapters/api/cli/__init__.py | 7 + src/auth/adapters/api/cli/secret.py | 7 + src/auth/adapters/api/cli/user.py | 48 + src/auth/adapters/api/cli/user_presenter.py | 23 + src/auth/adapters/api/http/__init__.py | 0 src/auth/adapters/api/http/router.py | 7 + src/auth/adapters/api/http/schemas.py | 22 + src/auth/adapters/api/http/token.py | 101 + src/auth/adapters/spi/__init__.py | 0 src/auth/adapters/spi/repositories/user.py | 32 + src/auth/alembic.ini | 107 + src/auth/di/__init__.py | 0 src/auth/di/mixins/__init__.py | 6 + src/auth/di/mixins/token.py | 8 + src/auth/di/mixins/user.py | 11 + src/auth/domain/__init__.py | 0 src/auth/domain/dtos/__init__.py | 0 src/auth/domain/dtos/country/__init__.py | 0 .../domain/dtos/country/create_country.py | 6 + .../domain/dtos/country/update_country.py | 7 + src/auth/domain/entities/__init__.py | 0 src/auth/domain/entities/user.py | 33 + src/auth/domain/entities/value_objects.py | 4 + src/auth/domain/ports/__init__.py | 0 .../domain/ports/repositories/__init__.py | 0 src/auth/domain/ports/repositories/user.py | 28 + src/auth/domain/services/__init__.py | 0 src/auth/domain/services/token.py | 58 + src/auth/domain/services/user.py | 28 + src/auth/domain/use_cases/__init__.py | 0 src/auth/domain/use_cases/user.py | 33 + src/auth/infra/__init__.py | 0 src/auth/infra/database/__init__.py | 0 .../infra/database/sqlalchemy/__init__.py | 0 .../database/sqlalchemy/models/__init__.py | 1 + .../infra/database/sqlalchemy/models/user.py | 21 + src/config/__init__.py | 26 + src/infra/__init__.py | 0 src/infra/cache/__init__.py | 0 src/infra/cache/memory_cache.py | 38 + src/infra/cache/ports.py | 27 + src/infra/cache/redis_cache.py | 50 + src/infra/database/__init__.py | 0 src/infra/database/alembic/env.py | 103 + src/infra/database/alembic/script.py.mako | 24 + .../82107b1b46b7_add_northwind_models.py | 207 + .../alembic/versions/ec48bb74a747_init.py | 41 + src/infra/database/sqlalchemy/__init__.py | 0 .../database/sqlalchemy/models/__init__.py | 0 src/infra/database/sqlalchemy/session.py | 79 + src/infra/database/sqlalchemy/sqlalchemy.py | 12 + src/logging.dev.yaml | 52 + src/manage.py | 31 + src/misc/__init__.py | 0 src/northwind/__init__.py | 0 src/northwind/adapters/__init__.py | 0 src/northwind/adapters/api/__init__.py | 0 src/northwind/adapters/api/http/__init__.py | 0 src/northwind/adapters/api/http/category.py | 141 + .../adapters/api/http/presenters/category.py | 29 + .../adapters/api/http/presenters/product.py | 29 + .../adapters/api/http/presenters/supplier.py | 29 + src/northwind/adapters/api/http/product.py | 142 + src/northwind/adapters/api/http/router.py | 11 + .../adapters/api/http/schemas/__init__.py | 0 .../adapters/api/http/schemas/category.py | 32 + .../adapters/api/http/schemas/product.py | 49 + .../adapters/api/http/schemas/supplier.py | 41 + src/northwind/adapters/api/http/supplier.py | 141 + src/northwind/adapters/spi/__init__.py | 0 .../adapters/spi/repositories/__init__.py | 0 .../adapters/spi/repositories/category.py | 26 + .../adapters/spi/repositories/product.py | 78 + .../adapters/spi/repositories/supplier.py | 22 + src/northwind/di/__init__.py | 0 src/northwind/di/mixins/__init__.py | 7 + src/northwind/di/mixins/category.py | 11 + src/northwind/di/mixins/product.py | 15 + src/northwind/di/mixins/supplier.py | 11 + src/northwind/domain/__init__.py | 0 src/northwind/domain/entities/__init__.py | 0 src/northwind/domain/entities/category.py | 11 + src/northwind/domain/entities/product.py | 20 + src/northwind/domain/entities/supplier.py | 20 + .../domain/entities/value_objects.py | 3 + src/northwind/domain/ports/__init__.py | 0 .../domain/ports/repositories/__init__.py | 0 .../domain/ports/repositories/category.py | 7 + .../domain/ports/repositories/product.py | 7 + .../domain/ports/repositories/supplier.py | 7 + src/northwind/domain/services/__init__.py | 0 src/northwind/domain/services/category.py | 27 + src/northwind/domain/services/product.py | 27 + src/northwind/domain/services/supplier.py | 27 + src/northwind/domain/use_cases/__init__.py | 0 src/northwind/domain/use_cases/category.py | 40 + src/northwind/domain/use_cases/product.py | 40 + src/northwind/domain/use_cases/supplier.py | 40 + src/northwind/infra/__init__.py | 0 src/northwind/infra/database/__init__.py | 0 .../infra/database/sqlalchemy/__init__.py | 0 .../infra/database/sqlalchemy/models.py | 209 + src/northwind/schemas/category.py | 17 + src/northwind/schemas/product.py | 31 + src/northwind/schemas/supplier.py | 37 + src/pytest.github.ini | 25 + src/pytest.ini | 26 + src/shared/api/__init__.py | 0 src/shared/api/schemas/__init__.py | 0 src/shared/api/schemas/page.py | 28 + src/shared/exceptions.py | 29 + src/shared/presenter.py | 14 + src/shared/repository/__init__.py | 0 src/shared/repository/ports/__init__.py | 0 src/shared/repository/ports/generic.py | 43 + src/shared/repository/sqlalchemy.py | 93 + src/tests/__init__.py | 0 src/tests/app/__init__.py | 0 src/tests/app/test_cli.py | 9 + src/tests/app/test_hello_world.py | 11 + src/tests/app/test_settings.py | 5 + src/tests/auth/__init__.py | 0 src/tests/auth/adapters/__init__.py | 0 src/tests/auth/adapters/api/__init__.py | 0 src/tests/auth/adapters/api/cli/__init_.py | 0 src/tests/auth/adapters/api/cli/test_user.py | 13 + src/tests/auth/adapters/api/http/__init__.py | 0 .../auth/adapters/api/http/_test_login.py | 31 + .../auth/adapters/api/http/_test_protected.py | 18 + .../auth/adapters/api/http/_test_token.py | 54 + src/tests/auth/conftest.py | 0 src/tests/conftest.py | 167 + src/tests/infra/test_auth_user.py | 9 + src/tests/northwind/__init__.py | 0 src/tests/northwind/adapters/__init__.py | 0 src/tests/northwind/adapters/api/__init__.py | 0 .../northwind/adapters/api/http/__init__.py | 0 .../adapters/api/http/test_category.py | 67 + src/tests/northwind/conftest.py | 34 + src/tests/utils/__init__.py | 0 src/tests/utils/test_di.py | 45 + src/utils/__init__.py | 0 src/utils/async_utils.py | 16 + src/utils/di.py | 37 + src/utils/logger/__init__.py | 0 src/utils/logger/formatter/__init__.py | 0 src/utils/logger/formatter/color_extra.py | 7 + src/utils/logger/formatter/standard_extra.py | 40 + src/utils/singleton.py | 40 + 176 files changed, 10409 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 Dockerfile create mode 100644 ER.png create mode 100644 data/northwind_data.sql create mode 100644 data/northwind_start.sql create mode 100644 docker-compose.yml create mode 100755 docker/entrypoint.sh create mode 100644 env-api-dev-example create mode 100644 mypy.ini create mode 100644 poetry.lock create mode 100644 poetry.toml create mode 100644 pyproject.toml create mode 100644 src/.coveragerc create mode 100644 src/alembic.ini create mode 100644 src/app/__init__.py create mode 100644 src/app/app_container.py create mode 100644 src/app/asgi.py create mode 100644 src/app/exceptions.py create mode 100644 src/app/schemas.py create mode 100644 src/app/session_deps.py create mode 100644 src/app/setup_logging.py create mode 100644 src/auth/__init__.py create mode 100644 src/auth/adapters/__init__.py create mode 100644 src/auth/adapters/api/__init__.py create mode 100644 src/auth/adapters/api/cli/__init__.py create mode 100644 src/auth/adapters/api/cli/secret.py create mode 100644 src/auth/adapters/api/cli/user.py create mode 100644 src/auth/adapters/api/cli/user_presenter.py create mode 100644 src/auth/adapters/api/http/__init__.py create mode 100644 src/auth/adapters/api/http/router.py create mode 100644 src/auth/adapters/api/http/schemas.py create mode 100644 src/auth/adapters/api/http/token.py create mode 100644 src/auth/adapters/spi/__init__.py create mode 100644 src/auth/adapters/spi/repositories/user.py create mode 100644 src/auth/alembic.ini create mode 100644 src/auth/di/__init__.py create mode 100644 src/auth/di/mixins/__init__.py create mode 100644 src/auth/di/mixins/token.py create mode 100644 src/auth/di/mixins/user.py create mode 100644 src/auth/domain/__init__.py create mode 100644 src/auth/domain/dtos/__init__.py create mode 100644 src/auth/domain/dtos/country/__init__.py create mode 100644 src/auth/domain/dtos/country/create_country.py create mode 100644 src/auth/domain/dtos/country/update_country.py create mode 100644 src/auth/domain/entities/__init__.py create mode 100644 src/auth/domain/entities/user.py create mode 100644 src/auth/domain/entities/value_objects.py create mode 100644 src/auth/domain/ports/__init__.py create mode 100644 src/auth/domain/ports/repositories/__init__.py create mode 100644 src/auth/domain/ports/repositories/user.py create mode 100644 src/auth/domain/services/__init__.py create mode 100644 src/auth/domain/services/token.py create mode 100644 src/auth/domain/services/user.py create mode 100644 src/auth/domain/use_cases/__init__.py create mode 100644 src/auth/domain/use_cases/user.py create mode 100644 src/auth/infra/__init__.py create mode 100644 src/auth/infra/database/__init__.py create mode 100644 src/auth/infra/database/sqlalchemy/__init__.py create mode 100644 src/auth/infra/database/sqlalchemy/models/__init__.py create mode 100644 src/auth/infra/database/sqlalchemy/models/user.py create mode 100644 src/config/__init__.py create mode 100644 src/infra/__init__.py create mode 100644 src/infra/cache/__init__.py create mode 100644 src/infra/cache/memory_cache.py create mode 100644 src/infra/cache/ports.py create mode 100644 src/infra/cache/redis_cache.py create mode 100644 src/infra/database/__init__.py create mode 100644 src/infra/database/alembic/env.py create mode 100644 src/infra/database/alembic/script.py.mako create mode 100644 src/infra/database/alembic/versions/82107b1b46b7_add_northwind_models.py create mode 100644 src/infra/database/alembic/versions/ec48bb74a747_init.py create mode 100644 src/infra/database/sqlalchemy/__init__.py create mode 100644 src/infra/database/sqlalchemy/models/__init__.py create mode 100644 src/infra/database/sqlalchemy/session.py create mode 100644 src/infra/database/sqlalchemy/sqlalchemy.py create mode 100644 src/logging.dev.yaml create mode 100644 src/manage.py create mode 100644 src/misc/__init__.py create mode 100644 src/northwind/__init__.py create mode 100644 src/northwind/adapters/__init__.py create mode 100644 src/northwind/adapters/api/__init__.py create mode 100644 src/northwind/adapters/api/http/__init__.py create mode 100644 src/northwind/adapters/api/http/category.py create mode 100644 src/northwind/adapters/api/http/presenters/category.py create mode 100644 src/northwind/adapters/api/http/presenters/product.py create mode 100644 src/northwind/adapters/api/http/presenters/supplier.py create mode 100644 src/northwind/adapters/api/http/product.py create mode 100644 src/northwind/adapters/api/http/router.py create mode 100644 src/northwind/adapters/api/http/schemas/__init__.py create mode 100644 src/northwind/adapters/api/http/schemas/category.py create mode 100644 src/northwind/adapters/api/http/schemas/product.py create mode 100644 src/northwind/adapters/api/http/schemas/supplier.py create mode 100644 src/northwind/adapters/api/http/supplier.py create mode 100644 src/northwind/adapters/spi/__init__.py create mode 100644 src/northwind/adapters/spi/repositories/__init__.py create mode 100644 src/northwind/adapters/spi/repositories/category.py create mode 100644 src/northwind/adapters/spi/repositories/product.py create mode 100644 src/northwind/adapters/spi/repositories/supplier.py create mode 100644 src/northwind/di/__init__.py create mode 100644 src/northwind/di/mixins/__init__.py create mode 100644 src/northwind/di/mixins/category.py create mode 100644 src/northwind/di/mixins/product.py create mode 100644 src/northwind/di/mixins/supplier.py create mode 100644 src/northwind/domain/__init__.py create mode 100644 src/northwind/domain/entities/__init__.py create mode 100644 src/northwind/domain/entities/category.py create mode 100644 src/northwind/domain/entities/product.py create mode 100644 src/northwind/domain/entities/supplier.py create mode 100644 src/northwind/domain/entities/value_objects.py create mode 100644 src/northwind/domain/ports/__init__.py create mode 100644 src/northwind/domain/ports/repositories/__init__.py create mode 100644 src/northwind/domain/ports/repositories/category.py create mode 100644 src/northwind/domain/ports/repositories/product.py create mode 100644 src/northwind/domain/ports/repositories/supplier.py create mode 100644 src/northwind/domain/services/__init__.py create mode 100644 src/northwind/domain/services/category.py create mode 100644 src/northwind/domain/services/product.py create mode 100644 src/northwind/domain/services/supplier.py create mode 100644 src/northwind/domain/use_cases/__init__.py create mode 100644 src/northwind/domain/use_cases/category.py create mode 100644 src/northwind/domain/use_cases/product.py create mode 100644 src/northwind/domain/use_cases/supplier.py create mode 100644 src/northwind/infra/__init__.py create mode 100644 src/northwind/infra/database/__init__.py create mode 100644 src/northwind/infra/database/sqlalchemy/__init__.py create mode 100644 src/northwind/infra/database/sqlalchemy/models.py create mode 100644 src/northwind/schemas/category.py create mode 100644 src/northwind/schemas/product.py create mode 100644 src/northwind/schemas/supplier.py create mode 100644 src/pytest.github.ini create mode 100644 src/pytest.ini create mode 100644 src/shared/api/__init__.py create mode 100644 src/shared/api/schemas/__init__.py create mode 100644 src/shared/api/schemas/page.py create mode 100644 src/shared/exceptions.py create mode 100644 src/shared/presenter.py create mode 100644 src/shared/repository/__init__.py create mode 100644 src/shared/repository/ports/__init__.py create mode 100644 src/shared/repository/ports/generic.py create mode 100644 src/shared/repository/sqlalchemy.py create mode 100644 src/tests/__init__.py create mode 100644 src/tests/app/__init__.py create mode 100644 src/tests/app/test_cli.py create mode 100644 src/tests/app/test_hello_world.py create mode 100644 src/tests/app/test_settings.py create mode 100644 src/tests/auth/__init__.py create mode 100644 src/tests/auth/adapters/__init__.py create mode 100644 src/tests/auth/adapters/api/__init__.py create mode 100644 src/tests/auth/adapters/api/cli/__init_.py create mode 100644 src/tests/auth/adapters/api/cli/test_user.py create mode 100644 src/tests/auth/adapters/api/http/__init__.py create mode 100644 src/tests/auth/adapters/api/http/_test_login.py create mode 100644 src/tests/auth/adapters/api/http/_test_protected.py create mode 100644 src/tests/auth/adapters/api/http/_test_token.py create mode 100644 src/tests/auth/conftest.py create mode 100644 src/tests/conftest.py create mode 100644 src/tests/infra/test_auth_user.py create mode 100644 src/tests/northwind/__init__.py create mode 100644 src/tests/northwind/adapters/__init__.py create mode 100644 src/tests/northwind/adapters/api/__init__.py create mode 100644 src/tests/northwind/adapters/api/http/__init__.py create mode 100644 src/tests/northwind/adapters/api/http/test_category.py create mode 100644 src/tests/northwind/conftest.py create mode 100644 src/tests/utils/__init__.py create mode 100644 src/tests/utils/test_di.py create mode 100644 src/utils/__init__.py create mode 100644 src/utils/async_utils.py create mode 100644 src/utils/di.py create mode 100644 src/utils/logger/__init__.py create mode 100644 src/utils/logger/formatter/__init__.py create mode 100644 src/utils/logger/formatter/color_extra.py create mode 100644 src/utils/logger/formatter/standard_extra.py create mode 100644 src/utils/singleton.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..2c9f709 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,53 @@ +name: Check code and Pytest + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16.1 + env: + POSTGRES_USER: northwind + POSTGRES_PASSWORD: northwind + POSTGRES_DB: northwind + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + + steps: + - uses: actions/checkout@v3 + + - name: psycopg prerequisites + run: sudo apt-get install libpq-dev + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install poetry ruff + poetry install + + - name: Lint with ruff + run: | + ruff check ./src + + - name: Test with pytest + working-directory: ./src + run: | + poetry run pytest -c pytest.github.ini + + # - name: SonarCloud Scan + # uses: SonarSource/sonarcloud-github-action@master + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee72a1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,140 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +env-local +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# History extension +.history + +# Custom +\!* +.DS_Store +src/debug_scripts/ +env-api-dev +.vscode \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..12aba42 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +exclude: "docs|node_modules|migrations|shared|.git|.tox|.hbs" +default_stages: [commit] +fail_fast: true + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks # Refer to this repository for futher documentation about official pre-commit hooks + rev: v4.5.0 + hooks: + - id: check-added-large-files + - id: trailing-whitespace + - id: end-of-file-fixer + - id: double-quote-string-fixer + - id: mixed-line-ending + - id: check-toml + - id: check-yaml + args: + - --unsafe # Instead of loading the files, simply parse them for syntax. + - id: detect-private-key + + + - repo: https://github.com/myint/autoflake + rev: v2.2.1 + hooks: + - id: autoflake + args: + [ + "--in-place", + "--remove-all-unused-imports", + "--remove-unused-variable", + ] + + # - repo: https://github.com/psf/black # Refer to this repository for futher documentation about black hook + # rev: 23.12.1 + # hooks: + # - id: black + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.11 + hooks: + # Run the linter. + - id: ruff + args: [--fix] # Enables autofix + # Run the formatter. + - id: ruff-format + + - repo: https://github.com/pycqa/bandit + rev: 1.7.6 + hooks: + - id: bandit + args: [ "-iii", "-ll" ] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + args: [--config-file=mypy.ini, --ignore-missing-imports] + additional_dependencies: [types-redis, types-pyyaml, types-sqlalchemy, types-click, types-python-jose] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf561eb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +ARG PYTHON_VERSION=3.12.1 + +FROM python:${PYTHON_VERSION}-bookworm AS build-image + +# Update and install dependencies +RUN apt-get -qq update && apt-get -qq install lsb-release && apt-get -qq install git + +RUN mkdir -m 700 /root/.ssh; \ + touch -m 600 /root/.ssh/known_hosts; \ + ssh-keyscan github.com > /root/.ssh/known_hosts + +# poetry +WORKDIR /srv +RUN pip install pip --upgrade && pip install poetry==1.4.2 +COPY poetry.lock pyproject.toml /srv/ +ARG POETRY_DEV=false +RUN --mount=type=ssh,id=default --mount=type=cache,mode=0777,target=/root/.cache/pip \ + poetry export -f requirements.txt -o requirements.txt --without-hashes $(test "$POETRY_DEV" = "true" && echo "--with dev,test") \ + && python -m venv venv && . venv/bin/activate && pip install -r requirements.txt +# end poetry + +# Optimized build +FROM python:${PYTHON_VERSION}-slim-bookworm +ARG WAIT_BIN=wait + +# Add wait script +ADD "https://github.com/ufoscout/docker-compose-wait/releases/download/2.12.0/${WAIT_BIN}" /wait +RUN chmod +x /wait +# wait script + +COPY --from=build-image /srv/venv/ /srv/venv/ + +ENV PATH="/srv/venv/bin:$PATH" + +# Set working directory to function root directory +WORKDIR /app + +# Copy the rest of the working directory contents into the container at /app +COPY src/ . +COPY docker/entrypoint.sh /entrypoint.sh + +ENTRYPOINT [ "/entrypoint.sh" ] +CMD ["run-asgi"] diff --git a/ER.png b/ER.png new file mode 100644 index 0000000000000000000000000000000000000000..6fe4269b97fa647d822856da647af63eca0e83d1 GIT binary patch literal 280466 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

GYD5ZR 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

y1>kC^1&pAC=gHl-Z_h+OIbpfYr`Kt;H-` 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 a@=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" +category = "main" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, +] + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + +[[package]] +name = "bcrypt" +version = "4.1.2" +description = "Modern password hashing for your software and your servers" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, + {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, + {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, + {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, + {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, + {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, + {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "build" +version = "1.0.3" +description = "A simple, correct Python build frontend" +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"}, + {file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +packaging = ">=19.0" +pyproject_hooks = "*" + +[package.extras] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "cachecontrol" +version = "0.13.1" +description = "httplib2 caching for requests" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachecontrol-0.13.1-py3-none-any.whl", hash = "sha256:95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4"}, + {file = "cachecontrol-0.13.1.tar.gz", hash = "sha256:f012366b79d2243a6118309ce73151bf52a38d4a5dac8ea57f09bd29087e506b"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = ">=2.16.0" + +[package.extras] +dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "mypy", "pytest", "pytest-cov", "sphinx", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +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 = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +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 = "cleo" +version = "2.1.0" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=3.0.0,<4.0.0" + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +category = "main" +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." +category = "main" +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 = "colorlog" +version = "6.8.0" +description = "Add colours to the output of Python's logging module." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.8.0-py3-none-any.whl", hash = "sha256:4ed23b05a1154294ac99f511fabe8c1d6d4364ec1f7fc989c7fb515ccc29d375"}, + {file = "colorlog-6.8.0.tar.gz", hash = "sha256:fbb6fdf9d5685f2517f388fb29bb27d54e8654dd31f58bc2a3b217e967a95ca6"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "coverage" +version = "7.3.4" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"}, + {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"}, + {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"}, + {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"}, + {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"}, + {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"}, + {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"}, + {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"}, + {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"}, + {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"}, + {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"}, + {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"}, + {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"}, + {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + +[[package]] +name = "cryptography" +version = "41.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "debugpy" +version = "1.8.0" +description = "An implementation of the Debug Adapter Protocol for Python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb"}, + {file = "debugpy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada"}, + {file = "debugpy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f"}, + {file = "debugpy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637"}, + {file = "debugpy-1.8.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e"}, + {file = "debugpy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6"}, + {file = "debugpy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b"}, + {file = "debugpy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153"}, + {file = "debugpy-1.8.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd"}, + {file = "debugpy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f"}, + {file = "debugpy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa"}, + {file = "debugpy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595"}, + {file = "debugpy-1.8.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8"}, + {file = "debugpy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332"}, + {file = "debugpy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6"}, + {file = "debugpy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926"}, + {file = "debugpy-1.8.0-py2.py3-none-any.whl", hash = "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4"}, + {file = "debugpy-1.8.0.zip", hash = "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "dulwich" +version = "0.21.7" +description = "Python Git Library" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, + {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, + {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, + {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, + {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, + {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, + {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, + {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, + {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, + {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, + {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, + {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, + {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, + {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, + {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "faker" +version = "22.0.0" +description = "Faker is a Python package that generates fake data for you." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-22.0.0-py3-none-any.whl", hash = "sha256:9c22c0a734ca01c6e4f2259eab5dab9081905a9d67b27272aea5c9feeb5a3789"}, + {file = "Faker-22.0.0.tar.gz", hash = "sha256:1d5dc0a75da7bc40741ee4c84d99dc087b97bd086d4222ad06ac4dd2219bcf3f"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" + +[[package]] +name = "fastapi" +version = "0.108.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.108.0-py3-none-any.whl", hash = "sha256:8c7bc6d315da963ee4cdb605557827071a9a7f95aeb8fcdd3bde48cdc8764dd7"}, + {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.29.0,<0.33.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastjsonschema" +version = "2.19.0" +description = "Fastest Python implementation of JSON schema" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.19.0-py3-none-any.whl", hash = "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e"}, + {file = "fastjsonschema-2.19.0.tar.gz", hash = "sha256:e25df6647e1bc4a26070b700897b07b542ec898dd4f1f6ea013e7f6a88417225"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.2" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] +trio = ["trio (>=0.22.0,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = ">=1.0.0,<2.0.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +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 = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + +[[package]] +name = "jaraco-classes" +version = "3.3.0" +description = "Utility functions for Python class constructs" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, + {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "keyring" +version = "24.3.0" +description = "Store and access your passwords safely." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, + {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, +] + +[package.dependencies] +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "mako" +version = "1.3.0" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.0-py3-none-any.whl", hash = "sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9"}, + {file = "Mako-1.3.0.tar.gz", hash = "sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +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 = "more-itertools" +version = "10.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, + {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, +] + +[[package]] +name = "msgpack" +version = "1.0.7" +description = "MessagePack serializer" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, + {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, + {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, + {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, + {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, + {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, + {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, + {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, + {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, + {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, + {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, + {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, +] + +[[package]] +name = "orjson" +version = "3.9.10" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.9.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c18a4da2f50050a03d1da5317388ef84a16013302a5281d6f64e4a3f406aabc4"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5148bab4d71f58948c7c39d12b14a9005b6ab35a0bdf317a8ade9a9e4d9d0bd5"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cf7837c3b11a2dfb589f8530b3cff2bd0307ace4c301e8997e95c7468c1378e"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c62b6fa2961a1dcc51ebe88771be5319a93fd89bd247c9ddf732bc250507bc2b"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb3922a7a804755bbe6b5be9b312e746137a03600f488290318936c1a2d4dc"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1234dc92d011d3554d929b6cf058ac4a24d188d97be5e04355f1b9223e98bbe9"}, + {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:06ad5543217e0e46fd7ab7ea45d506c76f878b87b1b4e369006bdb01acc05a83"}, + {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fd72fab7bddce46c6826994ce1e7de145ae1e9e106ebb8eb9ce1393ca01444d"}, + {file = "orjson-3.9.10-cp310-none-win32.whl", hash = "sha256:b5b7d4a44cc0e6ff98da5d56cde794385bdd212a86563ac321ca64d7f80c80d1"}, + {file = "orjson-3.9.10-cp310-none-win_amd64.whl", hash = "sha256:61804231099214e2f84998316f3238c4c2c4aaec302df12b21a64d72e2a135c7"}, + {file = "orjson-3.9.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cff7570d492bcf4b64cc862a6e2fb77edd5e5748ad715f487628f102815165e9"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8bc367f725dfc5cabeed1ae079d00369900231fbb5a5280cf0736c30e2adf7"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c812312847867b6335cfb264772f2a7e85b3b502d3a6b0586aa35e1858528ab1"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edd2856611e5050004f4722922b7b1cd6268da34102667bd49d2a2b18bafb81"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:674eb520f02422546c40401f4efaf8207b5e29e420c17051cddf6c02783ff5ca"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0dc4310da8b5f6415949bd5ef937e60aeb0eb6b16f95041b5e43e6200821fb"}, + {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99c625b8c95d7741fe057585176b1b8783d46ed4b8932cf98ee145c4facf499"}, + {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec6f18f96b47299c11203edfbdc34e1b69085070d9a3d1f302810cc23ad36bf3"}, + {file = "orjson-3.9.10-cp311-none-win32.whl", hash = "sha256:ce0a29c28dfb8eccd0f16219360530bc3cfdf6bf70ca384dacd36e6c650ef8e8"}, + {file = "orjson-3.9.10-cp311-none-win_amd64.whl", hash = "sha256:cf80b550092cc480a0cbd0750e8189247ff45457e5a023305f7ef1bcec811616"}, + {file = "orjson-3.9.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:602a8001bdf60e1a7d544be29c82560a7b49319a0b31d62586548835bbe2c862"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f295efcd47b6124b01255d1491f9e46f17ef40d3d7eabf7364099e463fb45f0f"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5a02360e73e7208a872bf65a7554c9f15df5fe063dc047f79738998b0506a14"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858379cbb08d84fe7583231077d9a36a1a20eb72f8c9076a45df8b083724ad1d"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666c6fdcaac1f13eb982b649e1c311c08d7097cbda24f32612dae43648d8db8d"}, + {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3fb205ab52a2e30354640780ce4587157a9563a68c9beaf52153e1cea9aa0921"}, + {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7ec960b1b942ee3c69323b8721df2a3ce28ff40e7ca47873ae35bfafeb4555ca"}, + {file = "orjson-3.9.10-cp312-none-win_amd64.whl", hash = "sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d"}, + {file = "orjson-3.9.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8b9ba0ccd5a7f4219e67fbbe25e6b4a46ceef783c42af7dbc1da548eb28b6531"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e2ecd1d349e62e3960695214f40939bbfdcaeaaa62ccc638f8e651cf0970e5f"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f433be3b3f4c66016d5a20e5b4444ef833a1f802ced13a2d852c637f69729c1"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4689270c35d4bb3102e103ac43c3f0b76b169760aff8bcf2d401a3e0e58cdb7f"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd176f528a8151a6efc5359b853ba3cc0e82d4cd1fab9c1300c5d957dc8f48c"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a2ce5ea4f71681623f04e2b7dadede3c7435dfb5e5e2d1d0ec25b35530e277b"}, + {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:49f8ad582da6e8d2cf663c4ba5bf9f83cc052570a3a767487fec6af839b0e777"}, + {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a11b4b1a8415f105d989876a19b173f6cdc89ca13855ccc67c18efbd7cbd1f8"}, + {file = "orjson-3.9.10-cp38-none-win32.whl", hash = "sha256:a353bf1f565ed27ba71a419b2cd3db9d6151da426b61b289b6ba1422a702e643"}, + {file = "orjson-3.9.10-cp38-none-win_amd64.whl", hash = "sha256:e28a50b5be854e18d54f75ef1bb13e1abf4bc650ab9d635e4258c58e71eb6ad5"}, + {file = "orjson-3.9.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ee5926746232f627a3be1cc175b2cfad24d0170d520361f4ce3fa2fd83f09e1d"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a73160e823151f33cdc05fe2cea557c5ef12fdf276ce29bb4f1c571c8368a60"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c338ed69ad0b8f8f8920c13f529889fe0771abbb46550013e3c3d01e5174deef"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5869e8e130e99687d9e4be835116c4ebd83ca92e52e55810962446d841aba8de"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2c1e559d96a7f94a4f581e2a32d6d610df5840881a8cba8f25e446f4d792df3"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a3a3a72c9811b56adf8bcc829b010163bb2fc308877e50e9910c9357e78521"}, + {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f8fb7f5ecf4f6355683ac6881fd64b5bb2b8a60e3ccde6ff799e48791d8f864"}, + {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c943b35ecdf7123b2d81d225397efddf0bce2e81db2f3ae633ead38e85cd5ade"}, + {file = "orjson-3.9.10-cp39-none-win32.whl", hash = "sha256:fb0b361d73f6b8eeceba47cd37070b5e6c9de5beaeaa63a1cb35c7e1a73ef088"}, + {file = "orjson-3.9.10-cp39-none-win_amd64.whl", hash = "sha256:b90f340cb6397ec7a854157fac03f0c82b744abdd1c0941a024c3c29d1340aff"}, + {file = "orjson-3.9.10.tar.gz", hash = "sha256:9ebbdbd6a046c304b1845e96fbcc5559cd296b4dfd3ad2509e33c4d9ce07d6a1"}, +] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry" +version = "1.7.1" +description = "Python dependency management and packaging made easy." +category = "dev" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry-1.7.1-py3-none-any.whl", hash = "sha256:03d3807a0fb3bc1028cc3707dfd646aae629d58e476f7e7f062437680741c561"}, + {file = "poetry-1.7.1.tar.gz", hash = "sha256:b348a70e7d67ad9c0bd3d0ea255bc6df84c24cf4b16f8d104adb30b425d6ff32"}, +] + +[package.dependencies] +build = ">=1.0.3,<2.0.0" +cachecontrol = {version = ">=0.13.0,<0.14.0", extras = ["filecache"]} +cleo = ">=2.1.0,<3.0.0" +crashtest = ">=0.4.1,<0.5.0" +dulwich = ">=0.21.2,<0.22.0" +fastjsonschema = ">=2.18.0,<3.0.0" +installer = ">=0.7.0,<0.8.0" +keyring = ">=24.0.0,<25.0.0" +packaging = ">=20.5" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.9.4,<2.0.0" +platformdirs = ">=3.0.0,<4.0.0" +poetry-core = "1.8.1" +poetry-plugin-export = ">=1.6.0,<2.0.0" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.26,<3.0" +requests-toolbelt = ">=0.9.1,<2" +shellingham = ">=1.5,<2.0" +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +virtualenv = ">=20.23.0,<21.0.0" +xattr = {version = ">=0.10.0,<0.11.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.8.1" +description = "Poetry PEP 517 Build Backend" +category = "dev" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry_core-1.8.1-py3-none-any.whl", hash = "sha256:194832b24f3283e01c5402eae71a6aae850ecdfe53f50a979c76bf7aa5010ffa"}, + {file = "poetry_core-1.8.1.tar.gz", hash = "sha256:67a76c671da2a70e55047cddda83566035b701f7e463b32a2abfeac6e2a16376"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.6.0" +description = "Poetry plugin to export the dependencies to various formats" +category = "dev" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry_plugin_export-1.6.0-py3-none-any.whl", hash = "sha256:2dce6204c9318f1f6509a11a03921fb3f461b201840b59f1c237b6ab454dabcf"}, + {file = "poetry_plugin_export-1.6.0.tar.gz", hash = "sha256:091939434984267a91abf2f916a26b00cff4eee8da63ec2a24ba4b17cf969a59"}, +] + +[package.dependencies] +poetry = ">=1.6.0,<2.0.0" +poetry-core = ">=1.7.0,<2.0.0" + +[[package]] +name = "poetry-types" +version = "0.5.0" +description = "A poetry plugin that adds/removes type stubs as dependencies like the mypy --install-types command." +category = "dev" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry_types-0.5.0-py3-none-any.whl", hash = "sha256:349cceabae252e17ad9845def4422263c6aa0474545f537e772ea04b38d7b4fb"}, + {file = "poetry_types-0.5.0.tar.gz", hash = "sha256:ac9ecd49eb4dc889f883106038e42749f8c509cd66e33f393226f1cfdf604c23"}, +] + +[package.dependencies] +packaging = ">=21.3,<24.0" +poetry = ">=1.6,<2.0" +tomlkit = ">=0.11.4,<0.12.0" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pyasn1" +version = "0.5.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, +] + +[[package]] +name = "pycountry" +version = "22.3.5" +description = "ISO country, subdivision, language, currency and script definitions and their translations" +category = "main" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "2.5.3" +description = "Data validation using Python type hints" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, + {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.14.6" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.14.6" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, + {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, + {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, + {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, + {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, + {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, + {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, + {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, + {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, + {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, + {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, + {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, + {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, + {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, + {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, + {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.1.0" +description = "Settings management using Pydantic" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.1.0-py3-none-any.whl", hash = "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"}, + {file = "pydantic_settings-2.1.0.tar.gz", hash = "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c"}, +] + +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.2" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.2.tar.gz", hash = "sha256:c16052382554c7b22d48782ab3438d5b10f8cf7a4bdcae7f0f67f097d95beecc"}, + {file = "pytest_asyncio-0.23.2-py3-none-any.whl", hash = "sha256:ea9021364e32d58f0be43b91c6233fb8d2224ccef2398d6837559e587682808f"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-env" +version = "1.1.3" +description = "pytest plugin that allows you to add environment variables." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_env-1.1.3-py3-none-any.whl", hash = "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc"}, + {file = "pytest_env-1.1.3.tar.gz", hash = "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b"}, +] + +[package.dependencies] +pytest = ">=7.4.3" + +[package.extras] +test = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "pytest-mock (>=3.12)"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +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-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-jose" +version = "3.3.0" +description = "JOSE implementation in Python" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, + {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryptography\""} +ecdsa = "!=0.15" +pyasn1 = "*" +rsa = "*" + +[package.extras] +cryptography = ["cryptography (>=3.4.0)"] +pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] +pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +category = "main" +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 = "rapidfuzz" +version = "3.6.1" +description = "rapid fuzzy string matching" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win32.whl", hash = "sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win32.whl", hash = "sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-win32.whl", hash = "sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win32.whl", hash = "sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win_arm64.whl", hash = "sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b"}, + {file = "rapidfuzz-3.6.1.tar.gz", hash = "sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7"}, +] + +[package.extras] +full = ["numpy"] + +[[package]] +name = "redis" +version = "5.0.1" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, + {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, +] + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "dev" +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 = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +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 = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f801d85ba4753d4ed97181d003e5d3fa330ac7c4587d131f61d7f968f416862"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b35c35e3923ade1e7ac44e150dec29f5863513246c8bf85e2d7d313e3832bcfb"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9b3fd5eca3c0b137a5e0e468e24ca544ed8ca4783e0e55341b7ed2807518ee"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a6209e689d0ff206c40032b6418e3cfcfc5af044b3f66e381d7f1ae301544b4"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:37e89d965b52e8b20571b5d44f26e2124b26ab63758bf1b7598a0e38fb2c4005"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6910eb4ea90c0889f363965cd3c8c45a620ad27b526a7899f0054f6c1b9219e"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-win32.whl", hash = "sha256:d8e7e8a150e7b548e7ecd6ebb9211c37265991bf2504297d9454e01b58530fc6"}, + {file = "SQLAlchemy-2.0.24-cp310-cp310-win_amd64.whl", hash = "sha256:396f05c552f7fa30a129497c41bef5b4d1423f9af8fe4df0c3dcd38f3e3b9a14"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:adbd67dac4ebf54587198b63cd30c29fd7eafa8c0cab58893d9419414f8efe4b"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a0f611b431b84f55779cbb7157257d87b4a2876b067c77c4f36b15e44ced65e2"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56a0e90a959e18ac5f18c80d0cad9e90cb09322764f536e8a637426afb1cae2f"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6db686a1d9f183c639f7e06a2656af25d4ed438eda581de135d15569f16ace33"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0cc0b486a56dff72dddae6b6bfa7ff201b0eeac29d4bc6f0e9725dc3c360d71"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a1d4856861ba9e73bac05030cec5852eabfa9ef4af8e56c19d92de80d46fc34"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-win32.whl", hash = "sha256:a3c2753bf4f48b7a6024e5e8a394af49b1b12c817d75d06942cae03d14ff87b3"}, + {file = "SQLAlchemy-2.0.24-cp311-cp311-win_amd64.whl", hash = "sha256:38732884eabc64982a09a846bacf085596ff2371e4e41d20c0734f7e50525d01"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9f992e0f916201731993eab8502912878f02287d9f765ef843677ff118d0e0b1"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2587e108463cc2e5b45a896b2e7cc8659a517038026922a758bde009271aed11"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb7cedcddffca98c40bb0becd3423e293d1fef442b869da40843d751785beb3"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fa6df0e035689df89ff77a46bf8738696785d3156c2c61494acdcddc75c69d"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cc889fda484d54d0b31feec409406267616536d048a450fc46943e152700bb79"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57ef6f2cb8b09a042d0dbeaa46a30f2df5dd1e1eb889ba258b0d5d7d6011b81c"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-win32.whl", hash = "sha256:ea490564435b5b204d8154f0e18387b499ea3cedc1e6af3b3a2ab18291d85aa7"}, + {file = "SQLAlchemy-2.0.24-cp312-cp312-win_amd64.whl", hash = "sha256:ccfd336f96d4c9bbab0309f2a565bf15c468c2d8b2d277a32f89c5940f71fcf9"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9aaaaa846b10dfbe1bda71079d0e31a7e2cebedda9409fa7dba3dfed1ae803e8"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95bae3d38f8808d79072da25d5e5a6095f36fe1f9d6c614dd72c59ca8397c7c0"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04191a7c8d77e63f6fc1e8336d6c6e93176c0c010833e74410e647f0284f5a1"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:acc58b7c2e40235712d857fdfc8f2bda9608f4a850d8d9ac0dd1fc80939ca6ac"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00d76fe5d7cdb5d84d625ce002ce29fefba0bfd98e212ae66793fed30af73931"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-win32.whl", hash = "sha256:29e51f848f843bbd75d74ae64ab1ab06302cb1dccd4549d1f5afe6b4a946edb2"}, + {file = "SQLAlchemy-2.0.24-cp37-cp37m-win_amd64.whl", hash = "sha256:e9d036e343a604db3f5a6c33354018a84a1d3f6dcae3673358b404286204798c"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9bafaa05b19dc07fa191c1966c5e852af516840b0d7b46b7c3303faf1a349bc9"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e69290b921b7833c04206f233d6814c60bee1d135b09f5ae5d39229de9b46cd4"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8398593ccc4440ce6dffcc4f47d9b2d72b9fe7112ac12ea4a44e7d4de364db1"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f073321a79c81e1a009218a21089f61d87ee5fa3c9563f6be94f8b41ff181812"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9036ebfd934813990c5b9f71f297e77ed4963720db7d7ceec5a3fdb7cd2ef6ce"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcf84fe93397a0f67733aa2a38ed4eab9fc6348189fc950e656e1ea198f45668"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-win32.whl", hash = "sha256:6f5e75de91c754365c098ac08c13fdb267577ce954fa239dd49228b573ca88d7"}, + {file = "SQLAlchemy-2.0.24-cp38-cp38-win_amd64.whl", hash = "sha256:9f29c7f0f4b42337ec5a779e166946a9f86d7d56d827e771b69ecbdf426124ac"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07cc423892f2ceda9ae1daa28c0355757f362ecc7505b1ab1a3d5d8dc1c44ac6"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a479aa1ab199178ff1956b09ca8a0693e70f9c762875d69292d37049ffd0d8f"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b8d0e8578e7f853f45f4512b5c920f6a546cd4bed44137460b2a56534644205"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17e7e27af178d31b436dda6a596703b02a89ba74a15e2980c35ecd9909eea3a"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1ca7903d5e7db791a355b579c690684fac6304478b68efdc7f2ebdcfe770d8d7"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db09e424d7bb89b6215a184ca93b4f29d7f00ea261b787918a1af74143b98c06"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-win32.whl", hash = "sha256:a5cd7d30e47f87b21362beeb3e86f1b5886e7d9b0294b230dde3d3f4a1591375"}, + {file = "SQLAlchemy-2.0.24-cp39-cp39-win_amd64.whl", hash = "sha256:7ae5d44517fe81079ce75cf10f96978284a6db2642c5932a69c82dbae09f009a"}, + {file = "SQLAlchemy-2.0.24-py3-none-any.whl", hash = "sha256:8f358f5cfce04417b6ff738748ca4806fe3d3ae8040fb4e6a0c9a6973ccf9b6e"}, + {file = "SQLAlchemy-2.0.24.tar.gz", hash = "sha256:6db97656fd3fe3f7e5b077f12fa6adb5feb6e0b567a3e99f47ecf5f7ea0a09e3"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.32.0.post1" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.32.0.post1-py3-none-any.whl", hash = "sha256:cd0cb10ddb49313f609cedfac62c8c12e56c7314b66d89bb077ba228bada1b09"}, + {file = "starlette-0.32.0.post1.tar.gz", hash = "sha256:e54e2b7e2fb06dff9eac40133583f10dfa05913f5a85bf26f427c7a40a9a3d02"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + +[[package]] +name = "trove-classifiers" +version = "2023.11.29" +description = "Canonical source for classifiers on PyPI (pypi.org)." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "trove-classifiers-2023.11.29.tar.gz", hash = "sha256:ff8f7fd82c7932113b46e7ef6742c70091cc63640c8c65db00d91f2e940b9514"}, + {file = "trove_classifiers-2023.11.29-py3-none-any.whl", hash = "sha256:02307750cbbac2b3d13078662f8a5bf077732bf506e9c33c97204b7f68f3699e"}, +] + +[[package]] +name = "types-pyopenssl" +version = "23.3.0.0" +description = "Typing stubs for pyOpenSSL" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-pyOpenSSL-23.3.0.0.tar.gz", hash = "sha256:5ffb077fe70b699c88d5caab999ae80e192fe28bf6cda7989b7e79b1e4e2dcd3"}, + {file = "types_pyOpenSSL-23.3.0.0-py3-none-any.whl", hash = "sha256:00171433653265843b7469ddb9f3c86d698668064cc33ef10537822156130ebf"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" + +[[package]] +name = "types-redis" +version = "4.6.0.11" +description = "Typing stubs for redis" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-redis-4.6.0.11.tar.gz", hash = "sha256:c8cfc84635183deca2db4a528966c5566445fd3713983f0034fb0f5a09e0890d"}, + {file = "types_redis-4.6.0.11-py3-none-any.whl", hash = "sha256:94fc61118601fb4f79206b33b9f4344acff7ca1d7bba67834987fb0efcf6a770"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-pyOpenSSL = "*" + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "urllib3" +version = "2.1.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +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 = "uvicorn" +version = "0.25.0" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.25.0-py3-none-any.whl", hash = "sha256:ce107f5d9bd02b4636001a77a4e74aab5e1e2b146868ebbad565237145af444c"}, + {file = "uvicorn-0.25.0.tar.gz", hash = "sha256:6dddbad1d7ee0f5140aba5ec138ddc9612c5109399903828b4874c9937f009c2"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "virtualenv" +version = "20.25.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "xattr" +version = "0.10.1" +description = "Python wrapper for extended filesystem attributes" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"}, + {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"}, + {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"}, + {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"}, +] + +[package.dependencies] +cffi = ">=1.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "2c4099d99b375795e1d392d3bbafd41f118a738506667e78106f1646ed23d293" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..d2eefcc --- /dev/null +++ b/poetry.toml @@ -0,0 +1,5 @@ +[virtualenvs] +in-project = true + +[experimental] +new-installer = false diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6d95362 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,168 @@ +[tool.ruff] +src = ["src"] +line-length = 120 +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "N", # pep8-naming (N) + "YTT", # flake8-2020 + "C", # flake8-comprehensions + "B", # flake8-bugbear + "T20", # flake8-print + "RET", # flake8-return + "SLF", # flake8-self + "ARG", # flake8-unused-arguments + "TCH", # flake8-type-checking + "RUF", # Ruff-specific rules +] +ignore = [ + "B008", # do not perform function calls in argument defaults + "C901", # too complex + "B904", # `except` clause, raise exceptions with `raise, + "B027", # is an empty method in an abstract base class + "B024", # abstract base class, but it has no abstract methods + "B026", # Star-arg unpacking after a keyword argument is strongly discouraged + "B905", # `zip()` without an explicit `strict=` parameter + "E701", # multiple statements on one line +] +# flake8 previously used the following codes: +# E701 E231 E225 E999 W503 E251 C901 I004 E800 B008 B024 B026 B028 +target-version = "py312" + +[tool.ruff.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 + +# ISORT CONFIGURATION USED BY RUFF +[tool.ruff.isort] # https://beta.ruff.rs/docs/settings/#isort +force-sort-within-sections = true + +force-wrap-aliases = true +combine-as-imports = true + +# The sections and order of the imports +section-order = [ + "future", + "standard-library", + "third-party", + "first-party", + "local-folder", +] + +# Our libraries used on the project, must be added here, then they will be groupped together +known-first-party = [] + +# Our local folders used on the project, must be added here, then they will be groupped together +known-local-folder = [ + "app", + "auth", + "config", + "core", + "northwind", + "infra", + "shared", + "utils", +] + +# Third party libraries used on the project, must be added here, then they will be groupped together +known-third-party = [ + "beartype", + "bcrypt", + "colorlog", + "fastapi", + "pydantic", + "pytest", + "typer", + "yaml", +] + +lines-after-imports = 2 +# lines-between-types = 1 + +[tool.ruff.flake8-bugbear] +extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"] + + +[tool.ruff.format] +quote-style = "single" +indent-style = "space" +docstring-code-format = true + +[tool.black] +line-length = 120 +skip-string-normalization = true +target-version = ['py312'] +include = '\.pyi?$' +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | migrations + )/ + | foo.py # also separately exclude a file named foo.py in + # the root of the project +) +''' + +[tool.pylint] +max-line-length = 120 +disable = ["C0112", "C0114", "C0115", "C0116"] + + +[tool.poetry] +name = "abusquets-northwind" +version = "0.1.0" +description = "" +authors = ["Alexandre Busquets Triola "] +readme = "README.md" +packages = [] + +[tool.poetry.dependencies] +python = "^3.12" +fastapi = "^0.108.0" +uvicorn = "^0.25.0" +pydantic = "^2.5.3" +pyyaml = "^6.0" +colorlog = "^6.7.0" +sqlalchemy = "^2.0.24" +alembic = "^1.13.1" +bcrypt = "^4.0.1" +asyncpg = "0.29" +httpx = "0.26.0" +click = "^8.1.7" +pycountry = "^22.3.5" +python-jose = { extras = ["cryptography"], version = "^3.3.0" } +redis = "^5.0.1" +pydantic-settings = "^2.1.0" +orjson = "^3.9.5" +greenlet = "^3.0.3" +faker = "22.0.0" +anyio = "^4.2.0" +pytest = "^7.4.4" +rapidfuzz = "^3.6.1" + +[tool.poetry.group.dev.dependencies] +debugpy = "^1.8.0" +pytest = "^7.4.3" +pytest-env = "^1.1.3" +pytest-cov = "^4.1.0" +pytest-asyncio = "^0.23.2" +poetry-types = "^0.5.0" +types-redis = "^4.6.0.11" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/src/.coveragerc b/src/.coveragerc new file mode 100644 index 0000000..a7cba45 --- /dev/null +++ b/src/.coveragerc @@ -0,0 +1,3 @@ +[run] +omit = + tests/* diff --git a/src/alembic.ini b/src/alembic.ini new file mode 100644 index 0000000..eddc4b5 --- /dev/null +++ b/src/alembic.ini @@ -0,0 +1,105 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = infra/database/alembic + +version_locations = infra/database/alembic/versions + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/src/app/__init__.py b/src/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app_container.py b/src/app/app_container.py new file mode 100644 index 0000000..3a2ebae --- /dev/null +++ b/src/app/app_container.py @@ -0,0 +1,28 @@ +from auth.di.mixins import AuthContainerMixin +from config import settings +from infra.cache.ports import AbstractCacheRepository +from infra.cache.redis_cache import RedisCache +from infra.database.sqlalchemy.session import AbstractDatabase, Database +from northwind.di.mixins import NorthwindContainerMixin +from utils.di import DIContainer, di_singleton + + +class AppContainerMixin: + db: AbstractDatabase + cache_repository: AbstractCacheRepository + + def _get_db(self) -> AbstractDatabase: + return Database() + + @di_singleton + def _get_cache_repository(self) -> AbstractCacheRepository: + return RedisCache( + url=settings.REDIS_URL, + user=settings.REDIS_USER, + password=settings.REDIS_PASSWORD, + ) + + +# @singleton +class AppContainer(NorthwindContainerMixin, AuthContainerMixin, AppContainerMixin, DIContainer): + pass diff --git a/src/app/asgi.py b/src/app/asgi.py new file mode 100644 index 0000000..fcdaed7 --- /dev/null +++ b/src/app/asgi.py @@ -0,0 +1,65 @@ +from contextlib import asynccontextmanager +import logging +from typing import AsyncIterator + +from fastapi import FastAPI, Request +from fastapi.responses import ORJSONResponse + +from .app_container import AppContainer +from app.setup_logging import setup_logging +from auth.adapters.api.http.router import router as auth_router +from config import settings +from northwind.adapters.api.http.router import router as northwind_router +from shared.exceptions import APPExceptionError + + +setup_logging() + +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(_: FastAPI) -> AsyncIterator: + await AppContainer().cache_repository.init() + # Start redis connection pool + yield + # Stop redis connection pool + await AppContainer().cache_repository.close() + + +app = FastAPI(debug=settings.DEBUG, openapi_url=None, lifespan=lifespan) + + +# @app.middleware('http') +# async def add_process_time_header(request: Request, call_next: Callable[[Request], Response]) -> Response: +# start_time = time.time() +# response = await call_next(request) +# process_time = time.time() - start_time +# response.headers['X-Process-Time'] = str(process_time) +# return response + + +@app.get('/') +async def root() -> dict: + return {'message': 'Hello World'} + + +api_app = FastAPI( + title=settings.PROJECT_NAME, + description=settings.PROJECT_DESCRIPTION, + version=settings.VERSION, + default_response_class=ORJSONResponse, +) + +api_app.include_router(auth_router) +api_app.include_router(northwind_router) + +app.mount('/api/v1', api_app) + + +@api_app.exception_handler(APPExceptionError) +async def custom_exception_handler(_: Request, exc: APPExceptionError) -> ORJSONResponse: + return ORJSONResponse( + status_code=exc.status_code, + content={'error': {'code': exc.code, 'message': exc.message}}, + ) diff --git a/src/app/exceptions.py b/src/app/exceptions.py new file mode 100644 index 0000000..2bea9af --- /dev/null +++ b/src/app/exceptions.py @@ -0,0 +1,7 @@ +from shared.exceptions import APPExceptionError + + +class EmptyPayloadExceptionError(APPExceptionError): + status_code = 422 + code = 'empty-payload' + message = "You haven't sent any data" diff --git a/src/app/schemas.py b/src/app/schemas.py new file mode 100644 index 0000000..aa80feb --- /dev/null +++ b/src/app/schemas.py @@ -0,0 +1,20 @@ +from typing import Optional + +from pydantic import BaseModel + + +class Profile(BaseModel): + first_name: str + last_name: Optional[str] = None + is_admin: bool + + +class Session(BaseModel): + uuid: str + expires: int + username: str + profile: Profile + + +class DetailResponse(BaseModel): + detail: str diff --git a/src/app/session_deps.py b/src/app/session_deps.py new file mode 100644 index 0000000..369a27f --- /dev/null +++ b/src/app/session_deps.py @@ -0,0 +1,77 @@ +from fastapi import Depends, HTTPException +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer +from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN + +from app.app_container import AppContainer +from app.schemas import Session +from auth.domain.services.token import TokenService +from infra.cache.ports import AbstractCacheRepository + + +http_bearer = HTTPBearer() + + +def get_token_service() -> TokenService: + return AppContainer().token_service + + +def get_cache_repository() -> AbstractCacheRepository: + return AppContainer().cache_repository + + +async def _check_token( + token_type: str, + credentials: HTTPAuthorizationCredentials, + cache_repository: AbstractCacheRepository, + token_service: TokenService, +) -> Session: + token = credentials.credentials + + if not token: + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail='Invalid authentication credentials.', + ) + + claims = token_service.decode_token(token) + + if claims is None: + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail='Invalid token or expired token.') + + if claims.get('type') != token_type: + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail='Invalid token.') + + uuid = str(claims.get('jit')) + if await cache_repository.get(uuid): + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail='Revoked token.') + + return Session(uuid=uuid, expires=claims.get('exp'), username=claims.get('sub'), profile=claims.get('profile')) + + +async def check_access_token( + credentials: HTTPAuthorizationCredentials = Depends(http_bearer), + cache_repository: AbstractCacheRepository = Depends(get_cache_repository), + token_service: TokenService = Depends(get_token_service), +) -> Session: + return await _check_token('a', credentials, cache_repository, token_service) + + +async def check_refresh_token( + credentials: HTTPAuthorizationCredentials = Depends(http_bearer), + cache_repository: AbstractCacheRepository = Depends(get_cache_repository), + token_service: TokenService = Depends(get_token_service), +) -> Session: + return await _check_token('r', credentials, cache_repository, token_service) + + +async def is_admin_session( + credentials: HTTPAuthorizationCredentials = Depends(http_bearer), + cache_repository: AbstractCacheRepository = Depends(get_cache_repository), + token_service: TokenService = Depends(get_token_service), +) -> Session: + session = await check_access_token(credentials, cache_repository, token_service) + + if not session.profile.is_admin: + raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail='Not authorized.') + + return session diff --git a/src/app/setup_logging.py b/src/app/setup_logging.py new file mode 100644 index 0000000..da99ad2 --- /dev/null +++ b/src/app/setup_logging.py @@ -0,0 +1,19 @@ +import logging.config +import os + +import yaml + +from config import settings + + +APP_ENV = os.getenv('APP_ENV', 'dev') + + +def setup_logging() -> None: + env = settings.APP_ENV + if env == 'test': + env = 'dev' + path = f'logging.{env}.yaml' + with open(path, 'rt', encoding='utf-8') as file: + config = yaml.safe_load(file.read()) + logging.config.dictConfig(config) diff --git a/src/auth/__init__.py b/src/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/adapters/__init__.py b/src/auth/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/adapters/api/__init__.py b/src/auth/adapters/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/adapters/api/cli/__init__.py b/src/auth/adapters/api/cli/__init__.py new file mode 100644 index 0000000..87cb57f --- /dev/null +++ b/src/auth/adapters/api/cli/__init__.py @@ -0,0 +1,7 @@ +from typing import Callable, List + +from .secret import create_secret +from .user import create_admin + + +commands: List[Callable[..., None]] = [create_secret, create_admin] diff --git a/src/auth/adapters/api/cli/secret.py b/src/auth/adapters/api/cli/secret.py new file mode 100644 index 0000000..a042a90 --- /dev/null +++ b/src/auth/adapters/api/cli/secret.py @@ -0,0 +1,7 @@ +import secrets + +import click + + +def create_secret() -> None: + click.echo(secrets.token_urlsafe(32)) diff --git a/src/auth/adapters/api/cli/user.py b/src/auth/adapters/api/cli/user.py new file mode 100644 index 0000000..3e1bbc5 --- /dev/null +++ b/src/auth/adapters/api/cli/user.py @@ -0,0 +1,48 @@ +from typing import Optional + +import click +from pydantic import BaseModel, Field, ValidationError + +from app.app_container import AppContainer +from auth.adapters.api.cli.user_presenter import UserPresenter +from auth.domain.services.user import CreateUserInDTO, UserService +from auth.domain.use_cases.user import CreateUserUseCase +from shared.exceptions import AlreadyExistsError +from utils.async_utils import async_exec + + +class CreateUserStdinDTO(BaseModel): + email: str = Field(max_length=255) + first_name: str = Field(max_length=255) + last_name: Optional[str] = Field(max_length=255, default=None) + password: str = Field(max_length=255, min_length=4) + + +async def _create_admin(email: str, first_name: str, password: str) -> None: + # Validate input + try: + in_data = CreateUserStdinDTO(email=email, first_name=first_name, password=password) + except ValidationError as e: + for error in e.errors(): + message = f'Error: {error}' + raise click.BadParameter(message) + + repo = AppContainer().user_repository + user_service = UserService(repo) + in_dto = CreateUserInDTO(**in_data.model_dump(), is_admin=True) + presenter = UserPresenter() + use_case = CreateUserUseCase(presenter=presenter, service=user_service) + try: + await use_case.execute(in_dto) + user = presenter.result + click.echo(f'The User {user.first_name}, {user.email} has been created') + except AlreadyExistsError as e: + click.echo(f'Error: {e.message}') + raise click.Abort() + + +@click.argument('email') +@click.argument('first_name') +@click.option('--password', '-p', help='Enter a password', prompt=True) +def create_admin(email: str, first_name: str, password: str) -> None: + async_exec(_create_admin, email, first_name, password) diff --git a/src/auth/adapters/api/cli/user_presenter.py b/src/auth/adapters/api/cli/user_presenter.py new file mode 100644 index 0000000..5c550a2 --- /dev/null +++ b/src/auth/adapters/api/cli/user_presenter.py @@ -0,0 +1,23 @@ +from typing import Optional + +from pydantic import BaseModel + +from auth.domain.entities.user import User +from auth.domain.entities.value_objects import UserId +from shared.presenter import AbstractPresenter + + +class UserResponse(BaseModel): + uuid: UserId + email: str + first_name: str + last_name: Optional[str] + is_admin: bool = False + is_active: bool = True + + +class UserPresenter(AbstractPresenter[User, UserResponse]): + result: UserResponse + + async def present(self, data: User) -> None: + self.result = UserResponse.model_validate(data, from_attributes=True) diff --git a/src/auth/adapters/api/http/__init__.py b/src/auth/adapters/api/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/adapters/api/http/router.py b/src/auth/adapters/api/http/router.py new file mode 100644 index 0000000..02b740e --- /dev/null +++ b/src/auth/adapters/api/http/router.py @@ -0,0 +1,7 @@ +from fastapi.routing import APIRouter + +from .token import router as token_router + + +router = APIRouter(prefix='/auth', tags=['auth']) +router.include_router(token_router) diff --git a/src/auth/adapters/api/http/schemas.py b/src/auth/adapters/api/http/schemas.py new file mode 100644 index 0000000..c9acecd --- /dev/null +++ b/src/auth/adapters/api/http/schemas.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel, Extra, Field + + +class LoginResponse(BaseModel): + access_token: str + refresh_token: str + + +class RefreshTokenResponse(BaseModel): + access_token: str + + +class ProtectedResponse(BaseModel): + username: str + + +class UserAuthRequest(BaseModel): + email: str = Field(..., description='User email') + password: str = Field(..., max_length=255, description='User password') + + class Config: + extra = Extra.forbid diff --git a/src/auth/adapters/api/http/token.py b/src/auth/adapters/api/http/token.py new file mode 100644 index 0000000..3281809 --- /dev/null +++ b/src/auth/adapters/api/http/token.py @@ -0,0 +1,101 @@ +from typing import Any, Dict + +from fastapi import Depends, HTTPException, status +from fastapi.routing import APIRouter + +from app.app_container import AppContainer +from app.schemas import DetailResponse, Session +from app.session_deps import check_access_token, check_refresh_token +from auth.adapters.api.cli.user_presenter import UserPresenter +from auth.adapters.api.http.schemas import LoginResponse, ProtectedResponse, RefreshTokenResponse, UserAuthRequest +from auth.domain.services.token import TokenService +from auth.domain.services.user import UserService +from auth.domain.use_cases.user import GetUserAndVerifyPasswordUseCase, InvalidPasswordExceptionError +from shared.exceptions import NotFoundError + + +router = APIRouter() + + +@router.post( + '/login', + summary='Create access and refresh tokens for user', + responses={ + 200: {'description': 'Successful Response'}, + 401: {'description': 'Unauthorized'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def login( + payload: UserAuthRequest, + app_container: AppContainer = Depends(AppContainer), +) -> LoginResponse: + user_presenter = UserPresenter() + user_service = UserService(app_container.user_repository) + get_user_use_case = GetUserAndVerifyPasswordUseCase(user_presenter, user_service) + + try: + await get_user_use_case.execute(payload.email, payload.password) + user = user_presenter.result + except (NotFoundError, InvalidPasswordExceptionError) as exc: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Incorrect email or password') from exc + + claims: Dict[str, Any] = { + 'profile': {'first_name': user.first_name, 'last_name': user.last_name, 'is_admin': user.is_admin} + } + + token_service = TokenService() + + access_token: str = token_service.create_access_token(user.email, claims) + refresh_token: str = token_service.create_refresh_token(user.email, claims) + + return LoginResponse(access_token=access_token, refresh_token=refresh_token) + + +@router.get( + '/protected', + summary='Get current session - example of protected endpoint', + responses={ + 200: {'description': 'Successful Response'}, + 403: {'description': 'Permission denied'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def protected(current_session: Session = Depends(check_access_token)) -> ProtectedResponse: + return ProtectedResponse(username=current_session.username) + + +@router.post('/refresh-token', summary='Refresh access token') +async def refresh( + session: Session = Depends(check_refresh_token), + app_container: AppContainer = Depends(AppContainer), +) -> RefreshTokenResponse: + token_service = TokenService(cache_repository=app_container.cache_repository) + + profile = session.profile + claims: Dict[str, Any] = { + 'profile': {'first_name': profile.first_name, 'last_name': profile.last_name, 'is_admin': profile.is_admin} + } + access_token: str = token_service.create_access_token(session.username, claims) + return RefreshTokenResponse(access_token=access_token) + + +@router.delete('/access-revoke') +async def access_revoke( + session: Session = Depends(check_access_token), + app_container: AppContainer = Depends(AppContainer), +) -> DetailResponse: + token_service = TokenService(cache_repository=app_container.cache_repository) + await token_service.revoke_access_token(session.uuid) + return DetailResponse(detail='Access Token has been revoked') + + +@router.delete('/refresh-revoke') +async def refresh_revoke( + session: Session = Depends(check_refresh_token), + app_container: AppContainer = Depends(AppContainer), +) -> DetailResponse: + token_service = TokenService(cache_repository=app_container.cache_repository) + await token_service.revoke_refresh_token(session.uuid) + + return DetailResponse(detail='Refresh Token has been revoked') diff --git a/src/auth/adapters/spi/__init__.py b/src/auth/adapters/spi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/adapters/spi/repositories/user.py b/src/auth/adapters/spi/repositories/user.py new file mode 100644 index 0000000..0efeb87 --- /dev/null +++ b/src/auth/adapters/spi/repositories/user.py @@ -0,0 +1,32 @@ +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import registry + +from auth.domain.entities.user import User +from auth.domain.ports.repositories.user import AbstractUserRepository, CreateUserInDTO, UpdatePartialUserInDTO +from auth.infra.database.sqlalchemy.models.user import users +from shared.exceptions import AlreadyExistsError +from shared.repository.sqlalchemy import SqlAlchemyRepository + + +mapper_registry = registry() + + +mapper_registry.map_imperatively( + User, + users, +) + + +class UserRepository( + SqlAlchemyRepository[User, CreateUserInDTO, UpdatePartialUserInDTO], + AbstractUserRepository, +): + key = 'email' + + async def create(self, data: CreateUserInDTO) -> User: + try: + ret = await super().create(data) + except IntegrityError as e: + if 'duplicate key value violates unique constraint "uq_core_user_email"' in str(e): + raise AlreadyExistsError(User.__name__) + return ret diff --git a/src/auth/alembic.ini b/src/auth/alembic.ini new file mode 100644 index 0000000..5650adc --- /dev/null +++ b/src/auth/alembic.ini @@ -0,0 +1,107 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = auth/infra/database/alembic/ + +version_locations = auth/infra/database/alembic/versions + + + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/src/auth/di/__init__.py b/src/auth/di/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/di/mixins/__init__.py b/src/auth/di/mixins/__init__.py new file mode 100644 index 0000000..b993673 --- /dev/null +++ b/src/auth/di/mixins/__init__.py @@ -0,0 +1,6 @@ +from auth.di.mixins.token import TokenContainerMixin +from auth.di.mixins.user import UserContainerMixin + + +class AuthContainerMixin(UserContainerMixin, TokenContainerMixin): + pass diff --git a/src/auth/di/mixins/token.py b/src/auth/di/mixins/token.py new file mode 100644 index 0000000..83a0303 --- /dev/null +++ b/src/auth/di/mixins/token.py @@ -0,0 +1,8 @@ +from auth.domain.services.token import TokenService + + +class TokenContainerMixin: + token_service: TokenService + + def _get_token_service(self) -> TokenService: + return TokenService() diff --git a/src/auth/di/mixins/user.py b/src/auth/di/mixins/user.py new file mode 100644 index 0000000..07c3056 --- /dev/null +++ b/src/auth/di/mixins/user.py @@ -0,0 +1,11 @@ +from auth.adapters.spi.repositories.user import UserRepository +from auth.domain.ports.repositories.user import AbstractUserRepository +from infra.database.sqlalchemy.session import AbstractDatabase + + +class UserContainerMixin: + db: AbstractDatabase + user_repository: AbstractUserRepository + + def _get_user_repository(self) -> AbstractUserRepository: + return UserRepository(self.db.session) diff --git a/src/auth/domain/__init__.py b/src/auth/domain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/dtos/__init__.py b/src/auth/domain/dtos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/dtos/country/__init__.py b/src/auth/domain/dtos/country/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/dtos/country/create_country.py b/src/auth/domain/dtos/country/create_country.py new file mode 100644 index 0000000..a3a3730 --- /dev/null +++ b/src/auth/domain/dtos/country/create_country.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel, Field + + +class CreateCountryInDTO(BaseModel): + code: str = Field(max_length=2) + name: str = Field(max_length=255) diff --git a/src/auth/domain/dtos/country/update_country.py b/src/auth/domain/dtos/country/update_country.py new file mode 100644 index 0000000..81daa15 --- /dev/null +++ b/src/auth/domain/dtos/country/update_country.py @@ -0,0 +1,7 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class UpdatePartialCountryInDTO(BaseModel): + name: Optional[str] = Field(max_length=255) diff --git a/src/auth/domain/entities/__init__.py b/src/auth/domain/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/entities/user.py b/src/auth/domain/entities/user.py new file mode 100644 index 0000000..b5b6492 --- /dev/null +++ b/src/auth/domain/entities/user.py @@ -0,0 +1,33 @@ +from dataclasses import dataclass, field +from typing import Optional +import uuid as uuid_lib + +import bcrypt + +from auth.domain.entities.value_objects import UserId + + +@dataclass(kw_only=True) +class BaseUser: + email: str + first_name: str + last_name: Optional[str] + password: str + is_admin: bool = False + is_active: bool = True + + @staticmethod + def encrypt_password(password: str) -> str: + salt = bcrypt.gensalt() + hashed_password = bcrypt.hashpw(password.encode(), salt) + return str(hashed_password.decode()) + + @staticmethod + def verify_password(password: str, hashed: str) -> bool: + ret: bool = bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode() + return ret + + +@dataclass(kw_only=True) +class User(BaseUser): + uuid: UserId = field(default_factory=uuid_lib.uuid4) diff --git a/src/auth/domain/entities/value_objects.py b/src/auth/domain/entities/value_objects.py new file mode 100644 index 0000000..fa36a74 --- /dev/null +++ b/src/auth/domain/entities/value_objects.py @@ -0,0 +1,4 @@ +import uuid + + +UserId = uuid.UUID diff --git a/src/auth/domain/ports/__init__.py b/src/auth/domain/ports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/ports/repositories/__init__.py b/src/auth/domain/ports/repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/ports/repositories/user.py b/src/auth/domain/ports/repositories/user.py new file mode 100644 index 0000000..65cc1d1 --- /dev/null +++ b/src/auth/domain/ports/repositories/user.py @@ -0,0 +1,28 @@ +from typing import Optional + +from pydantic import BaseModel, Field + +from auth.domain.entities.user import User +from shared.repository.ports.generic import AbstractRepository + + +class CreateUserInDTO(BaseModel): + email: str = Field(max_length=255) + first_name: str = Field(max_length=255) + last_name: Optional[str] = Field(max_length=255, default=None) + password: str + is_admin: bool = Field(default=False) + is_active: bool = Field(default=True) + + # Hauriem d'haver encriptat la contrasenya abans de guardar-la a la base de dades + + +class UpdatePartialUserInDTO(BaseModel): + email: Optional[str] = Field(max_length=255) + first_name: Optional[str] = Field(max_length=255) + last_name: Optional[str] = Field(max_length=255) + password: Optional[str] = Field(max_length=255) + + +class AbstractUserRepository(AbstractRepository[User, CreateUserInDTO, UpdatePartialUserInDTO]): + pass diff --git a/src/auth/domain/services/__init__.py b/src/auth/domain/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/services/token.py b/src/auth/domain/services/token.py new file mode 100644 index 0000000..98c7bf6 --- /dev/null +++ b/src/auth/domain/services/token.py @@ -0,0 +1,58 @@ +from datetime import datetime, timedelta +import logging +from typing import Literal, Optional, Type +import uuid + +from jose import JWTError, jwt + +from config import settings +from infra.cache.ports import AbstractCacheRepository + + +logguer = logging.getLogger(settings.APP_LOGGER_NAME) + + +class TokenService: + def __init__(self, cache_repository: Optional[AbstractCacheRepository] = None) -> None: + self.cache_repository = cache_repository + + @classmethod + def _create_token( + cls: Type['TokenService'], type_token: Literal['a', 'r'], subject: str, claims: dict, expiration_minutes: int + ) -> str: + now = datetime.utcnow() + expires = datetime.utcnow() + timedelta(minutes=expiration_minutes) + payload = { + 'type': type_token, + 'nbf': now, + 'iat': now, + 'exp': expires, + 'sub': subject, + 'jit': uuid.uuid4().hex, + } | claims + + return jwt.encode(payload | claims, settings.JWT_SECRET_KEY, settings.JWT_ALGORITHM) + + def create_access_token(self, subject: str, claims: dict) -> str: + return self._create_token('a', subject, claims, settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + def create_refresh_token(self, subject: str, claims: dict) -> str: + return self._create_token('r', subject, claims, settings.REFRESH_TOKEN_EXPIRE_MINUTES) + + def decode_token(self, token: str) -> Optional[dict]: + claims = None + try: + claims = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]) + except JWTError: + logguer.debug('Token verification failed!', extra={'token': token}) + return claims + + async def revoke_access_token(self, token: str) -> None: + if not self.cache_repository: + raise ValueError('Cache repository not set!') + await self.cache_repository.set(token, 'true', settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60) + + async def revoke_refresh_token(self, token: str) -> None: + if not self.cache_repository: + raise ValueError('Cache repository not set!') + await self.cache_repository.set(token, 'true', settings.REFRESH_TOKEN_EXPIRE_MINUTES * 60) diff --git a/src/auth/domain/services/user.py b/src/auth/domain/services/user.py new file mode 100644 index 0000000..4a1ca18 --- /dev/null +++ b/src/auth/domain/services/user.py @@ -0,0 +1,28 @@ +from typing import Type + +from pydantic import validator + +from auth.domain.entities.user import User +from auth.domain.ports.repositories.user import ( + AbstractUserRepository, + CreateUserInDTO as CreateUserInRepoDTO, +) + + +class CreateUserInDTO(CreateUserInRepoDTO): + @validator('password', pre=True, always=True) + @classmethod + def check_password(cls: Type['CreateUserInDTO'], v: str) -> str: + return User.encrypt_password(v) + + +class UserService: + def __init__(self, user_repository: AbstractUserRepository): + self.user_repository = user_repository + + async def create_user(self, user_in: CreateUserInDTO) -> User: + user_in_repo = CreateUserInRepoDTO(**user_in.model_dump()) + return await self.user_repository.create(user_in_repo) + + async def get_user_by_username(self, uuid: str) -> User: + return await self.user_repository.get_by_id(uuid) diff --git a/src/auth/domain/use_cases/__init__.py b/src/auth/domain/use_cases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/domain/use_cases/user.py b/src/auth/domain/use_cases/user.py new file mode 100644 index 0000000..980e211 --- /dev/null +++ b/src/auth/domain/use_cases/user.py @@ -0,0 +1,33 @@ +from auth.domain.entities.user import User +from auth.domain.services.user import CreateUserInDTO, UserService +from shared.exceptions import APPExceptionError +from shared.presenter import AbstractPresenter + + +class InvalidPasswordExceptionError(APPExceptionError): + status_code = 401 + code = 'invalid-password' + message = 'Invalid password' + + +class CreateUserUseCase: + def __init__(self, presenter: AbstractPresenter, service: UserService): + self.presenter = presenter + self.service = service + + async def execute(self, in_dto: CreateUserInDTO) -> None: + user = await self.service.create_user(in_dto) + await self.presenter.present(user) + + +class GetUserAndVerifyPasswordUseCase: + def __init__(self, presenter: AbstractPresenter, service: UserService): + self.presenter = presenter + self.service = service + + async def execute(self, username: str, password: str) -> None: + user = await self.service.get_user_by_username(username) + if not User.verify_password(password, user.password): + raise InvalidPasswordExceptionError() + + await self.presenter.present(user) diff --git a/src/auth/infra/__init__.py b/src/auth/infra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/infra/database/__init__.py b/src/auth/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/infra/database/sqlalchemy/__init__.py b/src/auth/infra/database/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/infra/database/sqlalchemy/models/__init__.py b/src/auth/infra/database/sqlalchemy/models/__init__.py new file mode 100644 index 0000000..7b12cc3 --- /dev/null +++ b/src/auth/infra/database/sqlalchemy/models/__init__.py @@ -0,0 +1 @@ +from . import user # noqa diff --git a/src/auth/infra/database/sqlalchemy/models/user.py b/src/auth/infra/database/sqlalchemy/models/user.py new file mode 100644 index 0000000..3874411 --- /dev/null +++ b/src/auth/infra/database/sqlalchemy/models/user.py @@ -0,0 +1,21 @@ +import uuid + +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.schema import Column, Table +from sqlalchemy.types import Boolean, Integer, String, Text + +from infra.database.sqlalchemy.sqlalchemy import metadata + + +users = Table( + 'auth_user', + metadata, + Column('id', Integer, primary_key=True), + Column('uuid', UUID(as_uuid=True), unique=True, nullable=False, default=uuid.uuid4), + Column('first_name', String(255), nullable=False), + Column('last_name', String(255), nullable=True), + Column('email', String(255), unique=True, nullable=False), + Column('password', Text(), nullable=False), + Column('is_active', Boolean(), nullable=False, default=True), + Column('is_admin', Boolean(), nullable=False, default=False), +) diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..990a530 --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1,26 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + PROJECT_NAME: str = 'Northwind API' + PROJECT_DESCRIPTION: str = 'Northwind API - PROOF OF CONCEPT' + VERSION: str = '0.1.0' + APP_LOGGER_NAME: str = 'northwind' + + APP_ENV: str = 'dev' + DEBUG: bool = False + + DATABASE_URL: str = 'postgresql+asyncpg://postgres:change-me@postgres:5432/postgres' + + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # 30 minutes + REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days + JWT_ALGORITHM: str = 'HS256' + JWT_SECRET_KEY: str = 'change-me' + JWT_REFRESH_SECRET_KEY: str = 'change-me' + + REDIS_URL: str = 'redis://redis:6379/0' + REDIS_USER: str = 'default' + REDIS_PASSWORD: str = 'change-me' + + +settings = Settings() diff --git a/src/infra/__init__.py b/src/infra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/cache/__init__.py b/src/infra/cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/cache/memory_cache.py b/src/infra/cache/memory_cache.py new file mode 100644 index 0000000..181ef30 --- /dev/null +++ b/src/infra/cache/memory_cache.py @@ -0,0 +1,38 @@ +from threading import RLock +import time +from typing import Any, Dict, Optional + +from infra.cache.ports import AbstractCacheRepository, EncodableT + + +class MemoryCache(AbstractCacheRepository): + def __init__(self) -> None: + self._data: Dict[str, tuple[EncodableT, float]] = {} + self.lock = RLock() + + async def init(self) -> None: + pass + + async def close(self) -> None: + with self.lock: + self._data = {} + + async def get(self, key: str) -> Any: + with self.lock: + ret = self._data.get(key) + if ret is not None: + if ret[1] < time.time(): + return ret[0] + self._data.pop(key, None) + return None + + async def set(self, key: str, value: Optional[EncodableT], expire: int) -> None: + with self.lock: + if value is None: + self._data.pop(key, None) + else: + self._data[key] = (value, time.time() + expire) + + async def delete(self, key: str) -> None: + with self.lock: + self._data.pop(key, None) diff --git a/src/infra/cache/ports.py b/src/infra/cache/ports.py new file mode 100644 index 0000000..6216e69 --- /dev/null +++ b/src/infra/cache/ports.py @@ -0,0 +1,27 @@ +import abc +from typing import Any, Optional, Union + + +EncodableT = Union[str, int, float, bytes] + + +class AbstractCacheRepository(abc.ABC): + @abc.abstractmethod + async def get(self, key: str) -> Any: + ... + + @abc.abstractmethod + async def set(self, key: str, value: Optional[EncodableT], expire: int) -> None: + ... + + @abc.abstractmethod + async def delete(self, key: str) -> None: + ... + + @abc.abstractmethod + async def init(self) -> None: + ... + + @abc.abstractmethod + async def close(self) -> None: + ... diff --git a/src/infra/cache/redis_cache.py b/src/infra/cache/redis_cache.py new file mode 100644 index 0000000..4cad1cc --- /dev/null +++ b/src/infra/cache/redis_cache.py @@ -0,0 +1,50 @@ +from typing import Any, Optional + +from redis import ( + asyncio as aioredis, +) + +from infra.cache.ports import AbstractCacheRepository, EncodableT +from utils.singleton import singleton + + +@singleton +class RedisCache(AbstractCacheRepository): + def __init__(self, url: str, user: str, password: str) -> None: + self.redis: Optional[aioredis.Redis] = None + self.url = url + self.user = user + self.password = password + + async def init(self) -> None: + self.redis = await aioredis.from_url( + self.url, + username=self.user, + password=self.password, + max_connections=10, + decode_responses=True, + ) + + async def get_redis(self) -> aioredis.Redis: + if self.redis is None: + await self.init() + if self.redis is None: + raise Exception('Redis not initialized') + return self.redis + + async def close(self) -> None: + await (await self.get_redis()).connection_pool.disconnect() + + async def get(self, key: str) -> Any: + return await (await self.get_redis()).get(key) + + async def set(self, key: str, value: Optional[EncodableT], expire: int) -> None: + """expire: expiration in seconds""" + await self.get_redis() + if value is None: + await self.delete(key) + else: + await (await self.get_redis()).set(key, value, expire) + + async def delete(self, key: str) -> None: + await (await self.get_redis()).delete(key) diff --git a/src/infra/database/__init__.py b/src/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/database/alembic/env.py b/src/infra/database/alembic/env.py new file mode 100644 index 0000000..290db2b --- /dev/null +++ b/src/infra/database/alembic/env.py @@ -0,0 +1,103 @@ +import asyncio +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import AsyncEngine + +import auth.infra.database.sqlalchemy.models # noqa +from config import settings +from infra.database.sqlalchemy.sqlalchemy import metadata +import northwind.infra.database.sqlalchemy.models # noqa + + +# import northwind.infra.database.sqlalchemy.models + + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +config.set_main_option('sqlalchemy.url', settings.DATABASE_URL) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +# target_metadata = None + + +target_metadata = metadata + + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option('sqlalchemy.url') + + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={'paramstyle': 'named'}, + transaction_per_migration=True, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = AsyncEngine( + engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + future=True, + ) + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + asyncio.run(run_migrations_online()) diff --git a/src/infra/database/alembic/script.py.mako b/src/infra/database/alembic/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/src/infra/database/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/src/infra/database/alembic/versions/82107b1b46b7_add_northwind_models.py b/src/infra/database/alembic/versions/82107b1b46b7_add_northwind_models.py new file mode 100644 index 0000000..2d9b327 --- /dev/null +++ b/src/infra/database/alembic/versions/82107b1b46b7_add_northwind_models.py @@ -0,0 +1,207 @@ +"""add northwind models + +Revision ID: 82107b1b46b7 +Revises: ec48bb74a747 +Create Date: 2024-01-01 07:56:14.455308 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '82107b1b46b7' +down_revision = 'ec48bb74a747' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'categories', + sa.Column('category_id', sa.Integer(), nullable=False), + sa.Column('category_name', sa.String(length=15), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('picture', sa.LargeBinary(), nullable=True), + sa.PrimaryKeyConstraint('category_id', name='categories_pkey'), + ) + op.create_table( + 'customer_demographics', + sa.Column('customer_type_id', sa.String(length=5), nullable=False), + sa.Column('customer_desc', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('customer_type_id', name='pk_customer_demographics'), + ) + op.create_table( + 'customers', + sa.Column('customer_id', sa.String(length=5), nullable=False), + sa.Column('company_name', sa.String(length=40), nullable=False), + sa.Column('contact_name', sa.String(length=30), nullable=True), + sa.Column('contact_title', sa.String(length=30), nullable=True), + sa.Column('address', sa.String(length=60), nullable=True), + sa.Column('city', sa.String(length=15), nullable=True), + sa.Column('region', sa.String(length=15), nullable=True), + sa.Column('postal_code', sa.String(length=10), nullable=True), + sa.Column('country', sa.String(length=15), nullable=True), + sa.Column('phone', sa.String(length=24), nullable=True), + sa.Column('fax', sa.String(length=24), nullable=True), + sa.PrimaryKeyConstraint('customer_id', name='pk_customers'), + ) + op.create_table( + 'employees', + sa.Column('employee_id', sa.Integer(), nullable=False), + sa.Column('last_name', sa.String(length=20), nullable=False), + sa.Column('first_name', sa.String(length=10), nullable=False), + sa.Column('title', sa.String(length=30), nullable=True), + sa.Column('title_of_courtesy', sa.String(length=25), nullable=True), + sa.Column('birth_date', sa.Date(), nullable=True), + sa.Column('hire_date', sa.Date(), nullable=True), + sa.Column('address', sa.String(length=60), nullable=True), + sa.Column('city', sa.String(length=15), nullable=True), + sa.Column('region', sa.String(length=15), nullable=True), + sa.Column('postal_code', sa.String(length=10), nullable=True), + sa.Column('country', sa.String(length=15), nullable=True), + sa.Column('home_phone', sa.String(length=24), nullable=True), + sa.Column('extension', sa.String(length=4), nullable=True), + sa.Column('photo', sa.LargeBinary(), nullable=True), + sa.Column('notes', sa.Text(), nullable=True), + sa.Column('reports_to', sa.SmallInteger(), nullable=True), + sa.Column('photo_path', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['reports_to'], ['employees.employee_id'], name='fk_employees_employees'), + sa.PrimaryKeyConstraint('employee_id', name='employees_pkey'), + ) + op.create_table( + 'region', + sa.Column('region_id', sa.Integer(), nullable=False), + sa.Column('region_description', sa.String(length=60), nullable=False), + sa.PrimaryKeyConstraint('region_id', name='region_pkey'), + ) + op.create_table( + 'shippers', + sa.Column('shipper_id', sa.Integer(), nullable=False), + sa.Column('company_name', sa.String(length=40), nullable=False), + sa.Column('phone', sa.String(length=24), nullable=True), + sa.PrimaryKeyConstraint('shipper_id', name='shippers_pkey'), + ) + op.create_table( + 'suppliers', + sa.Column('supplier_id', sa.Integer(), nullable=False), + sa.Column('company_name', sa.String(length=40), nullable=False), + sa.Column('contact_name', sa.String(length=30), nullable=True), + sa.Column('contact_title', sa.String(length=30), nullable=True), + sa.Column('address', sa.String(length=60), nullable=True), + sa.Column('city', sa.String(length=15), nullable=True), + sa.Column('region', sa.String(length=15), nullable=True), + sa.Column('postal_code', sa.String(length=10), nullable=True), + sa.Column('country', sa.String(length=15), nullable=True), + sa.Column('phone', sa.String(length=24), nullable=True), + sa.Column('fax', sa.String(length=24), nullable=True), + sa.Column('homepage', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('supplier_id', name='suppliers_pkey'), + ) + op.create_table( + 'us_states', + sa.Column('state_id', sa.Integer(), nullable=False), + sa.Column('state_name', sa.String(length=100), nullable=True), + sa.Column('state_abbr', sa.String(length=2), nullable=True), + sa.Column('state_region', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('state_id', name='us_states_pkey'), + ) + op.create_table( + 'customer_customer_demo', + sa.Column('customer_id', sa.String(length=5), nullable=False), + sa.Column('customer_type_id', sa.String(length=5), nullable=False), + sa.ForeignKeyConstraint(['customer_id'], ['customers.customer_id'], name='fk_customer_customer_demo_customers'), + sa.ForeignKeyConstraint( + ['customer_type_id'], + ['customer_demographics.customer_type_id'], + name='fk_customer_customer_demo_customer_demographics', + ), + sa.PrimaryKeyConstraint('customer_id', 'customer_type_id', name='pk_customer_customer_demo'), + ) + op.create_table( + 'orders', + sa.Column('order_id', sa.Integer(), nullable=False), + sa.Column('customer_id', sa.String(length=5), nullable=True), + sa.Column('employee_id', sa.SmallInteger(), nullable=True), + sa.Column('order_date', sa.Date(), nullable=True), + sa.Column('required_date', sa.Date(), nullable=True), + sa.Column('shipped_date', sa.Date(), nullable=True), + sa.Column('ship_via', sa.SmallInteger(), nullable=True), + sa.Column('freight', sa.Float(), nullable=True), + sa.Column('ship_name', sa.String(length=40), nullable=True), + sa.Column('ship_address', sa.String(length=60), nullable=True), + sa.Column('ship_city', sa.String(length=15), nullable=True), + sa.Column('ship_region', sa.String(length=15), nullable=True), + sa.Column('ship_postal_code', sa.String(length=10), nullable=True), + sa.Column('ship_country', sa.String(length=15), nullable=True), + sa.ForeignKeyConstraint(['customer_id'], ['customers.customer_id'], name='fk_orders_customers'), + sa.ForeignKeyConstraint(['employee_id'], ['employees.employee_id'], name='fk_orders_employees'), + sa.ForeignKeyConstraint(['ship_via'], ['shippers.shipper_id'], name='fk_orders_shippers'), + sa.PrimaryKeyConstraint('order_id', name='orders_pkey'), + ) + op.create_table( + 'products', + sa.Column('product_id', sa.Integer(), nullable=False), + sa.Column('product_name', sa.String(length=40), nullable=False), + sa.Column('supplier_id', sa.SmallInteger(), nullable=True), + sa.Column('category_id', sa.SmallInteger(), nullable=True), + sa.Column('quantity_per_unit', sa.String(length=20), nullable=True), + sa.Column('unit_price', sa.Float(), nullable=True), + sa.Column('units_in_stock', sa.SmallInteger(), nullable=True), + sa.Column('units_on_order', sa.SmallInteger(), nullable=True), + sa.Column('reorder_level', sa.SmallInteger(), nullable=True), + sa.Column('discontinued', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['category_id'], ['categories.category_id'], name='fk_products_categories'), + sa.ForeignKeyConstraint(['supplier_id'], ['suppliers.supplier_id'], name='fk_products_suppliers'), + sa.PrimaryKeyConstraint('product_id', name='products_pkey'), + ) + op.create_table( + 'territories', + sa.Column('territory_id', sa.String(length=20), nullable=False), + sa.Column('territory_description', sa.String(length=60), nullable=False), + sa.Column('region_id', sa.SmallInteger(), nullable=False), + sa.ForeignKeyConstraint(['region_id'], ['region.region_id'], name='fk_territories_region'), + sa.PrimaryKeyConstraint('territory_id', name='pk_territories'), + ) + op.create_table( + 'employee_territories', + sa.Column('employee_id', sa.SmallInteger(), nullable=False), + sa.Column('territory_id', sa.String(length=20), nullable=False), + sa.ForeignKeyConstraint(['employee_id'], ['employees.employee_id'], name='fk_employee_territories_employees'), + sa.ForeignKeyConstraint( + ['territory_id'], ['territories.territory_id'], name='fk_employee_territories_territories' + ), + sa.PrimaryKeyConstraint('employee_id', 'territory_id', name='pk_employee_territories'), + ) + op.create_table( + 'order_details', + sa.Column('order_id', sa.SmallInteger(), nullable=False), + sa.Column('product_id', sa.SmallInteger(), nullable=False), + sa.Column('unit_price', sa.Float(), nullable=False), + sa.Column('quantity', sa.SmallInteger(), nullable=False), + sa.Column('discount', sa.Float(), nullable=False), + sa.ForeignKeyConstraint(['order_id'], ['orders.order_id'], name='fk_order_details_orders'), + sa.ForeignKeyConstraint(['product_id'], ['products.product_id'], name='fk_order_details_products'), + sa.PrimaryKeyConstraint('order_id', 'product_id', name='pk_order_details'), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('order_details') + op.drop_table('employee_territories') + op.drop_table('territories') + op.drop_table('products') + op.drop_table('orders') + op.drop_table('customer_customer_demo') + op.drop_table('us_states') + op.drop_table('suppliers') + op.drop_table('shippers') + op.drop_table('region') + op.drop_table('employees') + op.drop_table('customers') + op.drop_table('customer_demographics') + op.drop_table('categories') + # ### end Alembic commands ### diff --git a/src/infra/database/alembic/versions/ec48bb74a747_init.py b/src/infra/database/alembic/versions/ec48bb74a747_init.py new file mode 100644 index 0000000..0fc9e75 --- /dev/null +++ b/src/infra/database/alembic/versions/ec48bb74a747_init.py @@ -0,0 +1,41 @@ +"""init + +Revision ID: ec48bb74a747 +Revises: +Create Date: 2023-12-27 08:06:48.964137 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ec48bb74a747' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'auth_user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uuid', sa.UUID(), nullable=False), # type: ignore + sa.Column('first_name', sa.String(length=255), nullable=False), + sa.Column('last_name', sa.String(length=255), nullable=True), + sa.Column('email', sa.String(length=255), nullable=False), + sa.Column('password', sa.Text(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('is_admin', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('pk_auth_user')), + sa.UniqueConstraint('email', name=op.f('uq_auth_user_email')), + sa.UniqueConstraint('uuid', name=op.f('uq_auth_user_uuid')), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('auth_user') + # ### end Alembic commands ### diff --git a/src/infra/database/sqlalchemy/__init__.py b/src/infra/database/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/database/sqlalchemy/models/__init__.py b/src/infra/database/sqlalchemy/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/database/sqlalchemy/session.py b/src/infra/database/sqlalchemy/session.py new file mode 100644 index 0000000..2c01c98 --- /dev/null +++ b/src/infra/database/sqlalchemy/session.py @@ -0,0 +1,79 @@ +import abc +from contextlib import asynccontextmanager +import contextvars +import logging +import os +from typing import TYPE_CHECKING, Any, AsyncContextManager, AsyncIterator, Dict, Final, Optional, cast + +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine +from sqlalchemy.orm import sessionmaker + +from config import settings + + +# from utils.singleton import singleton + + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio.engine import AsyncEngine + + +logger = logging.getLogger(__name__) + + +db_session_context: contextvars.ContextVar = contextvars.ContextVar('db_ctx', default={'session': None, 'level': 0}) + + +SA_ECHO: Final[bool] = os.getenv('SA_ECHO', 'False').lower() == 'true' + + +class AbstractDatabase(abc.ABC): + @abc.abstractmethod + def session(self) -> AsyncContextManager[AsyncSession]: + ... + + +# @singleton +class Database(AbstractDatabase): + def __init__(self) -> None: + self.engine: AsyncEngine = create_async_engine(settings.DATABASE_URL, echo=SA_ECHO, future=True) + self._session_factory: sessionmaker = sessionmaker( + self.engine, class_=AsyncSession, autocommit=False, autoflush=False, expire_on_commit=False + ) + + @asynccontextmanager + async def session(self) -> AsyncIterator[AsyncSession]: + db_session: Optional[Dict[str, Any]] = None + db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + session: AsyncSession = cast(AsyncSession, self._session_factory()) + db_session['session'] = session + await session.begin() + logger.debug('session begin', extra={'level': db_session['level']}) + + else: + session = db_session['session'] + db_session['level'] = (db_session['level'] or 0) + 1 + db_session_context.set(db_session) + + try: + yield session + except Exception: + logger.exception('Session rollback because of exception') + await session.rollback() + logger.debug('session rollback') + raise + else: + # db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + await session.commit() + logger.debug('session commit', extra={'level': db_session['level']}) + finally: + # db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + await session.close() + logger.debug('session close', extra={'level': db_session['level']}) + db_session_context.set(None) + else: + db_session['level'] = (db_session['level'] or 0) - 1 + db_session_context.set(db_session) diff --git a/src/infra/database/sqlalchemy/sqlalchemy.py b/src/infra/database/sqlalchemy/sqlalchemy.py new file mode 100644 index 0000000..452c7d8 --- /dev/null +++ b/src/infra/database/sqlalchemy/sqlalchemy.py @@ -0,0 +1,12 @@ +from sqlalchemy.schema import MetaData + + +metadata = MetaData( + naming_convention={ + 'ix': 'ix_%(column_0_label)s', + 'uq': 'uq_%(table_name)s_%(column_0_name)s', + 'ck': 'ck_%(table_name)s_%(constraint_name)s', + 'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s', + 'pk': 'pk_%(table_name)s', + } +) diff --git a/src/logging.dev.yaml b/src/logging.dev.yaml new file mode 100644 index 0000000..e67ffbc --- /dev/null +++ b/src/logging.dev.yaml @@ -0,0 +1,52 @@ +version: 1 +disable_existing_loggers: False + +formatters: + standard_extra: + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + error: + format: "%(levelname)s %(name)s.%(funcName)s(): %(message)s" + colored_extra: + class: utils.logger.formatter.color_extra.ColorFormatterExtra + format: "%(asctime)s.%(msecs)03d | %(log_color)s%(levelname)s:%(name)s:%(message)s %(reset)s %(module)s.%(funcName)s" + datefmt: "%Y-%m-%d %H:%M:%S" + +handlers: + console: + class: logging.StreamHandler + level: DEBUG + stream: ext://sys.stdout + formatter: colored_extra + +root: + level: DEBUG + handlers: [console] + propagate: yes + +loggers: + requests: + level: INFO + sqlalchemy: + level: WARNING + uvicorn: + level: INFO + uvicorn.error: + level: INFO + uvicorn.access: + level: INFO + gunicorn: + level: INFO + botocore: + level: WARNING + aiormq: + level: ERROR + urllib3: + level: ERROR + aio_pika: + level: INFO + infra.database.sqlalchemy.session: + level: INFO + sqlalchemy.engine: + level: ERROR + faker.factory: + level: ERROR diff --git a/src/manage.py b/src/manage.py new file mode 100644 index 0000000..a137cf4 --- /dev/null +++ b/src/manage.py @@ -0,0 +1,31 @@ +import click + +import auth.adapters.api.cli + +# import core.adapters.api.cli +from utils.async_utils import async_exec + + +@click.group() +def cli_app() -> None: + pass + + +# for command in core.adapters.api.cli.commands: +# cli_app.command()(command) + +for command in auth.adapters.api.cli.commands: + cli_app.command()(command) + + +async def _test() -> None: + click.echo('Manage is working fine') + + +@cli_app.command() +def test() -> None: + async_exec(_test) + + +if __name__ == '__main__': + cli_app() diff --git a/src/misc/__init__.py b/src/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/__init__.py b/src/northwind/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/__init__.py b/src/northwind/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/api/__init__.py b/src/northwind/adapters/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/api/http/__init__.py b/src/northwind/adapters/api/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/api/http/category.py b/src/northwind/adapters/api/http/category.py new file mode 100644 index 0000000..0b78a5e --- /dev/null +++ b/src/northwind/adapters/api/http/category.py @@ -0,0 +1,141 @@ +from fastapi import Depends +from fastapi.responses import JSONResponse +from fastapi.routing import APIRouter + +from app.app_container import AppContainer +from app.exceptions import EmptyPayloadExceptionError +from app.schemas import Session +from app.session_deps import check_access_token, is_admin_session +from northwind.adapters.api.http.presenters.category import CategoryPagedListPresenter, CategoryPresenter +from northwind.adapters.api.http.schemas.category import ( + CategoryListPagedResponse, + CategoryResponse, + CreateCategoryRequestDTO, + CreateCategoryResponseDTO, + UpdateCategoryRequestDTO, + UpdatePartialCategoryRequestDTO, +) +from northwind.domain.entities.category import Category +from northwind.domain.services.category import Categorieservice +from northwind.domain.use_cases.category import ( + CreateCategoryUseCase, + GetCategoriesUseCase, + GetCategoryUseCase, + UpdateCategoryUseCase, +) +from northwind.schemas.category import CreateCategoryInDTO, UpdatePartialCategoryInDTO +from shared.api.schemas.page import PageParams + + +router = APIRouter(prefix='/category') + + +@router.get( + '/{id}', + responses={ + 200: {'description': 'Successful Response'}, + 404: {'description': 'Category not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def get_category( + id: int, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(check_access_token), +) -> CategoryResponse: + service = Categorieservice(container.category_repository) + presenter = CategoryPresenter() + usecase = GetCategoryUseCase(presenter, service) + await usecase.execute(id) + return presenter.result + + +@router.get( + '', + responses={ + 200: {'description': 'Successful Response'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def list_categories( + page_params: PageParams = Depends(), + container: AppContainer = Depends(AppContainer), + _: Session = Depends(check_access_token), +) -> CategoryListPagedResponse: + service = Categorieservice(container.category_repository) + presenter = CategoryPagedListPresenter(page_params) + usecase = GetCategoriesUseCase(presenter, service) + await usecase.execute(page_params) + return presenter.result + + +@router.post( + '', + response_class=JSONResponse, + response_model=CreateCategoryResponseDTO, + status_code=201, + responses={ + 201: {'description': 'Item created'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def create_category( + request_data: CreateCategoryRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> Category: + in_dto = CreateCategoryInDTO.model_validate(request_data.model_dump()) + service = Categorieservice(container.category_repository) + presenter = CategoryPresenter() + usecase = CreateCategoryUseCase(presenter, service) + await usecase.execute(in_dto) + return presenter.result + + +@router.put( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_category( + id: int, + request_data: UpdateCategoryRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> CategoryResponse: + in_data = request_data.model_dump() + in_dto = UpdatePartialCategoryInDTO.model_validate(in_data) + service = Categorieservice(container.category_repository) + presenter = CategoryPresenter() + usecase = UpdateCategoryUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result + + +@router.patch( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_category_partially( + id: int, + request_data: UpdatePartialCategoryRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> CategoryResponse: + in_data = request_data.model_dump(exclude_unset=True) + if not in_data.keys(): + raise EmptyPayloadExceptionError() + in_dto = UpdatePartialCategoryInDTO.model_validate(in_data) + + service = Categorieservice(container.category_repository) + presenter = CategoryPresenter() + usecase = UpdateCategoryUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result diff --git a/src/northwind/adapters/api/http/presenters/category.py b/src/northwind/adapters/api/http/presenters/category.py new file mode 100644 index 0000000..6f717d3 --- /dev/null +++ b/src/northwind/adapters/api/http/presenters/category.py @@ -0,0 +1,29 @@ +from typing import List + +from northwind.adapters.api.http.schemas.category import CategoryListPagedResponse, CategoryResponse +from northwind.domain.entities.category import Category +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class CategoryPresenter(AbstractPresenter[Category, CategoryResponse]): + result: CategoryResponse + + async def present(self, data: Category) -> None: + self.result = CategoryResponse.model_validate(data, from_attributes=True) + + +class CategoryPagedListPresenter(AbstractPresenter[List[Category], List[CategoryResponse]]): + result: CategoryListPagedResponse + + def __init__(self, page_params: PageParams) -> None: + self.page_params = page_params + + async def present(self, data: List[Category]) -> None: + list_items = [CategoryResponse.model_validate(item, from_attributes=True) for item in data] + self.result = CategoryListPagedResponse( + results=list_items, + total=len(list_items), + page=self.page_params.page, + size=self.page_params.size, + ) diff --git a/src/northwind/adapters/api/http/presenters/product.py b/src/northwind/adapters/api/http/presenters/product.py new file mode 100644 index 0000000..c62bbd5 --- /dev/null +++ b/src/northwind/adapters/api/http/presenters/product.py @@ -0,0 +1,29 @@ +from typing import List + +from northwind.adapters.api.http.schemas.product import ProductListPagedResponse, ProductResponse +from northwind.domain.entities.product import Product +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class ProductPresenter(AbstractPresenter[Product, ProductResponse]): + result: ProductResponse + + async def present(self, data: Product) -> None: + self.result = ProductResponse.model_validate(data, from_attributes=True) + + +class ProductPagedListPresenter(AbstractPresenter[List[Product], List[ProductResponse]]): + result: ProductListPagedResponse + + def __init__(self, page_params: PageParams) -> None: + self.page_params = page_params + + async def present(self, data: List[Product]) -> None: + list_items = [ProductResponse.model_validate(item, from_attributes=True) for item in data] + self.result = ProductListPagedResponse( + results=list_items, + total=len(list_items), + page=self.page_params.page, + size=self.page_params.size, + ) diff --git a/src/northwind/adapters/api/http/presenters/supplier.py b/src/northwind/adapters/api/http/presenters/supplier.py new file mode 100644 index 0000000..0032e58 --- /dev/null +++ b/src/northwind/adapters/api/http/presenters/supplier.py @@ -0,0 +1,29 @@ +from typing import List + +from northwind.adapters.api.http.schemas.supplier import SupplierListPagedResponse, SupplierResponse +from northwind.domain.entities.supplier import Supplier +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class SupplierPresenter(AbstractPresenter[Supplier, SupplierResponse]): + result: SupplierResponse + + async def present(self, data: Supplier) -> None: + self.result = SupplierResponse.model_validate(data, from_attributes=True) + + +class SupplierPagedListPresenter(AbstractPresenter[List[Supplier], List[SupplierResponse]]): + result: SupplierListPagedResponse + + def __init__(self, page_params: PageParams) -> None: + self.page_params = page_params + + async def present(self, data: List[Supplier]) -> None: + list_items = [SupplierResponse.model_validate(item, from_attributes=True) for item in data] + self.result = SupplierListPagedResponse( + results=list_items, + total=len(list_items), + page=self.page_params.page, + size=self.page_params.size, + ) diff --git a/src/northwind/adapters/api/http/product.py b/src/northwind/adapters/api/http/product.py new file mode 100644 index 0000000..6a52280 --- /dev/null +++ b/src/northwind/adapters/api/http/product.py @@ -0,0 +1,142 @@ +from fastapi import Depends +from fastapi.responses import JSONResponse +from fastapi.routing import APIRouter + +from app.app_container import AppContainer +from app.exceptions import EmptyPayloadExceptionError +from app.schemas import Session +from app.session_deps import is_admin_session +from northwind.adapters.api.http.presenters.product import ( + ProductPagedListPresenter, + ProductPresenter, +) +from northwind.adapters.api.http.schemas.product import ( + CreateProductRequestDTO, + CreateProductResponseDTO, + ProductListPagedResponse, + ProductResponse, + UpdatePartialProductRequestDTO, + UpdateProductRequestDTO, +) +from northwind.domain.entities.product import Product +from northwind.domain.services.product import ProductService +from northwind.domain.use_cases.product import ( + CreateProductUseCase, + GetProductsUseCase, + GetProductUseCase, + UpdateProductUseCase, +) +from northwind.schemas.product import CreateProductInDTO, UpdatePartialProductInDTO +from shared.api.schemas.page import PageParams + + +router = APIRouter(prefix='/product') + + +@router.get( + '/{uuid}', + responses={ + 200: {'description': 'Successful Response'}, + 404: {'description': 'Product not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def get_product( + id: int, + container: AppContainer = Depends(AppContainer), +) -> ProductResponse: + service = ProductService(container.product_repository) + presenter = ProductPresenter() + usecase = GetProductUseCase(presenter, service) + await usecase.execute(id) + return presenter.result + + +@router.get( + '', + responses={ + 200: {'description': 'Successful Response'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def list_products( + page_params: PageParams = Depends(), + container: AppContainer = Depends(AppContainer), +) -> ProductListPagedResponse: + service = ProductService(container.product_repository) + presenter = ProductPagedListPresenter(page_params) + usecase = GetProductsUseCase(presenter, service) + await usecase.execute(page_params) + return presenter.result + + +@router.post( + '', + response_class=JSONResponse, + response_model=CreateProductResponseDTO, + status_code=201, + responses={ + 201: {'description': 'Item created'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def create_product( + request_data: CreateProductRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> Product: + in_dto = CreateProductInDTO.model_validate(request_data.model_dump()) + service = ProductService(container.product_repository) + presenter = ProductPresenter() + usecase = CreateProductUseCase(presenter, service) + await usecase.execute(in_dto) + return presenter.result + + +@router.put( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_product( + id: int, + request_data: UpdateProductRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> ProductResponse: + in_data = request_data.model_dump() + in_dto = UpdatePartialProductInDTO.model_validate(in_data) + service = ProductService(container.product_repository) + presenter = ProductPresenter() + usecase = UpdateProductUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result + + +@router.patch( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_product_partially( + id: int, + request_data: UpdatePartialProductRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> ProductResponse: + in_data = request_data.model_dump(exclude_unset=True) + if not in_data.keys(): + raise EmptyPayloadExceptionError() + in_dto = UpdatePartialProductInDTO.model_validate(in_data) + + service = ProductService(container.product_repository) + presenter = ProductPresenter() + usecase = UpdateProductUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result diff --git a/src/northwind/adapters/api/http/router.py b/src/northwind/adapters/api/http/router.py new file mode 100644 index 0000000..5084820 --- /dev/null +++ b/src/northwind/adapters/api/http/router.py @@ -0,0 +1,11 @@ +from fastapi.routing import APIRouter + +from .category import router as category_router +from .product import router as product_router +from .supplier import router as supplier_router + + +router = APIRouter(prefix='/northwind', tags=['northwind']) +router.include_router(category_router) +router.include_router(supplier_router) +router.include_router(product_router) diff --git a/src/northwind/adapters/api/http/schemas/__init__.py b/src/northwind/adapters/api/http/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/api/http/schemas/category.py b/src/northwind/adapters/api/http/schemas/category.py new file mode 100644 index 0000000..df384da --- /dev/null +++ b/src/northwind/adapters/api/http/schemas/category.py @@ -0,0 +1,32 @@ +from typing import Optional + +from pydantic import BaseModel, ConfigDict + +from northwind.schemas.category import CreateCategoryInDTO, UpdateCategoryInDTO, UpdatePartialCategoryInDTO +from shared.api.schemas.page import PagedResponseSchema + + +class CategoryResponse(BaseModel): + id: int + name: str + description: Optional[str] = None + + +class CategoryListPagedResponse(PagedResponseSchema[CategoryResponse]): + pass + + +class CreateCategoryRequestDTO(CreateCategoryInDTO): + model_config = ConfigDict(extra='ignore') + + +class CreateCategoryResponseDTO(CategoryResponse): + pass + + +class UpdateCategoryRequestDTO(UpdateCategoryInDTO): + model_config = ConfigDict(extra='ignore') + + +class UpdatePartialCategoryRequestDTO(UpdatePartialCategoryInDTO): + model_config = ConfigDict(extra='ignore') diff --git a/src/northwind/adapters/api/http/schemas/product.py b/src/northwind/adapters/api/http/schemas/product.py new file mode 100644 index 0000000..8bad61d --- /dev/null +++ b/src/northwind/adapters/api/http/schemas/product.py @@ -0,0 +1,49 @@ +from typing import Optional + +from pydantic import BaseModel, ConfigDict + +from northwind.schemas.product import CreateProductInDTO, UpdatePartialProductInDTO, UpdateProductInDTO +from shared.api.schemas.page import PagedResponseSchema + + +class Supplier(BaseModel): + id: int + company_name: str + + +class Category(BaseModel): + id: int + name: str + + +class ProductResponse(BaseModel): + id: int + name: str + supplier: Supplier + category: Category + quantity_per_unit: str + unit_price: float + units_in_stock: int + units_on_order: int + reorder_level: int + discontinued: Optional[int] = None + + +class ProductListPagedResponse(PagedResponseSchema[ProductResponse]): + pass + + +class CreateProductRequestDTO(CreateProductInDTO): + model_config = ConfigDict(extra='ignore') + + +class CreateProductResponseDTO(ProductResponse): + pass + + +class UpdateProductRequestDTO(UpdateProductInDTO): + model_config = ConfigDict(extra='ignore') + + +class UpdatePartialProductRequestDTO(UpdatePartialProductInDTO): + model_config = ConfigDict(extra='ignore') diff --git a/src/northwind/adapters/api/http/schemas/supplier.py b/src/northwind/adapters/api/http/schemas/supplier.py new file mode 100644 index 0000000..665fc22 --- /dev/null +++ b/src/northwind/adapters/api/http/schemas/supplier.py @@ -0,0 +1,41 @@ +from typing import Optional + +from pydantic import BaseModel, ConfigDict + +from northwind.schemas.supplier import CreateSupplierInDTO, UpdatePartialSupplierInDTO, UpdateSupplierInDTO +from shared.api.schemas.page import PagedResponseSchema + + +class SupplierResponse(BaseModel): + id: int + company_name: str + contact_name: Optional[str] = None + contact_title: Optional[str] = None + address: Optional[str] = None + city: Optional[str] = None + region: Optional[str] = None + postal_code: Optional[str] = None + country: Optional[str] = None + phone: Optional[str] = None + fax: Optional[str] = None + homepage: Optional[str] = None + + +class SupplierListPagedResponse(PagedResponseSchema[SupplierResponse]): + pass + + +class CreateSupplierRequestDTO(CreateSupplierInDTO): + model_config = ConfigDict(extra='ignore') + + +class CreateSupplierResponseDTO(SupplierResponse): + pass + + +class UpdateSupplierRequestDTO(UpdateSupplierInDTO): + model_config = ConfigDict(extra='ignore') + + +class UpdatePartialSupplierRequestDTO(UpdatePartialSupplierInDTO): + model_config = ConfigDict(extra='ignore') diff --git a/src/northwind/adapters/api/http/supplier.py b/src/northwind/adapters/api/http/supplier.py new file mode 100644 index 0000000..f0612a6 --- /dev/null +++ b/src/northwind/adapters/api/http/supplier.py @@ -0,0 +1,141 @@ +from fastapi import Depends +from fastapi.responses import JSONResponse +from fastapi.routing import APIRouter + +from app.app_container import AppContainer +from app.exceptions import EmptyPayloadExceptionError +from app.schemas import Session +from app.session_deps import check_access_token, is_admin_session +from northwind.adapters.api.http.presenters.supplier import SupplierPagedListPresenter, SupplierPresenter +from northwind.adapters.api.http.schemas.supplier import ( + CreateSupplierRequestDTO, + CreateSupplierResponseDTO, + SupplierListPagedResponse, + SupplierResponse, + UpdatePartialSupplierRequestDTO, + UpdateSupplierRequestDTO, +) +from northwind.domain.entities.supplier import Supplier +from northwind.domain.services.supplier import SupplierService +from northwind.domain.use_cases.supplier import ( + CreateSupplierUseCase, + GetSuppliersUseCase, + GetSupplierUseCase, + UpdateSupplierUseCase, +) +from northwind.schemas.supplier import CreateSupplierInDTO, UpdatePartialSupplierInDTO +from shared.api.schemas.page import PageParams + + +router = APIRouter(prefix='/supplier') + + +@router.get( + '/{id}', + responses={ + 200: {'description': 'Successful Response'}, + 404: {'description': 'Supplier not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def get_supplier( + id: int, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(check_access_token), +) -> SupplierResponse: + service = SupplierService(container.supplier_repository) + presenter = SupplierPresenter() + usecase = GetSupplierUseCase(presenter, service) + await usecase.execute(id) + return presenter.result + + +@router.get( + '', + responses={ + 200: {'description': 'Successful Response'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def list_categories( + page_params: PageParams = Depends(), + container: AppContainer = Depends(AppContainer), + _: Session = Depends(check_access_token), +) -> SupplierListPagedResponse: + service = SupplierService(container.supplier_repository) + presenter = SupplierPagedListPresenter(page_params) + usecase = GetSuppliersUseCase(presenter, service) + await usecase.execute(page_params) + return presenter.result + + +@router.post( + '', + response_class=JSONResponse, + response_model=CreateSupplierResponseDTO, + status_code=201, + responses={ + 201: {'description': 'Item created'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def create_supplier( + request_data: CreateSupplierRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> Supplier: + in_dto = CreateSupplierInDTO.model_validate(request_data.model_dump()) + service = SupplierService(container.supplier_repository) + presenter = SupplierPresenter() + usecase = CreateSupplierUseCase(presenter, service) + await usecase.execute(in_dto) + return presenter.result + + +@router.put( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_supplier( + id: int, + request_data: UpdateSupplierRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> SupplierResponse: + in_data = request_data.model_dump() + in_dto = UpdatePartialSupplierInDTO.model_validate(in_data) + service = SupplierService(container.supplier_repository) + presenter = SupplierPresenter() + usecase = UpdateSupplierUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result + + +@router.patch( + '/{id}', + responses={ + 200: {'description': 'Item updated'}, + 404: {'description': 'Item not found'}, + 422: {'description': 'Unprocessable Entity'}, + }, +) +async def update_supplier_partially( + id: int, + request_data: UpdatePartialSupplierRequestDTO, + container: AppContainer = Depends(AppContainer), + _: Session = Depends(is_admin_session), +) -> SupplierResponse: + in_data = request_data.model_dump(exclude_unset=True) + if not in_data.keys(): + raise EmptyPayloadExceptionError() + in_dto = UpdatePartialSupplierInDTO.model_validate(in_data) + + service = SupplierService(container.supplier_repository) + presenter = SupplierPresenter() + usecase = UpdateSupplierUseCase(presenter, service) + await usecase.execute(id, in_dto) + return presenter.result diff --git a/src/northwind/adapters/spi/__init__.py b/src/northwind/adapters/spi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/spi/repositories/__init__.py b/src/northwind/adapters/spi/repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/adapters/spi/repositories/category.py b/src/northwind/adapters/spi/repositories/category.py new file mode 100644 index 0000000..8e3c554 --- /dev/null +++ b/src/northwind/adapters/spi/repositories/category.py @@ -0,0 +1,26 @@ +from sqlalchemy.orm import registry + +from northwind.domain.entities.category import Category +from northwind.domain.ports.repositories.category import AbstractCategoryRepository +from northwind.infra.database.sqlalchemy.models import categories +from northwind.schemas.category import CreateCategoryInDTO, UpdatePartialCategoryInDTO +from shared.repository.sqlalchemy import SqlAlchemyRepository + + +mapper_registry = registry() +mapper_registry.map_imperatively( + Category, + categories, + properties={ + 'id': categories.c.category_id, + 'name': categories.c.category_name, + 'description': categories.c.description, + }, +) + + +class CategoryRepository( + SqlAlchemyRepository[Category, CreateCategoryInDTO, UpdatePartialCategoryInDTO], + AbstractCategoryRepository, +): + pass diff --git a/src/northwind/adapters/spi/repositories/product.py b/src/northwind/adapters/spi/repositories/product.py new file mode 100644 index 0000000..17aec11 --- /dev/null +++ b/src/northwind/adapters/spi/repositories/product.py @@ -0,0 +1,78 @@ +from typing import AsyncContextManager, Callable, Union + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import registry, relationship + +from northwind.domain.entities.category import Category +from northwind.domain.entities.product import Product +from northwind.domain.entities.supplier import Supplier +from northwind.domain.ports.repositories.category import AbstractCategoryRepository +from northwind.domain.ports.repositories.product import AbstractProductRepository +from northwind.domain.ports.repositories.supplier import AbstractSupplierRepository +from northwind.infra.database.sqlalchemy.models import products +from northwind.schemas.product import CreateProductInDTO, UpdatePartialProductInDTO +from shared.repository.sqlalchemy import SqlAlchemyRepository + + +mapper_registry = registry() +mapper_registry.map_imperatively( + Product, + products, + properties={ + 'id': products.c.product_id, + 'name': products.c.product_name, + 'category': relationship(Category, lazy='joined'), + 'supplier': relationship(Supplier, lazy='joined'), + }, +) + +AsyncSessionCtxT = Callable[[], AsyncContextManager[AsyncSession]] + + +class ProductRepository( + SqlAlchemyRepository[Product, CreateProductInDTO, UpdatePartialProductInDTO], + AbstractProductRepository, +): + def __init__( + self, + session: AsyncSessionCtxT, + category_repository: AbstractCategoryRepository, + supplier_repository: AbstractSupplierRepository, + ) -> None: + super().__init__(session) + self.category_repository = category_repository + self.supplier_repository = supplier_repository + + async def create(self, data: CreateProductInDTO) -> Product: + async with self.session_factory() as session: + in_data = data.model_dump(exclude={'category_id', 'supplier_id'}) + category = await self.category_repository.get_by_id(data.category_id) + supplier = await self.supplier_repository.get_by_id(data.supplier_id) + instance = self.entity(**in_data, category=category, supplier=supplier) + session.add(instance) + return await self.get_by_id(instance.id or -1) + + async def update(self, uuid: Union[str, int], data: UpdatePartialProductInDTO) -> Product: + object_id = int(uuid) if isinstance(uuid, str) else uuid + to_update = data.model_dump(exclude_unset=True) + if not to_update: + raise ValueError('No data to update') + + async with self.session_factory() as session: + instance = await self.get_by_id(object_id) + + for key, value in to_update.items(): + if key == 'category_id' and value != instance.category.id: + category = await self.category_repository.get_by_id(value) + instance.category = category + continue + if key == 'supplier_id' and value != instance.supplier.id: + supplier = await self.supplier_repository.get_by_id(value) + instance.supplier = supplier + continue + + setattr(instance, key, value) + + session.add(instance) + + return await self.get_by_id(object_id) diff --git a/src/northwind/adapters/spi/repositories/supplier.py b/src/northwind/adapters/spi/repositories/supplier.py new file mode 100644 index 0000000..94b77b0 --- /dev/null +++ b/src/northwind/adapters/spi/repositories/supplier.py @@ -0,0 +1,22 @@ +from sqlalchemy.orm import registry + +from northwind.domain.entities.supplier import Supplier +from northwind.domain.ports.repositories.supplier import AbstractSupplierRepository +from northwind.infra.database.sqlalchemy.models import suppliers +from northwind.schemas.supplier import CreateSupplierInDTO, UpdatePartialSupplierInDTO +from shared.repository.sqlalchemy import SqlAlchemyRepository + + +mapper_registry = registry() +mapper_registry.map_imperatively( + Supplier, + suppliers, + properties={'id': suppliers.c.supplier_id}, +) + + +class SupplierRepository( + SqlAlchemyRepository[Supplier, CreateSupplierInDTO, UpdatePartialSupplierInDTO], + AbstractSupplierRepository, +): + pass diff --git a/src/northwind/di/__init__.py b/src/northwind/di/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/di/mixins/__init__.py b/src/northwind/di/mixins/__init__.py new file mode 100644 index 0000000..f4e88b4 --- /dev/null +++ b/src/northwind/di/mixins/__init__.py @@ -0,0 +1,7 @@ +from northwind.di.mixins.category import CategoryContainerMixin +from northwind.di.mixins.product import ProductContainerMixin +from northwind.di.mixins.supplier import SupplierContainerMixin + + +class NorthwindContainerMixin(CategoryContainerMixin, ProductContainerMixin, SupplierContainerMixin): + pass diff --git a/src/northwind/di/mixins/category.py b/src/northwind/di/mixins/category.py new file mode 100644 index 0000000..3eb13e7 --- /dev/null +++ b/src/northwind/di/mixins/category.py @@ -0,0 +1,11 @@ +from infra.database.sqlalchemy.session import AbstractDatabase +from northwind.adapters.spi.repositories.category import CategoryRepository +from northwind.domain.ports.repositories.category import AbstractCategoryRepository + + +class CategoryContainerMixin: + db: AbstractDatabase + category_repository: AbstractCategoryRepository + + def _get_category_repository(self) -> AbstractCategoryRepository: + return CategoryRepository(self.db.session) diff --git a/src/northwind/di/mixins/product.py b/src/northwind/di/mixins/product.py new file mode 100644 index 0000000..724e36d --- /dev/null +++ b/src/northwind/di/mixins/product.py @@ -0,0 +1,15 @@ +from infra.database.sqlalchemy.session import AbstractDatabase +from northwind.adapters.spi.repositories.product import ProductRepository +from northwind.domain.ports.repositories.category import AbstractCategoryRepository +from northwind.domain.ports.repositories.product import AbstractProductRepository +from northwind.domain.ports.repositories.supplier import AbstractSupplierRepository + + +class ProductContainerMixin: + db: AbstractDatabase + product_repository: AbstractProductRepository + category_repository: AbstractCategoryRepository + supplier_repository: AbstractSupplierRepository + + def _get_product_repository(self) -> AbstractProductRepository: + return ProductRepository(self.db.session, self.category_repository, self.supplier_repository) diff --git a/src/northwind/di/mixins/supplier.py b/src/northwind/di/mixins/supplier.py new file mode 100644 index 0000000..faadb77 --- /dev/null +++ b/src/northwind/di/mixins/supplier.py @@ -0,0 +1,11 @@ +from infra.database.sqlalchemy.session import AbstractDatabase +from northwind.adapters.spi.repositories.supplier import SupplierRepository +from northwind.domain.ports.repositories.supplier import AbstractSupplierRepository + + +class SupplierContainerMixin: + db: AbstractDatabase + supplier_repository: AbstractSupplierRepository + + def _get_supplier_repository(self) -> AbstractSupplierRepository: + return SupplierRepository(self.db.session) diff --git a/src/northwind/domain/__init__.py b/src/northwind/domain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/entities/__init__.py b/src/northwind/domain/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/entities/category.py b/src/northwind/domain/entities/category.py new file mode 100644 index 0000000..7cccb17 --- /dev/null +++ b/src/northwind/domain/entities/category.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from typing import Optional + +from northwind.domain.entities.value_objects import CategoryId + + +@dataclass(kw_only=True) +class Category: + id: Optional[CategoryId] = None + name: str + description: Optional[str] = None diff --git a/src/northwind/domain/entities/product.py b/src/northwind/domain/entities/product.py new file mode 100644 index 0000000..28c12e8 --- /dev/null +++ b/src/northwind/domain/entities/product.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import Optional + +from northwind.domain.entities.category import Category +from northwind.domain.entities.supplier import Supplier +from northwind.domain.entities.value_objects import ProductId + + +@dataclass(kw_only=True) +class Product: + id: Optional[ProductId] = None + name: str + supplier: Supplier + category: Category + quantity_per_unit: str + unit_price: float + units_in_stock: int + units_on_order: int + reorder_level: int + discontinued: Optional[int] = None diff --git a/src/northwind/domain/entities/supplier.py b/src/northwind/domain/entities/supplier.py new file mode 100644 index 0000000..426ab69 --- /dev/null +++ b/src/northwind/domain/entities/supplier.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import Optional + +from northwind.domain.entities.value_objects import SupplierId + + +@dataclass(kw_only=True) +class Supplier: + id: Optional[SupplierId] = None + company_name: str + contact_name: Optional[str] = None + contact_title: Optional[str] = None + address: Optional[str] = None + city: Optional[str] = None + region: Optional[str] = None + postal_code: Optional[str] = None + country: Optional[str] = None + phone: Optional[str] = None + fax: Optional[str] = None + homepage: Optional[str] = None diff --git a/src/northwind/domain/entities/value_objects.py b/src/northwind/domain/entities/value_objects.py new file mode 100644 index 0000000..5787a4f --- /dev/null +++ b/src/northwind/domain/entities/value_objects.py @@ -0,0 +1,3 @@ +CategoryId = int +ProductId = int +SupplierId = int diff --git a/src/northwind/domain/ports/__init__.py b/src/northwind/domain/ports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/ports/repositories/__init__.py b/src/northwind/domain/ports/repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/ports/repositories/category.py b/src/northwind/domain/ports/repositories/category.py new file mode 100644 index 0000000..94d72b8 --- /dev/null +++ b/src/northwind/domain/ports/repositories/category.py @@ -0,0 +1,7 @@ +from northwind.domain.entities.category import Category +from northwind.schemas.category import CreateCategoryInDTO, UpdatePartialCategoryInDTO +from shared.repository.ports.generic import AbstractRepository + + +class AbstractCategoryRepository(AbstractRepository[Category, CreateCategoryInDTO, UpdatePartialCategoryInDTO]): + key = 'id' diff --git a/src/northwind/domain/ports/repositories/product.py b/src/northwind/domain/ports/repositories/product.py new file mode 100644 index 0000000..44f938f --- /dev/null +++ b/src/northwind/domain/ports/repositories/product.py @@ -0,0 +1,7 @@ +from northwind.domain.entities.product import Product +from northwind.schemas.product import CreateProductInDTO, UpdatePartialProductInDTO +from shared.repository.ports.generic import AbstractRepository + + +class AbstractProductRepository(AbstractRepository[Product, CreateProductInDTO, UpdatePartialProductInDTO]): + key = 'id' diff --git a/src/northwind/domain/ports/repositories/supplier.py b/src/northwind/domain/ports/repositories/supplier.py new file mode 100644 index 0000000..92b2674 --- /dev/null +++ b/src/northwind/domain/ports/repositories/supplier.py @@ -0,0 +1,7 @@ +from northwind.domain.entities.supplier import Supplier +from northwind.schemas.supplier import CreateSupplierInDTO, UpdatePartialSupplierInDTO +from shared.repository.ports.generic import AbstractRepository + + +class AbstractSupplierRepository(AbstractRepository[Supplier, CreateSupplierInDTO, UpdatePartialSupplierInDTO]): + key = 'id' diff --git a/src/northwind/domain/services/__init__.py b/src/northwind/domain/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/services/category.py b/src/northwind/domain/services/category.py new file mode 100644 index 0000000..15332ad --- /dev/null +++ b/src/northwind/domain/services/category.py @@ -0,0 +1,27 @@ +from typing import List, Optional + +from northwind.domain.entities.category import Category +from northwind.domain.ports.repositories.category import AbstractCategoryRepository +from northwind.schemas.category import CreateCategoryInDTO, UpdateCategoryInDTO +from shared.api.schemas.page import PageParams + + +class Categorieservice: + def __init__(self, category_repository: AbstractCategoryRepository): + self.category_repository = category_repository + + async def get_category_by_id(self, id: int) -> Category: + return await self.category_repository.get_by_id(id) + + async def get_categories(self, *, page_params: Optional[PageParams] = None) -> List[Category]: + if not page_params: + ret = await self.category_repository.get_all() + else: + ret = await self.category_repository.get_xpage(page_params.page, page_params.size) + return ret + + async def create_category(self, in_dto: CreateCategoryInDTO) -> Category: + return await self.category_repository.create(in_dto) + + async def update_category(self, id: int, in_dto: UpdateCategoryInDTO) -> Category: + return await self.category_repository.update(id, in_dto) diff --git a/src/northwind/domain/services/product.py b/src/northwind/domain/services/product.py new file mode 100644 index 0000000..0372190 --- /dev/null +++ b/src/northwind/domain/services/product.py @@ -0,0 +1,27 @@ +from typing import List, Optional + +from northwind.domain.entities.product import Product +from northwind.domain.ports.repositories.product import AbstractProductRepository +from northwind.schemas.product import CreateProductInDTO, UpdateProductInDTO +from shared.api.schemas.page import PageParams + + +class ProductService: + def __init__(self, product_repository: AbstractProductRepository): + self.product_repository = product_repository + + async def get_product_by_id(self, id: int) -> Product: + return await self.product_repository.get_by_id(id) + + async def get_products(self, *, page_params: Optional[PageParams] = None) -> List[Product]: + if not page_params: + ret = await self.product_repository.get_all() + else: + ret = await self.product_repository.get_xpage(page_params.page, page_params.size) + return ret + + async def create_product(self, in_dto: CreateProductInDTO) -> Product: + return await self.product_repository.create(in_dto) + + async def update_product(self, id: int, in_dto: UpdateProductInDTO) -> Product: + return await self.product_repository.update(id, in_dto) diff --git a/src/northwind/domain/services/supplier.py b/src/northwind/domain/services/supplier.py new file mode 100644 index 0000000..1e7e6db --- /dev/null +++ b/src/northwind/domain/services/supplier.py @@ -0,0 +1,27 @@ +from typing import List, Optional + +from northwind.domain.entities.supplier import Supplier +from northwind.domain.ports.repositories.supplier import AbstractSupplierRepository +from northwind.schemas.supplier import CreateSupplierInDTO, UpdateSupplierInDTO +from shared.api.schemas.page import PageParams + + +class SupplierService: + def __init__(self, supplier_repository: AbstractSupplierRepository): + self.supplier_repository = supplier_repository + + async def get_supplier_by_id(self, id: int) -> Supplier: + return await self.supplier_repository.get_by_id(id) + + async def get_suppliers(self, *, page_params: Optional[PageParams] = None) -> List[Supplier]: + if not page_params: + ret = await self.supplier_repository.get_all() + else: + ret = await self.supplier_repository.get_xpage(page_params.page, page_params.size) + return ret + + async def create_supplier(self, in_dto: CreateSupplierInDTO) -> Supplier: + return await self.supplier_repository.create(in_dto) + + async def update_supplier(self, id: int, in_dto: UpdateSupplierInDTO) -> Supplier: + return await self.supplier_repository.update(id, in_dto) diff --git a/src/northwind/domain/use_cases/__init__.py b/src/northwind/domain/use_cases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/domain/use_cases/category.py b/src/northwind/domain/use_cases/category.py new file mode 100644 index 0000000..20209d0 --- /dev/null +++ b/src/northwind/domain/use_cases/category.py @@ -0,0 +1,40 @@ +from northwind.domain.services.category import Categorieservice +from northwind.schemas.category import CreateCategoryInDTO +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class GetCategoriesUseCase: + def __init__(self, presenter: AbstractPresenter, service: Categorieservice): + self.presenter = presenter + self.service = service + + async def execute(self, page_params: PageParams) -> None: + await self.presenter.present(await self.service.get_categories(page_params=page_params)) + + +class GetCategoryUseCase: + def __init__(self, presenter: AbstractPresenter, service: Categorieservice): + self.presenter = presenter + self.service = service + + async def execute(self, id: int) -> None: + await self.presenter.present(await self.service.get_category_by_id(id)) + + +class CreateCategoryUseCase: + def __init__(self, presenter: AbstractPresenter, service: Categorieservice): + self.presenter = presenter + self.service = service + + async def execute(self, in_dto: CreateCategoryInDTO) -> None: + await self.presenter.present(await self.service.create_category(in_dto)) + + +class UpdateCategoryUseCase: + def __init__(self, presenter: AbstractPresenter, service: Categorieservice): + self.presenter = presenter + self.service = service + + async def execute(self, id: int, in_data: CreateCategoryInDTO) -> None: + await self.presenter.present(await self.service.update_category(id, in_data)) diff --git a/src/northwind/domain/use_cases/product.py b/src/northwind/domain/use_cases/product.py new file mode 100644 index 0000000..960675b --- /dev/null +++ b/src/northwind/domain/use_cases/product.py @@ -0,0 +1,40 @@ +from northwind.domain.services.product import ProductService +from northwind.schemas.product import CreateProductInDTO +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class GetProductsUseCase: + def __init__(self, presenter: AbstractPresenter, service: ProductService): + self.presenter = presenter + self.service = service + + async def execute(self, page_params: PageParams) -> None: + await self.presenter.present(await self.service.get_products(page_params=page_params)) + + +class GetProductUseCase: + def __init__(self, presenter: AbstractPresenter, service: ProductService): + self.presenter = presenter + self.service = service + + async def execute(self, id: int) -> None: + await self.presenter.present(await self.service.get_product_by_id(id)) + + +class CreateProductUseCase: + def __init__(self, presenter: AbstractPresenter, service: ProductService): + self.presenter = presenter + self.service = service + + async def execute(self, in_dto: CreateProductInDTO) -> None: + await self.presenter.present(await self.service.create_product(in_dto)) + + +class UpdateProductUseCase: + def __init__(self, presenter: AbstractPresenter, service: ProductService): + self.presenter = presenter + self.service = service + + async def execute(self, id: int, in_data: CreateProductInDTO) -> None: + await self.presenter.present(await self.service.update_product(id, in_data)) diff --git a/src/northwind/domain/use_cases/supplier.py b/src/northwind/domain/use_cases/supplier.py new file mode 100644 index 0000000..3ed4d86 --- /dev/null +++ b/src/northwind/domain/use_cases/supplier.py @@ -0,0 +1,40 @@ +from northwind.domain.services.supplier import SupplierService +from northwind.schemas.supplier import CreateSupplierInDTO +from shared.api.schemas.page import PageParams +from shared.presenter import AbstractPresenter + + +class GetSuppliersUseCase: + def __init__(self, presenter: AbstractPresenter, service: SupplierService): + self.presenter = presenter + self.service = service + + async def execute(self, page_params: PageParams) -> None: + await self.presenter.present(await self.service.get_suppliers(page_params=page_params)) + + +class GetSupplierUseCase: + def __init__(self, presenter: AbstractPresenter, service: SupplierService): + self.presenter = presenter + self.service = service + + async def execute(self, id: int) -> None: + await self.presenter.present(await self.service.get_supplier_by_id(id)) + + +class CreateSupplierUseCase: + def __init__(self, presenter: AbstractPresenter, service: SupplierService): + self.presenter = presenter + self.service = service + + async def execute(self, in_dto: CreateSupplierInDTO) -> None: + await self.presenter.present(await self.service.create_supplier(in_dto)) + + +class UpdateSupplierUseCase: + def __init__(self, presenter: AbstractPresenter, service: SupplierService): + self.presenter = presenter + self.service = service + + async def execute(self, id: int, in_data: CreateSupplierInDTO) -> None: + await self.presenter.present(await self.service.update_supplier(id, in_data)) diff --git a/src/northwind/infra/__init__.py b/src/northwind/infra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/infra/database/__init__.py b/src/northwind/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/infra/database/sqlalchemy/__init__.py b/src/northwind/infra/database/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/northwind/infra/database/sqlalchemy/models.py b/src/northwind/infra/database/sqlalchemy/models.py new file mode 100644 index 0000000..872ab7e --- /dev/null +++ b/src/northwind/infra/database/sqlalchemy/models.py @@ -0,0 +1,209 @@ +from sqlalchemy import ( + Column, + Date, + Float, + ForeignKeyConstraint, + Integer, + LargeBinary, + PrimaryKeyConstraint, + SmallInteger, + String, + Table, + Text, +) + +from infra.database.sqlalchemy.sqlalchemy import metadata + + +categories = Table( + 'categories', + metadata, + Column('category_id', Integer, primary_key=True), + Column('category_name', String(15), nullable=False), + Column('description', Text), + Column('picture', LargeBinary), + PrimaryKeyConstraint('category_id', name='categories_pkey'), +) + +customer_demographics = Table( + 'customer_demographics', + metadata, + Column('customer_type_id', String(5), primary_key=True), + Column('customer_desc', Text), + PrimaryKeyConstraint('customer_type_id', name='pk_customer_demographics'), +) + +customers = Table( + 'customers', + metadata, + Column('customer_id', String(5), primary_key=True), + Column('company_name', String(40), nullable=False), + Column('contact_name', String(30)), + Column('contact_title', String(30)), + Column('address', String(60)), + Column('city', String(15)), + Column('region', String(15)), + Column('postal_code', String(10)), + Column('country', String(15)), + Column('phone', String(24)), + Column('fax', String(24)), + PrimaryKeyConstraint('customer_id', name='pk_customers'), +) + +employees = Table( + 'employees', + metadata, + Column('employee_id', Integer, primary_key=True), + Column('last_name', String(20), nullable=False), + Column('first_name', String(10), nullable=False), + Column('title', String(30)), + Column('title_of_courtesy', String(25)), + Column('birth_date', Date), + Column('hire_date', Date), + Column('address', String(60)), + Column('city', String(15)), + Column('region', String(15)), + Column('postal_code', String(10)), + Column('country', String(15)), + Column('home_phone', String(24)), + Column('extension', String(4)), + Column('photo', LargeBinary), + Column('notes', Text), + Column('reports_to', SmallInteger), + Column('photo_path', String(255)), + ForeignKeyConstraint(['reports_to'], ['employees.employee_id'], name='fk_employees_employees'), + PrimaryKeyConstraint('employee_id', name='employees_pkey'), +) + +region = Table( + 'region', + metadata, + Column('region_id', Integer, primary_key=True), + Column('region_description', String(60), nullable=False), + PrimaryKeyConstraint('region_id', name='region_pkey'), +) + +shippers = Table( + 'shippers', + metadata, + Column('shipper_id', Integer, primary_key=True), + Column('company_name', String(40), nullable=False), + Column('phone', String(24)), + PrimaryKeyConstraint('shipper_id', name='shippers_pkey'), +) + +suppliers = Table( + 'suppliers', + metadata, + Column('supplier_id', Integer, primary_key=True), + Column('company_name', String(40), nullable=False), + Column('contact_name', String(30)), + Column('contact_title', String(30)), + Column('address', String(60)), + Column('city', String(15)), + Column('region', String(15)), + Column('postal_code', String(10)), + Column('country', String(15)), + Column('phone', String(24)), + Column('fax', String(24)), + Column('homepage', Text), + PrimaryKeyConstraint('supplier_id', name='suppliers_pkey'), +) + +us_states = Table( + 'us_states', + metadata, + Column('state_id', Integer, primary_key=True), + Column('state_name', String(100)), + Column('state_abbr', String(2)), + Column('state_region', String(50)), + PrimaryKeyConstraint('state_id', name='us_states_pkey'), +) + +customer_customer_demo = Table( + 'customer_customer_demo', + metadata, + Column('customer_id', String(5), primary_key=True, nullable=False), + Column('customer_type_id', String(5), primary_key=True, nullable=False), + ForeignKeyConstraint(['customer_id'], ['customers.customer_id'], name='fk_customer_customer_demo_customers'), + ForeignKeyConstraint( + ['customer_type_id'], + ['customer_demographics.customer_type_id'], + name='fk_customer_customer_demo_customer_demographics', + ), + PrimaryKeyConstraint('customer_id', 'customer_type_id', name='pk_customer_customer_demo'), +) + +orders = Table( + 'orders', + metadata, + Column('order_id', Integer, primary_key=True), + Column('customer_id', String(5)), + Column('employee_id', SmallInteger), + Column('order_date', Date), + Column('required_date', Date), + Column('shipped_date', Date), + Column('ship_via', SmallInteger), + Column('freight', Float), + Column('ship_name', String(40)), + Column('ship_address', String(60)), + Column('ship_city', String(15)), + Column('ship_region', String(15)), + Column('ship_postal_code', String(10)), + Column('ship_country', String(15)), + ForeignKeyConstraint(['customer_id'], ['customers.customer_id'], name='fk_orders_customers'), + ForeignKeyConstraint(['employee_id'], ['employees.employee_id'], name='fk_orders_employees'), + ForeignKeyConstraint(['ship_via'], ['shippers.shipper_id'], name='fk_orders_shippers'), + PrimaryKeyConstraint('order_id', name='orders_pkey'), +) + +products = Table( + 'products', + metadata, + Column('product_id', Integer, primary_key=True), + Column('product_name', String(40), nullable=False), + Column('supplier_id', SmallInteger), + Column('category_id', SmallInteger), + Column('quantity_per_unit', String(20)), + Column('unit_price', Float), + Column('units_in_stock', SmallInteger), + Column('units_on_order', SmallInteger), + Column('reorder_level', SmallInteger), + Column('discontinued', Integer, nullable=False), + ForeignKeyConstraint(['category_id'], ['categories.category_id'], name='fk_products_categories'), + ForeignKeyConstraint(['supplier_id'], ['suppliers.supplier_id'], name='fk_products_suppliers'), + PrimaryKeyConstraint('product_id', name='products_pkey'), +) + +territories = Table( + 'territories', + metadata, + Column('territory_id', String(20), primary_key=True), + Column('territory_description', String(60), nullable=False), + Column('region_id', SmallInteger, nullable=False), + ForeignKeyConstraint(['region_id'], ['region.region_id'], name='fk_territories_region'), + PrimaryKeyConstraint('territory_id', name='pk_territories'), +) + +employee_territories = Table( + 'employee_territories', + metadata, + Column('employee_id', SmallInteger, primary_key=True, nullable=False), + Column('territory_id', String(20), primary_key=True, nullable=False), + ForeignKeyConstraint(['employee_id'], ['employees.employee_id'], name='fk_employee_territories_employees'), + ForeignKeyConstraint(['territory_id'], ['territories.territory_id'], name='fk_employee_territories_territories'), + PrimaryKeyConstraint('employee_id', 'territory_id', name='pk_employee_territories'), +) + +order_details = Table( + 'order_details', + metadata, + Column('order_id', SmallInteger, primary_key=True, nullable=False), + Column('product_id', SmallInteger, primary_key=True, nullable=False), + Column('unit_price', Float, nullable=False), + Column('quantity', SmallInteger, nullable=False), + Column('discount', Float, nullable=False), + ForeignKeyConstraint(['order_id'], ['orders.order_id'], name='fk_order_details_orders'), + ForeignKeyConstraint(['product_id'], ['products.product_id'], name='fk_order_details_products'), + PrimaryKeyConstraint('order_id', 'product_id', name='pk_order_details'), +) diff --git a/src/northwind/schemas/category.py b/src/northwind/schemas/category.py new file mode 100644 index 0000000..e9ca6b7 --- /dev/null +++ b/src/northwind/schemas/category.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class CreateCategoryInDTO(BaseModel): + name: str = Field(min_length=1, max_length=15) + description: Optional[str] = Field(default=None, max_length=1024) + + +class UpdateCategoryInDTO(CreateCategoryInDTO): + pass + + +class UpdatePartialCategoryInDTO(BaseModel): + name: Optional[str] = Field(default=None, min_length=1, max_length=15) + description: Optional[str] = Field(default=None, max_length=1024) diff --git a/src/northwind/schemas/product.py b/src/northwind/schemas/product.py new file mode 100644 index 0000000..a695117 --- /dev/null +++ b/src/northwind/schemas/product.py @@ -0,0 +1,31 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class CreateProductInDTO(BaseModel): + name: str = Field(min_length=1, max_length=40) + supplier_id: int + category_id: int + quantity_per_unit: str = Field(max_length=20) + unit_price: float + units_in_stock: int + units_on_order: int + reorder_level: int + discontinued: Optional[int] = None + + +class UpdateProductInDTO(CreateProductInDTO): + pass + + +class UpdatePartialProductInDTO(BaseModel): + name: Optional[str] = Field(default=None, min_length=1, max_length=40) + supplier_id: Optional[int] = None + category_id: Optional[int] = None + quantity_per_unit: str = Field(default=None, max_length=20) + unit_price: Optional[float] = None + units_in_stock: Optional[int] = None + units_on_order: Optional[int] = None + reorder_level: Optional[int] = None + discontinued: Optional[int] = None diff --git a/src/northwind/schemas/supplier.py b/src/northwind/schemas/supplier.py new file mode 100644 index 0000000..5e49292 --- /dev/null +++ b/src/northwind/schemas/supplier.py @@ -0,0 +1,37 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class CreateSupplierInDTO(BaseModel): + name: str = Field(min_length=1, max_length=15) + company_name: Optional[str] = Field(default=None, max_length=40) + contact_name: Optional[str] = Field(default=None, max_length=30) + contact_title: Optional[str] = Field(default=None, max_length=30) + address: Optional[str] = Field(default=None, max_length=60) + city: Optional[str] = Field(default=None, max_length=15) + region: Optional[str] = Field(default=None, max_length=15) + postal_code: Optional[str] = Field(default=None, max_length=10) + country: Optional[str] = Field(default=None, max_length=15) + phone: Optional[str] = Field(default=None, max_length=24) + fax: Optional[str] = Field(default=None, max_length=24) + homepage: Optional[str] = Field(default=None, max_length=1024) + + +class UpdateSupplierInDTO(CreateSupplierInDTO): + pass + + +class UpdatePartialSupplierInDTO(BaseModel): + name: Optional[str] = Field(default=None, min_length=1, max_length=15) + company_name: Optional[str] = Field(default=None, max_length=40) + contact_name: Optional[str] = Field(default=None, max_length=30) + contact_title: Optional[str] = Field(default=None, max_length=30) + address: Optional[str] = Field(default=None, max_length=60) + city: Optional[str] = Field(default=None, max_length=15) + region: Optional[str] = Field(default=None, max_length=15) + postal_code: Optional[str] = Field(default=None, max_length=10) + country: Optional[str] = Field(default=None, max_length=15) + phone: Optional[str] = Field(default=None, max_length=24) + fax: Optional[str] = Field(default=None, max_length=24) + homepage: Optional[str] = Field(default=None, max_length=1024) diff --git a/src/pytest.github.ini b/src/pytest.github.ini new file mode 100644 index 0000000..d861fce --- /dev/null +++ b/src/pytest.github.ini @@ -0,0 +1,25 @@ +[pytest] +norecursedirs = versions +testpaths = tests +python_files = tests.py test_*.py + +env = + APP_ENV=test + DATABASE_URL=postgresql+asyncpg://northwind:northwind@127.0.0.1:5432/northwind + + +addopts = + -p no:warnings + --cov=. + --no-cov-on-fail + --cov-report term-missing + --cov-report term:skip-covered + --cov-report xml + --cov-branch + + +; http://doc.pytest.org/en/latest/example/markers.html +markers = + unit_test: Pure unit tests. + integration_test: Tests that access a database, API, etc. + functional_test: End to end tests that needs a browser. diff --git a/src/pytest.ini b/src/pytest.ini new file mode 100644 index 0000000..fefb4bc --- /dev/null +++ b/src/pytest.ini @@ -0,0 +1,26 @@ +[pytest] +norecursedirs = versions +testpaths = tests +python_files = tests.py test_*.py +asyncio_mode = auto + +env = + APP_ENV=test + DATABASE_URL=postgresql+asyncpg://northwind:northwind@postgres-test:5432/northwind + SA_ECHO=false + +addopts = + -p no:warnings + --cov=. + --no-cov-on-fail + --cov-report term-missing + --cov-report term:skip-covered + --cov-report xml + --cov-branch + + +; http://doc.pytest.org/en/latest/example/markers.html +markers = + unit_test: Pure unit tests. + integration_test: Tests that access a database, API, etc. + functional_test: End to end tests that needs a browser. diff --git a/src/shared/api/__init__.py b/src/shared/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/api/schemas/__init__.py b/src/shared/api/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/api/schemas/page.py b/src/shared/api/schemas/page.py new file mode 100644 index 0000000..4e31991 --- /dev/null +++ b/src/shared/api/schemas/page.py @@ -0,0 +1,28 @@ +from enum import IntEnum +from typing import Generic, List, TypeVar + +from pydantic import BaseModel, ConfigDict, Field + + +class PageSize(IntEnum): + x100 = 100 + x250 = 250 + x500 = 500 + x1000 = 1000 + + +class PageParams(BaseModel): + page: int = Field(1, ge=1, description='Page number') + size: PageSize = PageSize.x500 + + model_config = ConfigDict(use_enum_values=True) + + +T = TypeVar('T') + + +class PagedResponseSchema(BaseModel, Generic[T]): + total: int + page: int + size: int + results: List[T] diff --git a/src/shared/exceptions.py b/src/shared/exceptions.py new file mode 100644 index 0000000..c7dbfea --- /dev/null +++ b/src/shared/exceptions.py @@ -0,0 +1,29 @@ +from typing import Any + + +class APPExceptionError(Exception): + status_code = 500 + code = 'app-exception' + message = 'An error occurred' + + +class NotFoundError(APPExceptionError): + status_code = 404 + code: str + message: str + + def __init__(self, entity: str, *args: Any, **kwargs: Any): + self.code = f'{entity.lower()}-not-found' + self.message = f'{entity} not found' + super().__init__(*args, **kwargs) + + +class AlreadyExistsError(APPExceptionError): + status_code = 422 + code: str + message: str + + def __init__(self, entity: str, *args: Any, **kwargs: Any): + self.code = f'{entity.lower()}-already-exists' + self.message = f'{entity} already exists' + super().__init__(*args, **kwargs) diff --git a/src/shared/presenter.py b/src/shared/presenter.py new file mode 100644 index 0000000..d8e3f6e --- /dev/null +++ b/src/shared/presenter.py @@ -0,0 +1,14 @@ +import abc +from typing import Generic, TypeVar + + +InT = TypeVar('InT') +OutT = TypeVar('OutT') + + +class AbstractPresenter(Generic[InT, OutT], abc.ABC): + result: OutT + + @abc.abstractmethod + async def present(self, data: InT) -> None: + ... diff --git a/src/shared/repository/__init__.py b/src/shared/repository/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/repository/ports/__init__.py b/src/shared/repository/ports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/repository/ports/generic.py b/src/shared/repository/ports/generic.py new file mode 100644 index 0000000..85a80d4 --- /dev/null +++ b/src/shared/repository/ports/generic.py @@ -0,0 +1,43 @@ +import abc +import datetime +from decimal import Decimal +from typing import Dict, Generic, List, Optional, TypeVar, Union + +from pydantic import BaseModel + + +EntityT = TypeVar('EntityT') +CreateT = TypeVar('CreateT', bound=BaseModel) +UpdateT = TypeVar('UpdateT', bound=BaseModel) + +FilterBy = Dict[str, Optional[Union[int, Decimal, str, datetime.date, datetime.datetime, bool]]] + + +class AbstractRepository(Generic[EntityT, CreateT, UpdateT], abc.ABC): + @abc.abstractmethod + async def get_by_id(self, uuid: Union[str, int]) -> EntityT: + ... + + @abc.abstractmethod + async def get_all(self) -> List[EntityT]: + ... + + @abc.abstractmethod + async def get_xpage(self, page: int, size: int) -> List[EntityT]: + ... + + @abc.abstractmethod + async def filter_by(self, by: FilterBy) -> List[EntityT]: + ... + + @abc.abstractmethod + async def create(self, data: CreateT) -> EntityT: + ... + + @abc.abstractmethod + async def update(self, uuid: Union[str, int], data: UpdateT) -> EntityT: + ... + + @abc.abstractmethod + async def delete(self, uuid: Union[str, int]) -> None: + ... diff --git a/src/shared/repository/sqlalchemy.py b/src/shared/repository/sqlalchemy.py new file mode 100644 index 0000000..e18f5dc --- /dev/null +++ b/src/shared/repository/sqlalchemy.py @@ -0,0 +1,93 @@ +from typing import Any, AsyncContextManager, Callable, List, Optional, Self, Tuple, Type, TypeVar, Union, cast + +from pydantic import BaseModel +from sqlalchemy.exc import NoResultFound +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.sql import Select, select + +from shared.exceptions import NotFoundError +from shared.repository.ports.generic import AbstractRepository, FilterBy + + +EntityT = TypeVar('EntityT') +CreateT = TypeVar('CreateT', bound=BaseModel) +UpdateT = TypeVar('UpdateT', bound=BaseModel) + + +class SqlAlchemyRepository(AbstractRepository[EntityT, CreateT, UpdateT]): + entity: Type[EntityT] + default_key_param = 'uuid' + + def __new__(cls, *args: Any, **kwargs: Any) -> Self: # noqa: ARG003 + base: Optional[Tuple[Any, ...]] = getattr(cls, '__orig_bases__', None) + if base: + generics: Tuple[Type[EntityT], Type[CreateT], Type[UpdateT]] = base[0].__args__ + cls.entity = generics[0] + return super().__new__(cls) + + def __init__( + self, + session: Callable[[], AsyncContextManager[AsyncSession]], + ) -> None: + self.session_factory = session + + def get_key_param( + self, + ) -> str: + return getattr(self, 'key', self.default_key_param) + + async def get_by_id(self, uuid: Union[str, int]) -> EntityT: + by = {self.get_key_param(): uuid} + try: + async with self.session_factory() as session: + query = select(self.entity).filter_by(**by) + results = await session.execute(query) + (result,) = results.one() + except NoResultFound: + raise NotFoundError(self.entity.__name__) + else: + return cast(EntityT, result) + + # From: https://github.com/lewoudar/fastapi-paginator/blob/main/fastapi_paginator/helpers.py + async def _paginate(self, query: Select, page: int, size: int) -> List[EntityT]: + # inspiration for this query comes from fastapi-pagination package + async with self.session_factory() as session: + return list(await session.scalars(query.limit(size).offset(page - 1))) + + async def get_all(self) -> List[EntityT]: + query = select(self.entity) + async with self.session_factory() as session: + return list((await session.scalars(query)).all()) + + async def get_xpage(self, page: int, size: int) -> List[EntityT]: + query = select(self.entity) + return await self._paginate(query, page, size) + + async def filter_by(self, by: FilterBy) -> List[EntityT]: + query = select(self.entity).filter_by(**by) + async with self.session_factory() as session: + return list((await session.scalars(query)).all()) + + async def create(self, data: CreateT) -> EntityT: + async with self.session_factory() as session: + instance = self.entity(**data.model_dump()) + session.add(instance) + return instance + + async def update(self, uuid: Union[str, int], data: UpdateT) -> EntityT: + to_update = data.model_dump(exclude_unset=True) + if not to_update: + raise ValueError('No data to update') + async with self.session_factory() as session: + instance = await self.get_by_id(uuid) + + for key, value in to_update.items(): + setattr(instance, key, value) + + session.add(instance) + return instance + + async def delete(self, uuid: Union[str, int]) -> None: + async with self.session_factory() as session: + instance = await self.get_by_id(uuid) + await session.delete(instance) diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/app/__init__.py b/src/tests/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/app/test_cli.py b/src/tests/app/test_cli.py new file mode 100644 index 0000000..48ca158 --- /dev/null +++ b/src/tests/app/test_cli.py @@ -0,0 +1,9 @@ +from click.testing import CliRunner + +from manage import cli_app + + +def test_main() -> None: + runner = CliRunner() + result = runner.invoke(cli_app, ['test']) + assert 'Manage is working fine' in result.stdout diff --git a/src/tests/app/test_hello_world.py b/src/tests/app/test_hello_world.py new file mode 100644 index 0000000..9207211 --- /dev/null +++ b/src/tests/app/test_hello_world.py @@ -0,0 +1,11 @@ +from httpx import AsyncClient +import pytest + + +@pytest.mark.asyncio +async def test_hello_world(async_root_client: AsyncClient) -> None: + response = await async_root_client.get('/') + assert response.status_code == 200 + + data = response.json() + assert data['message'] == 'Hello World' diff --git a/src/tests/app/test_settings.py b/src/tests/app/test_settings.py new file mode 100644 index 0000000..f6a3273 --- /dev/null +++ b/src/tests/app/test_settings.py @@ -0,0 +1,5 @@ +from config import settings + + +def test_seetings() -> None: + assert settings.APP_ENV == 'test' diff --git a/src/tests/auth/__init__.py b/src/tests/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/auth/adapters/__init__.py b/src/tests/auth/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/auth/adapters/api/__init__.py b/src/tests/auth/adapters/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/auth/adapters/api/cli/__init_.py b/src/tests/auth/adapters/api/cli/__init_.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/auth/adapters/api/cli/test_user.py b/src/tests/auth/adapters/api/cli/test_user.py new file mode 100644 index 0000000..a76ee7b --- /dev/null +++ b/src/tests/auth/adapters/api/cli/test_user.py @@ -0,0 +1,13 @@ +from click.testing import CliRunner + +from manage import cli_app + + +runner = CliRunner() + + +def test_create_admin() -> None: + result = runner.invoke(cli_app, ['create-admin', 'camila@gmail.com', 'Camila'], input='Berlin123\n') + assert result.exit_code == 0 + expected = 'Password: Berlin123\nThe User Camila, camila@gmail.com has been created\n' + assert result.output == expected diff --git a/src/tests/auth/adapters/api/http/__init__.py b/src/tests/auth/adapters/api/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/auth/adapters/api/http/_test_login.py b/src/tests/auth/adapters/api/http/_test_login.py new file mode 100644 index 0000000..62efba9 --- /dev/null +++ b/src/tests/auth/adapters/api/http/_test_login.py @@ -0,0 +1,31 @@ +from httpx import AsyncClient +import pytest + +from auth.domain.entities.user import User +from auth.domain.services.token import TokenService + + +@pytest.mark.asyncio +async def test_login(async_client: AsyncClient, token_service: TokenService, admin_user: User) -> None: + data = {'email': admin_user.email, 'password': '123456'} + response = await async_client.post('/auth/login', json=data) + assert response.status_code == 200 + result = response.json() + assert 'access_token' in result + + access_token = result['access_token'] + + claims = token_service.decode_token(access_token) + assert claims + assert claims['sub'] == admin_user.email + assert claims['profile']['first_name'] == admin_user.first_name + assert claims['profile']['is_admin'] == admin_user.is_admin + + +@pytest.mark.asyncio +async def test_bad_login(async_client: AsyncClient, admin_user: User) -> None: + data = {'email': admin_user.email, 'password': 'fakepassword'} + response = await async_client.post('/auth/login', json=data) + assert response.status_code == 401 + result = response.json() + assert result['detail'] == 'Incorrect email or password' diff --git a/src/tests/auth/adapters/api/http/_test_protected.py b/src/tests/auth/adapters/api/http/_test_protected.py new file mode 100644 index 0000000..3932e58 --- /dev/null +++ b/src/tests/auth/adapters/api/http/_test_protected.py @@ -0,0 +1,18 @@ +from httpx import AsyncClient +import pytest + +from auth.domain.services.token import TokenService + + +@pytest.mark.asyncio +async def test_protected(async_client: AsyncClient, token_service: TokenService) -> None: + access_token = token_service.create_access_token( + 'admin@admin.poc', {'profile': {'first_name': 'Admin', 'is_admin': True}} + ) + async_client.headers.update({'Authorization': f'Bearer {access_token}'}) + response = await async_client.get('/auth/protected') + assert response.status_code == 200 + result = response.json() + assert 'username' in result + + assert result['username'] == 'admin@admin.poc' diff --git a/src/tests/auth/adapters/api/http/_test_token.py b/src/tests/auth/adapters/api/http/_test_token.py new file mode 100644 index 0000000..7115be8 --- /dev/null +++ b/src/tests/auth/adapters/api/http/_test_token.py @@ -0,0 +1,54 @@ +from httpx import AsyncClient +import pytest + +from auth.domain.services.token import TokenService + + +@pytest.mark.asyncio +async def test_use_refresh_token_error(async_client: AsyncClient, token_service: TokenService) -> None: + refresh_token = token_service.create_refresh_token( + 'admin@admin.poc', {'profile': {'first_name': 'Admin', 'is_admin': True}} + ) + async_client.headers.update({'Authorization': f'Bearer {refresh_token}'}) + response = await async_client.get('/auth/protected') + assert response.status_code == 401 + result = response.json() + assert result['detail'] == 'Invalid token.' + + +@pytest.mark.asyncio +async def test_expired_token(async_client: AsyncClient) -> None: + access_token = TokenService._create_token( # noqa: SLF001 + 'a', 'admin@admin.poc', {'profile': {'first_name': 'Admin', 'is_admin': True}}, -1 + ) + async_client.headers.update({'Authorization': f'Bearer {access_token}'}) + response = await async_client.get('/auth/protected') + assert response.status_code == 401 + result = response.json() + assert result['detail'] == 'Invalid token or expired token.' + + +@pytest.mark.asyncio +async def test_refresh_token(async_client: AsyncClient, token_service: TokenService) -> None: + refresh_token = token_service.create_refresh_token( + 'admin@admin.poc', {'profile': {'first_name': 'Admin', 'is_admin': True}} + ) + async_client.headers.update({'Authorization': f'Bearer {refresh_token}'}) + response = await async_client.post('/auth/refresh-token') + assert response.status_code == 200 + access_token = response.json()['access_token'] + claims = token_service.decode_token(access_token) + assert claims is not None + assert claims['sub'] == 'admin@admin.poc' + assert claims['profile']['first_name'] == 'Admin' + assert claims['profile']['is_admin'] is True + + +@pytest.mark.asyncio +async def test_refresh_token_with_access_error(async_client: AsyncClient, token_service: TokenService) -> None: + refresh_token = token_service.create_access_token( + 'admin@admin.poc', {'profile': {'first_name': 'Admin', 'is_admin': True}} + ) + async_client.headers.update({'Authorization': f'Bearer {refresh_token}'}) + response = await async_client.post('/auth/refresh-token') + assert response.status_code == 401 diff --git a/src/tests/auth/conftest.py b/src/tests/auth/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/conftest.py b/src/tests/conftest.py new file mode 100644 index 0000000..2fe511f --- /dev/null +++ b/src/tests/conftest.py @@ -0,0 +1,167 @@ +import asyncio +from asyncio import current_task +from typing import AsyncGenerator, Generator, Iterator + +from httpx import AsyncClient +import pytest +import pytest_asyncio +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_scoped_session, create_async_engine +from sqlalchemy.orm import sessionmaker + +from app.app_container import AppContainer, AppContainerMixin +from app.asgi import app +from auth.domain.entities.user import User +from auth.domain.ports.repositories.user import AbstractUserRepository, CreateUserInDTO +from auth.domain.services.token import TokenService +from config import settings +from infra.cache.memory_cache import MemoryCache +from infra.cache.ports import AbstractCacheRepository +from infra.database.sqlalchemy.sqlalchemy import metadata +import northwind.infra.database.sqlalchemy.models # noqa +from utils.di import di_singleton + + +def pytest_addoption(parser: pytest.Parser) -> None: + # NOTE: when adding or removing an option, + # remove to remove/add from app/conftest.py:addoption_params + parser.addoption('--no-db', default=False, action='store_true', help='Disable testing database') + + +def addoption_params(config: pytest.Config) -> dict[str, bool]: + return { + 'no-db': config.getoption('--no-db'), + } + + +def pytest_configure(config: pytest.Config) -> None: + params = addoption_params(config) + if not params.get('no-db'): + next(_event_loop()).run_until_complete(create_all(_engine())) + + +def pytest_unconfigure(config: pytest.Config) -> None: + params = addoption_params(config) + if not params.get('no-db'): + next(_event_loop()).run_until_complete(drop_all(_engine())) + + +def _event_loop() -> Iterator[asyncio.AbstractEventLoop]: + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + yield loop + loop.close() + + +def _engine() -> AsyncEngine: + return create_async_engine(settings.DATABASE_URL, future=True, echo=False) + + +@pytest.fixture(scope='function', autouse=True) +def engine() -> Generator: + yield _engine() + + +@pytest.fixture +def async_session_maker(engine: AsyncEngine) -> Generator: + yield async_scoped_session( + sessionmaker(engine, expire_on_commit=False, class_=AsyncSession), scopefunc=current_task + ) + + +async def create_all(engine: AsyncEngine) -> None: + async with engine.begin() as conn: + await conn.run_sync(metadata.drop_all) + await conn.run_sync(metadata.create_all) + await engine.dispose() + + +async def drop_all(engine: AsyncEngine) -> None: + async with engine.begin() as conn: + await conn.run_sync(metadata.drop_all) + await engine.dispose() + + +@pytest_asyncio.fixture +async def async_root_client() -> AsyncClient: + async with AsyncClient(app=app, base_url='http://test') as ac: + yield ac + + +@pytest_asyncio.fixture +async def async_client() -> AsyncClient: + async with AsyncClient(app=app, base_url='http://test/api/v1') as ac: + yield ac + + +@pytest_asyncio.fixture +async def app_container() -> AsyncGenerator: + yield AppContainer() + + +@pytest_asyncio.fixture +async def user_repository(app_container: AppContainer) -> AsyncGenerator[AbstractUserRepository, None]: + yield app_container.user_repository + + +@pytest_asyncio.fixture +async def token_service(app_container: AppContainer) -> AsyncGenerator[TokenService, None]: + yield app_container.token_service + + +@pytest_asyncio.fixture +async def admin_user(user_repository: AbstractUserRepository) -> AsyncGenerator[User, None]: + # We have to encrypt the password here because the password is not encrypted in the repository + password = User.encrypt_password('123456') + in_dto = CreateUserInDTO(email='admin@northwind.poc', first_name='Admin', password=password, is_admin=True) + yield await user_repository.create(in_dto) + await user_repository.delete(in_dto.email) + + +@pytest_asyncio.fixture +async def normal_user(user_repository: AbstractUserRepository) -> AsyncGenerator[User, None]: + # We have to encrypt the password here because the password is not encrypted in the repository + password = User.encrypt_password('123456') + in_dto = CreateUserInDTO(email='user@northwind.poc', first_name='Example', password=password, is_admin=False) + yield await user_repository.create(in_dto) + await user_repository.delete(in_dto.email) + + +@pytest.fixture +def admin_access_token(admin_user: User, token_service: TokenService) -> str: + return token_service.create_access_token( + admin_user.email, {'profile': {'first_name': admin_user.first_name, 'is_admin': admin_user.is_admin}} + ) + + +@pytest.fixture +def normal_user_access_token(normal_user: User, token_service: TokenService) -> str: + return token_service.create_access_token( + normal_user.email, {'profile': {'first_name': normal_user.first_name, 'is_admin': normal_user.is_admin}} + ) + + +@pytest_asyncio.fixture +async def async_admin_client(admin_access_token: str) -> AsyncClient: + async with AsyncClient(app=app, base_url='http://test/api/v1') as ac: + ac.headers.update({'Authorization': f'Bearer {admin_access_token}'}) + yield ac + + +@pytest_asyncio.fixture +async def async_normal_client(normal_user_access_token: str) -> AsyncClient: + async with AsyncClient(app=app, base_url='http://test/api/v1') as ac: + ac.headers.update({'Authorization': f'Bearer {normal_user_access_token}'}) + yield ac + + +@pytest.fixture(autouse=True) +def memory_cache_database(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]: + @di_singleton + def fake_cache_gateway() -> AbstractCacheRepository: + return MemoryCache() + + monkeypatch.setattr(AppContainerMixin, '_get_cache_repository', lambda _: fake_cache_gateway()) + + yield diff --git a/src/tests/infra/test_auth_user.py b/src/tests/infra/test_auth_user.py new file mode 100644 index 0000000..73aff1d --- /dev/null +++ b/src/tests/infra/test_auth_user.py @@ -0,0 +1,9 @@ +import pytest +from sqlalchemy.ext.asyncio import async_scoped_session +from sqlalchemy.sql import text + + +@pytest.mark.asyncio +async def test_auth_user_table_exists(async_session_maker: async_scoped_session) -> None: + async with async_session_maker() as session: + await session.execute(text('SELECT * FROM auth_user')) diff --git a/src/tests/northwind/__init__.py b/src/tests/northwind/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/northwind/adapters/__init__.py b/src/tests/northwind/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/northwind/adapters/api/__init__.py b/src/tests/northwind/adapters/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/northwind/adapters/api/http/__init__.py b/src/tests/northwind/adapters/api/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/northwind/adapters/api/http/test_category.py b/src/tests/northwind/adapters/api/http/test_category.py new file mode 100644 index 0000000..8bbc686 --- /dev/null +++ b/src/tests/northwind/adapters/api/http/test_category.py @@ -0,0 +1,67 @@ +from typing import Dict, Union + +from httpx import AsyncClient +import pytest + + +@pytest.mark.asyncio +async def test_category_create(async_admin_client: AsyncClient) -> None: + data = {'name': 'Fish', 'description': 'Fish Description'} + response = await async_admin_client.post('/northwind/category', json=data) + assert response.status_code == 201 + result = response.json() + assert 'name' in result and result['name'] == data['name'] + assert 'description' in result and result['description'] == data['description'] + + +@pytest.mark.asyncio +async def test_category_list(async_normal_client: AsyncClient, category_cereals: Dict[str, Union[str, int]]) -> None: + response = await async_normal_client.get('/northwind/category') + assert response.status_code == 200 + paginated_result = response.json() + assert 'results' in paginated_result + names = [item.get('name') for item in paginated_result['results']] + assert category_cereals['name'] in names + + +@pytest.mark.asyncio +async def test_category_detail(async_normal_client: AsyncClient, category_cereals: Dict[str, Union[str, int]]) -> None: + response = await async_normal_client.get(f'/northwind/category/{category_cereals.get("id")}') + assert response.status_code == 200 + result = response.json() + assert 'name' in result and result['name'] == category_cereals['name'] + assert 'description' in result and result['description'] == category_cereals['description'] + + +# @pytest.mark.asyncio +# async def test_category_update(async_admin_client: AsyncClient, category_western: str) -> None: +# data = {'name': 'Western - wip'} +# response = await async_admin_client.put(f'/northwind/category/{category_western}', json=data) +# assert response.status_code == 200 +# result = response.json() +# assert 'name' in result +# assert result['name'] == 'Western - wip' + + +# @pytest.mark.asyncio +# async def test_category_update_permission(async_normal_client: AsyncClient, category_western: str) -> None: +# data = {'name': 'Western - wip'} +# response = await async_normal_client.put(f'/northwind/category/{category_western}', json=data) +# assert response.status_code == 403 + + +# @pytest.mark.asyncio +# async def test_category_update_partial(async_admin_client: AsyncClient, category_western: str) -> None: +# data = {'name': 'Western - example'} +# response = await async_admin_client.patch(f'/northwind/category/{category_western}', json=data) +# assert response.status_code == 200 +# result = response.json() +# assert 'name' in result +# assert result['name'] == 'Western - example' + + +# @pytest.mark.asyncio +# async def test_category_update_partial_permission(async_normal_client: AsyncClient, category_western: str) -> None: +# data = {'name': 'Western - example'} +# response = await async_normal_client.patch(f'/northwind/category/{category_western}', json=data) +# assert response.status_code == 403 diff --git a/src/tests/northwind/conftest.py b/src/tests/northwind/conftest.py new file mode 100644 index 0000000..8c6b61f --- /dev/null +++ b/src/tests/northwind/conftest.py @@ -0,0 +1,34 @@ +from typing import AsyncContextManager, AsyncGenerator, Callable, Dict, Union + +from faker import Faker +import pytest_asyncio +from sqlalchemy.ext.asyncio import AsyncSession + +from northwind.infra.database.sqlalchemy.models import categories + + +fake = Faker() + +AsyncSessionCtxT = Callable[[], AsyncContextManager[AsyncSession]] + + +@pytest_asyncio.fixture +async def category_cereals(async_session_maker: AsyncSessionCtxT) -> AsyncGenerator[Dict[str, Union[str, int]], None]: + async with async_session_maker() as session: + description = fake.paragraph(nb_sentences=2) + statement = ( + categories.insert() + .values(category_name='Cereals', description=description) + .returning(categories.c.category_id) + ) + result = await session.execute(statement) + await session.commit() + + object_id = result.scalar_one() + + yield {'id': object_id, 'name': 'Cereals', 'description': description} + + stmt = categories.delete().where(categories.c.category_id == object_id) + await session.execute(stmt) + + await session.commit() diff --git a/src/tests/utils/__init__.py b/src/tests/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/utils/test_di.py b/src/tests/utils/test_di.py new file mode 100644 index 0000000..af2bea6 --- /dev/null +++ b/src/tests/utils/test_di.py @@ -0,0 +1,45 @@ +import abc + +from utils.di import DIContainer + + +class AbstractRepositoryClass(abc.ABC): + pass + + +class RepositoryClass(AbstractRepositoryClass): + pass + + +class AbstractServiceClass(abc.ABC): + pass + + +class ServiceClass(AbstractServiceClass): + def __init__(self, repo: AbstractRepositoryClass): + self.repo = repo + + +class DocumentContainer(DIContainer): + repo: AbstractRepositoryClass + service: ServiceClass + + def _get_repo(self) -> AbstractRepositoryClass: + return RepositoryClass() + + _get_repo.__dict__['singleton'] = True + + def _get_service(self) -> ServiceClass: + return ServiceClass(self.repo) + + +def test_singleton() -> None: + c = DocumentContainer() + + assert c.repo == c.repo + + +def test_no_singleton() -> None: + c = DocumentContainer() + + assert c.service != c.service diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/async_utils.py b/src/utils/async_utils.py new file mode 100644 index 0000000..983a7d3 --- /dev/null +++ b/src/utils/async_utils.py @@ -0,0 +1,16 @@ +import asyncio +from typing import Any, Awaitable, Callable + + +def async_exec(func: Callable[..., Awaitable[Any]], *args: Any, **kwargs: Any) -> None: + try: + loop = asyncio.get_event_loop() + loop.run_until_complete(func(*args, **kwargs)) + except RuntimeError as e: + if str(e).startswith('There is no current event loop in thread'): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(func(*args, **kwargs)) + loop.close() + else: + raise diff --git a/src/utils/di.py b/src/utils/di.py new file mode 100644 index 0000000..27220ad --- /dev/null +++ b/src/utils/di.py @@ -0,0 +1,37 @@ +from functools import wraps +from typing import Any, Callable, Dict, TypeVar + + +P = TypeVar('P') +R = TypeVar('R') + + +def di_singleton(fnc: Callable[..., R]) -> Callable[..., R]: + setattr(fnc, 'singleton', True) # noqa + + @wraps(fnc) + def wrapper(*args: P, **kwargs: Any) -> R: + return fnc(*args, **kwargs) + + return wrapper + + +class DIContainer: + _register: Dict[str, Callable[..., Any]] + + def __init__(self) -> None: + self._register = {} + + def __getattribute__(self, name: str) -> Any: + ret = None + if name == '_register' or name.startswith('__'): + return super().__getattribute__(name) + + if name not in self._register: + fnc = super().__getattribute__(f'_get_{name}') + ret = fnc() + if getattr(fnc, 'singleton', False): + self._register[name] = ret + return ret + + return self._register[name] diff --git a/src/utils/logger/__init__.py b/src/utils/logger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger/formatter/__init__.py b/src/utils/logger/formatter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger/formatter/color_extra.py b/src/utils/logger/formatter/color_extra.py new file mode 100644 index 0000000..62ccfe2 --- /dev/null +++ b/src/utils/logger/formatter/color_extra.py @@ -0,0 +1,7 @@ +import colorlog + +from .standard_extra import FormatterExtra + + +class ColorFormatterExtra(colorlog.ColoredFormatter, FormatterExtra): + pass diff --git a/src/utils/logger/formatter/standard_extra.py b/src/utils/logger/formatter/standard_extra.py new file mode 100644 index 0000000..002cf41 --- /dev/null +++ b/src/utils/logger/formatter/standard_extra.py @@ -0,0 +1,40 @@ +import logging +from typing import ClassVar, List + + +class FormatterExtra(logging.Formatter): + # From: https://docs.python.org/3/library/logging.html#logrecord-attributes + reserved: ClassVar[List[str]] = [ + 'args', + 'asctime', + 'created', + 'exc_info', + 'filename', + 'funcName', + 'levelname', + 'levelno', + 'lineno', + 'message', + 'module', + 'msecs', + 'msg', + 'name', + 'pathname', + 'process', + 'processName', + 'relativeCreated', + 'stack_info', + 'thread', + 'threadName', + # Custom + 'exc_text', + 'color_message', + ] + + def format(self, record: logging.LogRecord) -> str: + extra = record.__dict__.copy() + for key in self.reserved: + extra.pop(key, None) + if extra: + record.msg += f' {extra}' + return super().format(record) diff --git a/src/utils/singleton.py b/src/utils/singleton.py new file mode 100644 index 0000000..34a1e82 --- /dev/null +++ b/src/utils/singleton.py @@ -0,0 +1,40 @@ +# With changes from: https://github.com/Kemaweyan/singleton_decorator/blob/master/singleton_decorator/decorator.py # noqa +import threading +from typing import Any, Type, TypeVar, cast + + +InstanceT = TypeVar('InstanceT') +WrappedT = TypeVar('WrappedT') + + +DecoratedT = TypeVar('DecoratedT', bound=Type[Any]) +ResultT = Any + + +class _SingletonWrapper: + """ + A singleton wrapper class. Its instances would be created + for each decorated class. + """ + + def __init__(self, cls: DecoratedT) -> None: + self.__wrapped__: DecoratedT = cls + self._instance: ResultT = None + self._lock = threading.Lock() + + def __call__(self, *args: Any, **kwargs: Any) -> ResultT: + """Returns a single instance of decorated class""" + if self._instance is None: + with self._lock: + if self._instance is None: + self._instance = self.__wrapped__(*args, **kwargs) + return self._instance + + +def singleton(cls: DecoratedT) -> DecoratedT: + """ + A singleton decorator. Returns a wrapper objects. A call on that object + returns a single instance object of decorated class. Use the __wrapped__ + attribute to access decorated class directly in unit tests + """ + return cast(DecoratedT, _SingletonWrapper(cls))