├── .coveragerc ├── .editorconfig ├── .env_example ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.prod ├── LICENSE.md ├── Makefile ├── README.md ├── alembic.ini ├── docker-compose.yaml ├── poetry.lock ├── pyproject.toml └── src ├── __init__.py ├── alembic ├── README ├── env.py ├── script.py.mako └── versions │ └── 5cfe696e2a6f_add_initial_migration.py ├── api ├── __init__.py ├── dependencies.py ├── main.py └── routes │ ├── __init__.py │ ├── auth.py │ ├── tasks.py │ └── users.py ├── core ├── __init__.py ├── celery_config.py ├── config.py ├── db.py ├── enums.py ├── error_messages.py ├── exceptions.py └── security.py ├── main.py ├── models ├── __init__.py ├── items.py └── users.py ├── repositories ├── __init__.py ├── base.py ├── item.py └── user.py ├── schemas ├── __init__.py ├── auth.py ├── base.py └── items.py ├── services ├── auth.py ├── base.py ├── item.py ├── item_service.py └── user.py ├── tasks.py └── tests ├── __init__.py ├── api └── __init__.py ├── conftest.py ├── factories.py └── services ├── __init__.py └── test_celery.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | src/tests/* 4 | src/tests/*/* 5 | src/core/config.py 6 | src/main.pyc 7 | src/api/dependencies.py 8 | src/api/routes/tasks.py 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 88 11 | -------------------------------------------------------------------------------- /.env_example: -------------------------------------------------------------------------------- 1 | # Domain 2 | # This would be set to the production domain with an env var on deployment 3 | DOMAIN=localhost 4 | 5 | # Environment: local, staging, production 6 | ENVIRONMENT=local 7 | 8 | PROJECT_NAME=FastAPI-template 9 | STACK_NAME=fastapi-template 10 | 11 | # Backend 12 | BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost" 13 | SECRET_KEY=verysecretkey 14 | FIRST_SUPERUSER=admin@example.com 15 | FIRST_SUPERUSER_PASSWORD=admin 16 | 17 | # Postgres 18 | POSTGRES_HOST=localhost 19 | POSTGRES_PORT=5432 20 | POSTGRES_DB=fastapi-template 21 | POSTGRES_USER=postgres 22 | POSTGRES_PASSWORD=postgres 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v3 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v4 15 | with: 16 | python-version: '3.12' 17 | 18 | - name: Install dependencies 19 | run: | 20 | pip install poetry 21 | poetry config virtualenvs.create false && poetry install --with dev --no-interaction --no-ansi 22 | 23 | 24 | - name: Run linters 25 | run: | 26 | make lint 27 | 28 | - name: Run tests 29 | run: | 30 | make test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Poetry 7 | .cache 8 | coverage.xml 9 | 10 | # Virtual environment 11 | .env/ 12 | .venv/ 13 | 14 | # Logs 15 | *.log 16 | 17 | # Coverage 18 | htmlcov/ 19 | 20 | pytest-cov 21 | .tox/ 22 | .coverage 23 | .coverage.* 24 | 25 | # MyPy cache 26 | .mypy_cache/ 27 | 28 | # Test reports 29 | .pytest_cache/ 30 | .env 31 | .idea/ 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to FastAPI Template 2 | 3 | Thank you for considering contributing to the FastAPI Kickstart template project! We welcome contributions from the community to improve and enhance the project. To ensure a smooth contribution process, please follow the guidelines outlined below. 4 | 5 | ## How to Contribute 6 | 7 | ### Reporting Issues 8 | 9 | If you encounter any bugs or issues with the project, please follow these steps to report them: 10 | 11 | 1. **Search for Existing Issues:** Before creating a new issue, check the [issue tracker](https://github.com/vjanz/fastapi-kickstart/issues) to see if the issue has already been reported. 12 | 2. **Provide Details:** When reporting a new issue, provide as much detail as possible. Include the following information: 13 | - A clear and descriptive title 14 | - Steps to reproduce the issue 15 | - Expected and actual behavior 16 | - Screenshots or logs, if applicable 17 | 18 | ### Requesting Features 19 | 20 | If you have a suggestion for a new feature or improvement, please follow these steps: 21 | 22 | 1. **Search for Existing Requests:** Check the [issue tracker](https://github.com/vjanz/fastapi-kickstart/issues) to see if the feature request has already been submitted. 23 | 2. **Provide a Detailed Description:** Include a detailed description of the feature or improvement, including its use case and potential benefits. 24 | 25 | ### Making Code Contributions 26 | 27 | To contribute code changes, please follow these guidelines: 28 | 29 | 1. **Fork the Repository:** Fork the repository to your own GitHub account. 30 | 2. **Clone Your Fork:** Clone the forked repository to your local machine. 31 | 3. **Create a New Branch:** Create a new branch for your changes: 32 | ```sh 33 | git checkout -b your-feature-branch 34 | 4. **Make Changes:** Make your changes in the new branch. Be sure to write clear, concise commit messages. 35 | 5. **Run tests:** Make sure all tests pass before submitting your changes. 36 | 6. **Submit a Pull Request:** Push your changes to your fork and submit a pull request to the main repository. 37 | 38 | 39 | ### Questions? 40 | If you have any questions or need further assistance, feel free to reach out by opening an issue or contacting the project maintainers. 41 | 42 | Thank you for contributing! 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | 3 | # Environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | # Set work directory 8 | WORKDIR /app 9 | 10 | # Install build dependencies and Poetry 11 | RUN apk add --no-cache gcc musl-dev python3-dev linux-headers \ 12 | && pip install --no-cache-dir poetry 13 | 14 | # Copy the dependency files 15 | COPY pyproject.toml poetry.lock ./ 16 | 17 | # Install dependencies without dev packages 18 | RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi 19 | 20 | # Copy the application source code 21 | COPY ./src /app/src 22 | 23 | CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] 24 | 25 | -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | 3 | # Environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | # Set work directory 8 | WORKDIR /app 9 | 10 | # Install Poetry 11 | RUN pip install --no-cache-dir poetry 12 | 13 | # Copy the dependency files 14 | COPY pyproject.toml poetry.lock ./ 15 | 16 | # Install dependencies without dev packages 17 | RUN poetry config virtualenvs.create false && poetry install --no-dev --no-interaction --no-ansi 18 | 19 | # Copy the application source code 20 | COPY ./src /app/src 21 | 22 | # Use Gunicorn for production with gunicorn workers 23 | CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "src.main:app", "--bind", "0.0.0.0:8000"] 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Valon Januzaj 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format lint help test up down down-v migrate revision logs 2 | 3 | GREEN = \033[0;32m 4 | CYAN = \033[0;36m 5 | RESET = \033[0m 6 | 7 | format: 8 | @echo "Formatting code..." 9 | set -e; \ 10 | set -x; \ 11 | ruff check src --fix; \ 12 | ruff format src 13 | 14 | lint: 15 | @echo "Running linters..." 16 | set -e; \ 17 | set -x; \ 18 | mypy src; \ 19 | ruff check src; \ 20 | ruff format src --check 21 | 22 | test: 23 | @echo "Running tests..." 24 | pytest src -v -s 25 | 26 | test-cov: 27 | @echo "Running tests with coverage..." 28 | @echo "Coverage report will be available in the htmlcov directory and in the coverage.xml file" 29 | pytest src --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml -v -s 30 | 31 | up: 32 | @echo "Starting containers..." 33 | docker compose up -d --build 34 | 35 | down: 36 | @echo "Stopping containers..." 37 | docker compose down 38 | 39 | down-v: 40 | @echo "Stopping containers and removing volumes..." 41 | docker compose down -v 42 | 43 | migrate: 44 | @echo "Applying migrations..." 45 | docker compose run --rm backend alembic upgrade head 46 | 47 | revision: 48 | @echo "Creating a new migration..." 49 | docker compose run --rm backend alembic revision --autogenerate -m "$(message)" 50 | 51 | message ?= "New migration" 52 | 53 | logs: 54 | @echo "Showing logs..." 55 | docker compose logs -f 56 | 57 | .PHONY: help 58 | help: 59 | @echo "" 60 | @echo "${CYAN}Available commands:${RESET}" 61 | @echo " ${GREEN}format${RESET} Format the code" 62 | @echo " ${GREEN}lint${RESET} Run all the linters" 63 | @echo " ${GREEN}test${RESET} Run the tests" 64 | @echo " ${GREEN}test-cov${RESET} Run the tests with coverage" 65 | @echo " ${GREEN}up${RESET} Start the containers and apply migrations" 66 | @echo " ${GREEN}down${RESET} Stop the containers" 67 | @echo " ${GREEN}down-v${RESET} Remove the volumes too" 68 | @echo " ${GREEN}migrate${RESET} Apply migrations" 69 | @echo " ${GREEN}revision${RESET} Create a new migration" 70 | @echo " ${GREEN}logs${RESET} Show logs" 71 | @echo " ${GREEN}help${RESET} Show this message" 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI-Kickstart Template :rocket: 2 | 3 | This repository provides a template for quickly starting a project with FastAPI. It includes configuration for testing with SQLite, PostgreSQL integration, and basic CRUD operations with SQLAlchemy. This template is ideal for developers looking to kickstart their FastAPI projects with a solid foundation. 4 | 5 | ## Features :mag_right: 6 | 7 | - **SQLAlchemy**: ORM for SQL databases. 8 | - **Authentication**: 9 | - **OAuth 2.0 with JWT (JSON Web Token)**: OAuth2PasswordBearer for token-based authentication, using JWT tokens for secure and stateless client authorization. 10 | - **SQLite for Testing**: In-memory SQLite database for testing. 11 | - **PostgreSQL Integration**: Basic configuration for PostgreSQL. 12 | - **Test Setup**: Fixtures for database setup with SQLite and teardown, celery and redis. 13 | - **CRUD Operations**: Basic CRUD operations with SQLAlchemy. 14 | - **Poetry**: Dependency management with Poetry. 15 | - **Celery**: Asynchronous task queue for background tasks. 16 | - **Separated Dockerfile for Development and Production**: Dockerfile for development and production environments. 17 | - **Workflows**: GitHub Actions for CI (Testing and Linting). 18 | - **Makefile**: Makefile for common commands. 19 | 20 | ## Getting Started :clipboard: 21 | 22 | ### Prerequisites :dart: 23 | 24 | - Python 3.12+ 25 | - PostgreSQL 26 | - SQLite 27 | - Redis 28 | - Docker & docker-compose 29 | 30 | ### Installation :inbox_tray: 31 | 32 | 1. **Clone the repository:** 33 | 34 | ```bash 35 | git clone https://github.com/vjanz/fastapi-kickstart.git 36 | cd fastapi-kickstart 37 | ``` 38 | 39 | 2. **Install dependencies using Poetry for local development:** 40 | 41 | ```bash 42 | poetry install 43 | ``` 44 | 45 | 3. **cp .env.example .env and modify if you'd like.** 46 | 47 | ```bash 48 | ENVIRONMENT=local 49 | PROJECT_NAME=FastAPI-kickstart 50 | 51 | # Backend 52 | BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost" 53 | SECRET_KEY=verysecretkey 54 | 55 | # Postgres 56 | POSTGRES_HOST=db 57 | POSTGRES_PORT=5432 58 | POSTGRES_DB=fastapi-kickstart 59 | POSTGRES_USER=postgres 60 | POSTGRES_PASSWORD=postgres 61 | 62 | # Redis/Celery 63 | REDIS_HOST=redis 64 | REDIS_PORT=6379 65 | ``` 66 | 67 | ### Running the Application :gear: 68 | 69 | The recommended way to run the stack is with Docker Compose. The following command will start the FastAPI application, PostgreSQL, and Redis: 70 | 71 | ```bash 72 | docker-compose up -d --build 73 | # or with make 74 | make up 75 | ``` 76 | 77 | ### Running & Generating migrations :arrows_counterclockwise: 78 | 79 | To run the migrations, use the following command: 80 | ```bash 81 | docker compose run --rm backend alembic upgrade head 82 | # or with make 83 | make migrate 84 | ``` 85 | 86 | To generate a new migration, use the following command: 87 | ```bash 88 | docker compose run --rm backend alembic revision --autogenerate -m "migration message" 89 | # or with make 90 | make revision message="migration message" 91 | ``` 92 | 93 | ### Running the tests :white_check_mark: 94 | To run the tests, use the following command: 95 | 96 | Test without cov: 97 | ```bash 98 | pytest src -v -s 99 | 100 | # or with make 101 | 102 | make test 103 | ``` 104 | 105 | Tests with cov: 106 | ```bash 107 | pytest src -v -s --cov=src --cov-report=term-missing 108 | 109 | # or with make 110 | 111 | make test-cov 112 | ``` 113 | 114 | ### Roadmap :construction: 115 | 116 | - [x] Add User model and CRUD operations 117 | - [x] Add JWT Authentication 118 | - [ ] Add Seeders for fast database setup 119 | - [ ] Add more CRUD operations 120 | 121 | ### Contributing :handshake: 122 | 123 | Contributions are welcome! Please feel free to submit a PR or open an issue if you encounter any problems. 124 | If you'd please read the [Contributing Guidelines](CONTRIBUTING.md) before submitting your PR. 125 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | # Use forward slashes (/) also on windows to provide an os agnostic path 6 | script_location = src/alembic 7 | 8 | # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s 9 | # Uncomment the line below if you want the files to be prepended with date and time 10 | # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file 11 | # for all available tokens 12 | # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s 13 | 14 | # sys.path path, will be prepended to sys.path if present. 15 | # defaults to the current working directory. 16 | prepend_sys_path = . 17 | 18 | # timezone to use when rendering the date within the migration file 19 | # as well as the filename. 20 | # If specified, requires the python>=3.9 or backports.zoneinfo library. 21 | # Any required deps can installed by adding `alembic[tz]` to the pip requirements 22 | # string value is passed to ZoneInfo() 23 | # leave blank for localtime 24 | # timezone = 25 | 26 | # max length of characters to apply to the "slug" field 27 | # truncate_slug_length = 40 28 | 29 | # set to 'true' to run the environment during 30 | # the 'revision' command, regardless of autogenerate 31 | # revision_environment = false 32 | 33 | # set to 'true' to allow .pyc and .pyo files without 34 | # a source .py file to be detected as revisions in the 35 | # versions/ directory 36 | # sourceless = false 37 | 38 | # version location specification; This defaults 39 | # to alembic/versions. When using multiple version 40 | # directories, initial revisions must be specified with --version-path. 41 | # The path separator used here should be the separator specified by "version_path_separator" below. 42 | # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions 43 | 44 | # version path separator; As mentioned above, this is the character used to split 45 | # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. 46 | # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. 47 | # Valid values for version_path_separator are: 48 | # 49 | # version_path_separator = : 50 | # version_path_separator = ; 51 | # version_path_separator = space 52 | version_path_separator = os # Use os.pathsep. Default configuration used for new projects. 53 | 54 | # set to 'true' to search source files recursively 55 | # in each "version_locations" directory 56 | # new in Alembic version 1.10 57 | # recursive_version_locations = false 58 | 59 | # the output encoding used when revision files 60 | # are written from script.py.mako 61 | # output_encoding = utf-8 62 | 63 | sqlalchemy.url = driver://user:pass@localhost/dbname 64 | 65 | 66 | [post_write_hooks] 67 | # post_write_hooks defines scripts or Python functions that are run 68 | # on newly generated revision scripts. See the documentation for further 69 | # detail and examples 70 | 71 | # format using "black" - use the console_scripts runner, against the "black" entrypoint 72 | # hooks = black 73 | # black.type = console_scripts 74 | # black.entrypoint = black 75 | # black.options = -l 79 REVISION_SCRIPT_FILENAME 76 | 77 | # lint with attempts to fix using "ruff" - use the exec runner, execute a binary 78 | # hooks = ruff 79 | # ruff.type = exec 80 | # ruff.executable = %(here)s/.venv/bin/ruff 81 | # ruff.options = --fix REVISION_SCRIPT_FILENAME 82 | 83 | # Logging configuration 84 | [loggers] 85 | keys = root,sqlalchemy,alembic 86 | 87 | [handlers] 88 | keys = console 89 | 90 | [formatters] 91 | keys = generic 92 | 93 | [logger_root] 94 | level = WARN 95 | handlers = console 96 | qualname = 97 | 98 | [logger_sqlalchemy] 99 | level = WARN 100 | handlers = 101 | qualname = sqlalchemy.engine 102 | 103 | [logger_alembic] 104 | level = INFO 105 | handlers = 106 | qualname = alembic 107 | 108 | [handler_console] 109 | class = StreamHandler 110 | args = (sys.stderr,) 111 | level = NOTSET 112 | formatter = generic 113 | 114 | [formatter_generic] 115 | format = %(levelname)-5.5s [%(name)s] %(message)s 116 | datefmt = %H:%M:%S 117 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | image: fastapi-kickstart 7 | container_name: backend 8 | ports: 9 | - "8000:8000" 10 | env_file: 11 | .env 12 | depends_on: 13 | - db 14 | volumes: 15 | - .:/app 16 | 17 | db: 18 | image: postgres:15.2-alpine 19 | container_name: postgres_db 20 | environment: 21 | POSTGRES_USER: ${POSTGRES_USER} 22 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} 23 | POSTGRES_DB: ${POSTGRES_DB} 24 | volumes: 25 | - postgres_data:/var/lib/postgresql/data 26 | ports: 27 | - "5432:5432" 28 | 29 | redis: 30 | image: redis:latest 31 | container_name: redis 32 | ports: 33 | - "6379:6379" 34 | volumes: 35 | - redis_data:/data 36 | 37 | celery: 38 | container_name: celery_worker 39 | image: fastapi-kickstart 40 | build: 41 | context: . 42 | dockerfile: Dockerfile 43 | command: ["celery", "-A", "src.celery_config.celery_app", "worker", "--loglevel=info"] 44 | depends_on: 45 | - redis 46 | - backend 47 | env_file: 48 | - .env 49 | 50 | volumes: 51 | postgres_data: 52 | redis_data: 53 | 54 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "alembic" 5 | version = "1.13.2" 6 | description = "A database migration tool for SQLAlchemy." 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, 11 | {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, 12 | ] 13 | 14 | [package.dependencies] 15 | Mako = "*" 16 | SQLAlchemy = ">=1.3.0" 17 | typing-extensions = ">=4" 18 | 19 | [package.extras] 20 | tz = ["backports.zoneinfo"] 21 | 22 | [[package]] 23 | name = "amqp" 24 | version = "5.2.0" 25 | description = "Low-level AMQP client for Python (fork of amqplib)." 26 | optional = false 27 | python-versions = ">=3.6" 28 | files = [ 29 | {file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"}, 30 | {file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"}, 31 | ] 32 | 33 | [package.dependencies] 34 | vine = ">=5.0.0,<6.0.0" 35 | 36 | [[package]] 37 | name = "annotated-types" 38 | version = "0.7.0" 39 | description = "Reusable constraint types to use with typing.Annotated" 40 | optional = false 41 | python-versions = ">=3.8" 42 | files = [ 43 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 44 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 45 | ] 46 | 47 | [[package]] 48 | name = "anyio" 49 | version = "4.4.0" 50 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 51 | optional = false 52 | python-versions = ">=3.8" 53 | files = [ 54 | {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, 55 | {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, 56 | ] 57 | 58 | [package.dependencies] 59 | idna = ">=2.8" 60 | sniffio = ">=1.1" 61 | 62 | [package.extras] 63 | doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 64 | 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)"] 65 | trio = ["trio (>=0.23)"] 66 | 67 | [[package]] 68 | name = "billiard" 69 | version = "4.2.0" 70 | description = "Python multiprocessing fork with improvements and bugfixes" 71 | optional = false 72 | python-versions = ">=3.7" 73 | files = [ 74 | {file = "billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d"}, 75 | {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, 76 | ] 77 | 78 | [[package]] 79 | name = "celery" 80 | version = "5.4.0" 81 | description = "Distributed Task Queue." 82 | optional = false 83 | python-versions = ">=3.8" 84 | files = [ 85 | {file = "celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64"}, 86 | {file = "celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706"}, 87 | ] 88 | 89 | [package.dependencies] 90 | billiard = ">=4.2.0,<5.0" 91 | click = ">=8.1.2,<9.0" 92 | click-didyoumean = ">=0.3.0" 93 | click-plugins = ">=1.1.1" 94 | click-repl = ">=0.2.0" 95 | kombu = ">=5.3.4,<6.0" 96 | python-dateutil = ">=2.8.2" 97 | redis = {version = ">=4.5.2,<4.5.5 || >4.5.5,<6.0.0", optional = true, markers = "extra == \"redis\""} 98 | tzdata = ">=2022.7" 99 | vine = ">=5.1.0,<6.0" 100 | 101 | [package.extras] 102 | arangodb = ["pyArango (>=2.0.2)"] 103 | auth = ["cryptography (==42.0.5)"] 104 | azureblockblob = ["azure-storage-blob (>=12.15.0)"] 105 | brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] 106 | cassandra = ["cassandra-driver (>=3.25.0,<4)"] 107 | consul = ["python-consul2 (==0.1.5)"] 108 | cosmosdbsql = ["pydocumentdb (==2.3.5)"] 109 | couchbase = ["couchbase (>=3.0.0)"] 110 | couchdb = ["pycouchdb (==1.14.2)"] 111 | django = ["Django (>=2.2.28)"] 112 | dynamodb = ["boto3 (>=1.26.143)"] 113 | elasticsearch = ["elastic-transport (<=8.13.0)", "elasticsearch (<=8.13.0)"] 114 | eventlet = ["eventlet (>=0.32.0)"] 115 | gcs = ["google-cloud-storage (>=2.10.0)"] 116 | gevent = ["gevent (>=1.5.0)"] 117 | librabbitmq = ["librabbitmq (>=2.0.0)"] 118 | memcache = ["pylibmc (==1.6.3)"] 119 | mongodb = ["pymongo[srv] (>=4.0.2)"] 120 | msgpack = ["msgpack (==1.0.8)"] 121 | pymemcache = ["python-memcached (>=1.61)"] 122 | pyro = ["pyro4 (==4.82)"] 123 | pytest = ["pytest-celery[all] (>=1.0.0)"] 124 | redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] 125 | s3 = ["boto3 (>=1.26.143)"] 126 | slmq = ["softlayer-messaging (>=1.0.3)"] 127 | solar = ["ephem (==4.1.5)"] 128 | sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] 129 | sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.4)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] 130 | tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] 131 | yaml = ["PyYAML (>=3.10)"] 132 | zookeeper = ["kazoo (>=1.3.1)"] 133 | zstd = ["zstandard (==0.22.0)"] 134 | 135 | [[package]] 136 | name = "certifi" 137 | version = "2024.8.30" 138 | description = "Python package for providing Mozilla's CA Bundle." 139 | optional = false 140 | python-versions = ">=3.6" 141 | files = [ 142 | {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, 143 | {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, 144 | ] 145 | 146 | [[package]] 147 | name = "charset-normalizer" 148 | version = "3.3.2" 149 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 150 | optional = false 151 | python-versions = ">=3.7.0" 152 | files = [ 153 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 154 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 155 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 156 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 157 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 158 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 159 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 160 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 161 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 162 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 163 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 164 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 165 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 166 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 167 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 168 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 169 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 170 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 171 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 172 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 173 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 174 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 175 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 176 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 177 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 178 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 179 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 180 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 181 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 182 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 183 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 184 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 185 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 186 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 187 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 188 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 189 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 190 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 191 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 192 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 193 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 194 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 195 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 196 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 197 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 198 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 199 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 200 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 201 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 202 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 203 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 204 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 205 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 206 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 207 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 208 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 209 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 210 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 211 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 212 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 213 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 214 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 215 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 216 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 217 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 218 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 219 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 220 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 221 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 222 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 223 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 224 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 225 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 226 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 227 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 228 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 229 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 230 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 231 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 232 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 233 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 234 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 235 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 236 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 237 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 238 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 239 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 240 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 241 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 242 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 243 | ] 244 | 245 | [[package]] 246 | name = "click" 247 | version = "8.1.7" 248 | description = "Composable command line interface toolkit" 249 | optional = false 250 | python-versions = ">=3.7" 251 | files = [ 252 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 253 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 254 | ] 255 | 256 | [package.dependencies] 257 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 258 | 259 | [[package]] 260 | name = "click-didyoumean" 261 | version = "0.3.1" 262 | description = "Enables git-like *did-you-mean* feature in click" 263 | optional = false 264 | python-versions = ">=3.6.2" 265 | files = [ 266 | {file = "click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c"}, 267 | {file = "click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463"}, 268 | ] 269 | 270 | [package.dependencies] 271 | click = ">=7" 272 | 273 | [[package]] 274 | name = "click-plugins" 275 | version = "1.1.1" 276 | description = "An extension module for click to enable registering CLI commands via setuptools entry-points." 277 | optional = false 278 | python-versions = "*" 279 | files = [ 280 | {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, 281 | {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, 282 | ] 283 | 284 | [package.dependencies] 285 | click = ">=4.0" 286 | 287 | [package.extras] 288 | dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] 289 | 290 | [[package]] 291 | name = "click-repl" 292 | version = "0.3.0" 293 | description = "REPL plugin for Click" 294 | optional = false 295 | python-versions = ">=3.6" 296 | files = [ 297 | {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, 298 | {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, 299 | ] 300 | 301 | [package.dependencies] 302 | click = ">=7.0" 303 | prompt-toolkit = ">=3.0.36" 304 | 305 | [package.extras] 306 | testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] 307 | 308 | [[package]] 309 | name = "colorama" 310 | version = "0.4.6" 311 | description = "Cross-platform colored terminal text." 312 | optional = false 313 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 314 | files = [ 315 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 316 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 317 | ] 318 | 319 | [[package]] 320 | name = "coverage" 321 | version = "7.6.1" 322 | description = "Code coverage measurement for Python" 323 | optional = false 324 | python-versions = ">=3.8" 325 | files = [ 326 | {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, 327 | {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, 328 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, 329 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, 330 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, 331 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, 332 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, 333 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, 334 | {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, 335 | {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, 336 | {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, 337 | {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, 338 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, 339 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, 340 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, 341 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, 342 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, 343 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, 344 | {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, 345 | {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, 346 | {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, 347 | {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, 348 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, 349 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, 350 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, 351 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, 352 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, 353 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, 354 | {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, 355 | {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, 356 | {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, 357 | {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, 358 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, 359 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, 360 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, 361 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, 362 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, 363 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, 364 | {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, 365 | {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, 366 | {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, 367 | {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, 368 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, 369 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, 370 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, 371 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, 372 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, 373 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, 374 | {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, 375 | {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, 376 | {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, 377 | {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, 378 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, 379 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, 380 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, 381 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, 382 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, 383 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, 384 | {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, 385 | {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, 386 | {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, 387 | {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, 388 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, 389 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, 390 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, 391 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, 392 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, 393 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, 394 | {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, 395 | {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, 396 | {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, 397 | {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, 398 | ] 399 | 400 | [package.extras] 401 | toml = ["tomli"] 402 | 403 | [[package]] 404 | name = "debugpy" 405 | version = "1.8.5" 406 | description = "An implementation of the Debug Adapter Protocol for Python" 407 | optional = false 408 | python-versions = ">=3.8" 409 | files = [ 410 | {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, 411 | {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, 412 | {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, 413 | {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, 414 | {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, 415 | {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, 416 | {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, 417 | {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, 418 | {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, 419 | {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, 420 | {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, 421 | {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, 422 | {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, 423 | {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, 424 | {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, 425 | {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, 426 | {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, 427 | {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, 428 | {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, 429 | {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, 430 | {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, 431 | {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, 432 | ] 433 | 434 | [[package]] 435 | name = "dnspython" 436 | version = "2.7.0" 437 | description = "DNS toolkit" 438 | optional = false 439 | python-versions = ">=3.9" 440 | files = [ 441 | {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, 442 | {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, 443 | ] 444 | 445 | [package.extras] 446 | dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] 447 | dnssec = ["cryptography (>=43)"] 448 | doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] 449 | doq = ["aioquic (>=1.0.0)"] 450 | idna = ["idna (>=3.7)"] 451 | trio = ["trio (>=0.23)"] 452 | wmi = ["wmi (>=1.5.1)"] 453 | 454 | [[package]] 455 | name = "docker" 456 | version = "7.1.0" 457 | description = "A Python library for the Docker Engine API." 458 | optional = false 459 | python-versions = ">=3.8" 460 | files = [ 461 | {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, 462 | {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, 463 | ] 464 | 465 | [package.dependencies] 466 | pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} 467 | requests = ">=2.26.0" 468 | urllib3 = ">=1.26.0" 469 | 470 | [package.extras] 471 | dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] 472 | docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] 473 | ssh = ["paramiko (>=2.4.3)"] 474 | websockets = ["websocket-client (>=1.3.0)"] 475 | 476 | [[package]] 477 | name = "email-validator" 478 | version = "2.2.0" 479 | description = "A robust email address syntax and deliverability validation library." 480 | optional = false 481 | python-versions = ">=3.8" 482 | files = [ 483 | {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, 484 | {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, 485 | ] 486 | 487 | [package.dependencies] 488 | dnspython = ">=2.0.0" 489 | idna = ">=2.0.0" 490 | 491 | [[package]] 492 | name = "fastapi" 493 | version = "0.112.4" 494 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 495 | optional = false 496 | python-versions = ">=3.8" 497 | files = [ 498 | {file = "fastapi-0.112.4-py3-none-any.whl", hash = "sha256:6d4f9c3301825d4620665cace8e2bc34e303f61c05a5382d1d61a048ea7f2f37"}, 499 | {file = "fastapi-0.112.4.tar.gz", hash = "sha256:b1f72e1f72afe7902ccd639ba320abb5d57a309804f45c10ab0ce3693cadeb33"}, 500 | ] 501 | 502 | [package.dependencies] 503 | 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" 504 | starlette = ">=0.37.2,<0.39.0" 505 | typing-extensions = ">=4.8.0" 506 | 507 | [package.extras] 508 | all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "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.7)", "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)"] 509 | standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] 510 | 511 | [[package]] 512 | name = "greenlet" 513 | version = "3.1.0" 514 | description = "Lightweight in-process concurrent programming" 515 | optional = false 516 | python-versions = ">=3.7" 517 | files = [ 518 | {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, 519 | {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, 520 | {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, 521 | {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, 522 | {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, 523 | {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, 524 | {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, 525 | {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, 526 | {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, 527 | {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, 528 | {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, 529 | {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, 530 | {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, 531 | {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, 532 | {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, 533 | {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, 534 | {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, 535 | {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, 536 | {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, 537 | {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, 538 | {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, 539 | {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, 540 | {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, 541 | {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, 542 | {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, 543 | {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, 544 | {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, 545 | {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, 546 | {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, 547 | {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, 548 | {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, 549 | {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, 550 | {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, 551 | {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, 552 | {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, 553 | {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, 554 | {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0"}, 555 | {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6"}, 556 | {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a"}, 557 | {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df"}, 558 | {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637"}, 559 | {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954"}, 560 | {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3"}, 561 | {file = "greenlet-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00"}, 562 | {file = "greenlet-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6"}, 563 | {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, 564 | {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, 565 | {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, 566 | {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, 567 | {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, 568 | {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, 569 | {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, 570 | {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, 571 | {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, 572 | {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, 573 | {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, 574 | {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, 575 | {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, 576 | {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, 577 | {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, 578 | {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, 579 | {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, 580 | {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, 581 | {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, 582 | {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, 583 | {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, 584 | ] 585 | 586 | [package.extras] 587 | docs = ["Sphinx", "furo"] 588 | test = ["objgraph", "psutil"] 589 | 590 | [[package]] 591 | name = "gunicorn" 592 | version = "23.0.0" 593 | description = "WSGI HTTP Server for UNIX" 594 | optional = false 595 | python-versions = ">=3.7" 596 | files = [ 597 | {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, 598 | {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, 599 | ] 600 | 601 | [package.dependencies] 602 | packaging = "*" 603 | 604 | [package.extras] 605 | eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] 606 | gevent = ["gevent (>=1.4.0)"] 607 | setproctitle = ["setproctitle"] 608 | testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] 609 | tornado = ["tornado (>=0.2)"] 610 | 611 | [[package]] 612 | name = "h11" 613 | version = "0.14.0" 614 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 615 | optional = false 616 | python-versions = ">=3.7" 617 | files = [ 618 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 619 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 620 | ] 621 | 622 | [[package]] 623 | name = "httpcore" 624 | version = "1.0.5" 625 | description = "A minimal low-level HTTP client." 626 | optional = false 627 | python-versions = ">=3.8" 628 | files = [ 629 | {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, 630 | {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, 631 | ] 632 | 633 | [package.dependencies] 634 | certifi = "*" 635 | h11 = ">=0.13,<0.15" 636 | 637 | [package.extras] 638 | asyncio = ["anyio (>=4.0,<5.0)"] 639 | http2 = ["h2 (>=3,<5)"] 640 | socks = ["socksio (==1.*)"] 641 | trio = ["trio (>=0.22.0,<0.26.0)"] 642 | 643 | [[package]] 644 | name = "httpx" 645 | version = "0.27.2" 646 | description = "The next generation HTTP client." 647 | optional = false 648 | python-versions = ">=3.8" 649 | files = [ 650 | {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, 651 | {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, 652 | ] 653 | 654 | [package.dependencies] 655 | anyio = "*" 656 | certifi = "*" 657 | httpcore = "==1.*" 658 | idna = "*" 659 | sniffio = "*" 660 | 661 | [package.extras] 662 | brotli = ["brotli", "brotlicffi"] 663 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] 664 | http2 = ["h2 (>=3,<5)"] 665 | socks = ["socksio (==1.*)"] 666 | zstd = ["zstandard (>=0.18.0)"] 667 | 668 | [[package]] 669 | name = "idna" 670 | version = "3.10" 671 | description = "Internationalized Domain Names in Applications (IDNA)" 672 | optional = false 673 | python-versions = ">=3.6" 674 | files = [ 675 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 676 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 677 | ] 678 | 679 | [package.extras] 680 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 681 | 682 | [[package]] 683 | name = "inflection" 684 | version = "0.5.1" 685 | description = "A port of Ruby on Rails inflector to Python" 686 | optional = false 687 | python-versions = ">=3.5" 688 | files = [ 689 | {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, 690 | {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, 691 | ] 692 | 693 | [[package]] 694 | name = "iniconfig" 695 | version = "2.0.0" 696 | description = "brain-dead simple config-ini parsing" 697 | optional = false 698 | python-versions = ">=3.7" 699 | files = [ 700 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 701 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 702 | ] 703 | 704 | [[package]] 705 | name = "kombu" 706 | version = "5.4.1" 707 | description = "Messaging library for Python." 708 | optional = false 709 | python-versions = ">=3.8" 710 | files = [ 711 | {file = "kombu-5.4.1-py3-none-any.whl", hash = "sha256:621d365f234e4c089596f3a2510f1ade07026efc28caca426161d8f458786cab"}, 712 | {file = "kombu-5.4.1.tar.gz", hash = "sha256:1c05178826dab811f8cab5b0a154d42a7a33d8bcdde9fa3d7b4582e43c3c03db"}, 713 | ] 714 | 715 | [package.dependencies] 716 | amqp = ">=5.1.1,<6.0.0" 717 | vine = "5.1.0" 718 | 719 | [package.extras] 720 | azureservicebus = ["azure-servicebus (>=7.10.0)"] 721 | azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] 722 | confluentkafka = ["confluent-kafka (>=2.2.0)"] 723 | consul = ["python-consul2 (==0.1.5)"] 724 | librabbitmq = ["librabbitmq (>=2.0.0)"] 725 | mongodb = ["pymongo (>=4.1.1)"] 726 | msgpack = ["msgpack (==1.1.0)"] 727 | pyro = ["pyro4 (==4.82)"] 728 | qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] 729 | redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2)"] 730 | slmq = ["softlayer-messaging (>=1.0.3)"] 731 | sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] 732 | sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] 733 | yaml = ["PyYAML (>=3.10)"] 734 | zookeeper = ["kazoo (>=2.8.0)"] 735 | 736 | [[package]] 737 | name = "mako" 738 | version = "1.3.5" 739 | description = "A super-fast templating language that borrows the best ideas from the existing templating languages." 740 | optional = false 741 | python-versions = ">=3.8" 742 | files = [ 743 | {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, 744 | {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, 745 | ] 746 | 747 | [package.dependencies] 748 | MarkupSafe = ">=0.9.2" 749 | 750 | [package.extras] 751 | babel = ["Babel"] 752 | lingua = ["lingua"] 753 | testing = ["pytest"] 754 | 755 | [[package]] 756 | name = "markupsafe" 757 | version = "2.1.5" 758 | description = "Safely add untrusted strings to HTML/XML markup." 759 | optional = false 760 | python-versions = ">=3.7" 761 | files = [ 762 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 763 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 764 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 765 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 766 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 767 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 768 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 769 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 770 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 771 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 772 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 773 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 774 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 775 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 776 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 777 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 778 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 779 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 780 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 781 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 782 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 783 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 784 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 785 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 786 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 787 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 788 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 789 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 790 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 791 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 792 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 793 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 794 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 795 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 796 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 797 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 798 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 799 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 800 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 801 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 802 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 803 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 804 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 805 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 806 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 807 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 808 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 809 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 810 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 811 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 812 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 813 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 814 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 815 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 816 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 817 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 818 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 819 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 820 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 821 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 822 | ] 823 | 824 | [[package]] 825 | name = "mypy" 826 | version = "1.13.0" 827 | description = "Optional static typing for Python" 828 | optional = false 829 | python-versions = ">=3.8" 830 | files = [ 831 | {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, 832 | {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, 833 | {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, 834 | {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, 835 | {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, 836 | {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, 837 | {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, 838 | {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, 839 | {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, 840 | {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, 841 | {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, 842 | {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, 843 | {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, 844 | {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, 845 | {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, 846 | {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, 847 | {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, 848 | {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, 849 | {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, 850 | {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, 851 | {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, 852 | {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, 853 | {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, 854 | {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, 855 | {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, 856 | {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, 857 | {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, 858 | {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, 859 | {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, 860 | {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, 861 | {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, 862 | {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, 863 | ] 864 | 865 | [package.dependencies] 866 | mypy-extensions = ">=1.0.0" 867 | typing-extensions = ">=4.6.0" 868 | 869 | [package.extras] 870 | dmypy = ["psutil (>=4.0)"] 871 | faster-cache = ["orjson"] 872 | install-types = ["pip"] 873 | mypyc = ["setuptools (>=50)"] 874 | reports = ["lxml"] 875 | 876 | [[package]] 877 | name = "mypy-extensions" 878 | version = "1.0.0" 879 | description = "Type system extensions for programs checked with the mypy type checker." 880 | optional = false 881 | python-versions = ">=3.5" 882 | files = [ 883 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 884 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 885 | ] 886 | 887 | [[package]] 888 | name = "packaging" 889 | version = "24.1" 890 | description = "Core utilities for Python packages" 891 | optional = false 892 | python-versions = ">=3.8" 893 | files = [ 894 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 895 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 896 | ] 897 | 898 | [[package]] 899 | name = "passlib" 900 | version = "1.7.4" 901 | description = "comprehensive password hashing framework supporting over 30 schemes" 902 | optional = false 903 | python-versions = "*" 904 | files = [ 905 | {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, 906 | {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, 907 | ] 908 | 909 | [package.extras] 910 | argon2 = ["argon2-cffi (>=18.2.0)"] 911 | bcrypt = ["bcrypt (>=3.1.0)"] 912 | build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] 913 | totp = ["cryptography"] 914 | 915 | [[package]] 916 | name = "pluggy" 917 | version = "1.5.0" 918 | description = "plugin and hook calling mechanisms for python" 919 | optional = false 920 | python-versions = ">=3.8" 921 | files = [ 922 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 923 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 924 | ] 925 | 926 | [package.extras] 927 | dev = ["pre-commit", "tox"] 928 | testing = ["pytest", "pytest-benchmark"] 929 | 930 | [[package]] 931 | name = "prompt-toolkit" 932 | version = "3.0.47" 933 | description = "Library for building powerful interactive command lines in Python" 934 | optional = false 935 | python-versions = ">=3.7.0" 936 | files = [ 937 | {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, 938 | {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, 939 | ] 940 | 941 | [package.dependencies] 942 | wcwidth = "*" 943 | 944 | [[package]] 945 | name = "psutil" 946 | version = "6.0.0" 947 | description = "Cross-platform lib for process and system monitoring in Python." 948 | optional = false 949 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 950 | files = [ 951 | {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, 952 | {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, 953 | {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, 954 | {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, 955 | {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, 956 | {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, 957 | {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, 958 | {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, 959 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, 960 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, 961 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, 962 | {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, 963 | {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, 964 | {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, 965 | {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, 966 | {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, 967 | {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, 968 | ] 969 | 970 | [package.extras] 971 | test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] 972 | 973 | [[package]] 974 | name = "psycopg" 975 | version = "3.2.1" 976 | description = "PostgreSQL database adapter for Python" 977 | optional = false 978 | python-versions = ">=3.8" 979 | files = [ 980 | {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, 981 | {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, 982 | ] 983 | 984 | [package.dependencies] 985 | psycopg-binary = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} 986 | typing-extensions = ">=4.4" 987 | tzdata = {version = "*", markers = "sys_platform == \"win32\""} 988 | 989 | [package.extras] 990 | binary = ["psycopg-binary (==3.2.1)"] 991 | c = ["psycopg-c (==3.2.1)"] 992 | dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] 993 | docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] 994 | pool = ["psycopg-pool"] 995 | test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] 996 | 997 | [[package]] 998 | name = "psycopg-binary" 999 | version = "3.2.1" 1000 | description = "PostgreSQL database adapter for Python -- C optimisation distribution" 1001 | optional = false 1002 | python-versions = ">=3.8" 1003 | files = [ 1004 | {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d"}, 1005 | {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f"}, 1006 | {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291"}, 1007 | {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a"}, 1008 | {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e"}, 1009 | {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879"}, 1010 | {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935"}, 1011 | {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea"}, 1012 | {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938"}, 1013 | {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd"}, 1014 | {file = "psycopg_binary-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489"}, 1015 | {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f"}, 1016 | {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7"}, 1017 | {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805"}, 1018 | {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040"}, 1019 | {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3"}, 1020 | {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b"}, 1021 | {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2"}, 1022 | {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68"}, 1023 | {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960"}, 1024 | {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b"}, 1025 | {file = "psycopg_binary-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f"}, 1026 | {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60"}, 1027 | {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db"}, 1028 | {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7"}, 1029 | {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707"}, 1030 | {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42"}, 1031 | {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e"}, 1032 | {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35"}, 1033 | {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1"}, 1034 | {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a"}, 1035 | {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f"}, 1036 | {file = "psycopg_binary-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674"}, 1037 | {file = "psycopg_binary-3.2.1-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788"}, 1038 | {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9"}, 1039 | {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555"}, 1040 | {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1"}, 1041 | {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801"}, 1042 | {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51"}, 1043 | {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c"}, 1044 | {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7"}, 1045 | {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22"}, 1046 | {file = "psycopg_binary-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46"}, 1047 | {file = "psycopg_binary-3.2.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9"}, 1048 | {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7"}, 1049 | {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d"}, 1050 | {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb"}, 1051 | {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef"}, 1052 | {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1"}, 1053 | {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5"}, 1054 | {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f"}, 1055 | {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2"}, 1056 | {file = "psycopg_binary-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073"}, 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "pydantic" 1061 | version = "2.9.2" 1062 | description = "Data validation using Python type hints" 1063 | optional = false 1064 | python-versions = ">=3.8" 1065 | files = [ 1066 | {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, 1067 | {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, 1068 | ] 1069 | 1070 | [package.dependencies] 1071 | annotated-types = ">=0.6.0" 1072 | email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} 1073 | pydantic-core = "2.23.4" 1074 | typing-extensions = [ 1075 | {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, 1076 | {version = ">=4.6.1", markers = "python_version < \"3.13\""}, 1077 | ] 1078 | 1079 | [package.extras] 1080 | email = ["email-validator (>=2.0.0)"] 1081 | timezone = ["tzdata"] 1082 | 1083 | [[package]] 1084 | name = "pydantic-core" 1085 | version = "2.23.4" 1086 | description = "Core functionality for Pydantic validation and serialization" 1087 | optional = false 1088 | python-versions = ">=3.8" 1089 | files = [ 1090 | {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, 1091 | {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, 1092 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, 1093 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, 1094 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, 1095 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, 1096 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, 1097 | {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, 1098 | {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, 1099 | {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, 1100 | {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, 1101 | {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, 1102 | {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, 1103 | {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, 1104 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, 1105 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, 1106 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, 1107 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, 1108 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, 1109 | {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, 1110 | {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, 1111 | {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, 1112 | {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, 1113 | {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, 1114 | {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, 1115 | {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, 1116 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, 1117 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, 1118 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, 1119 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, 1120 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, 1121 | {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, 1122 | {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, 1123 | {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, 1124 | {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, 1125 | {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, 1126 | {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, 1127 | {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, 1128 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, 1129 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, 1130 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, 1131 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, 1132 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, 1133 | {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, 1134 | {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, 1135 | {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, 1136 | {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, 1137 | {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, 1138 | {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, 1139 | {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, 1140 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, 1141 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, 1142 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, 1143 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, 1144 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, 1145 | {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, 1146 | {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, 1147 | {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, 1148 | {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, 1149 | {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, 1150 | {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, 1151 | {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, 1152 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, 1153 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, 1154 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, 1155 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, 1156 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, 1157 | {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, 1158 | {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, 1159 | {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, 1160 | {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, 1161 | {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, 1162 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, 1163 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, 1164 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, 1165 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, 1166 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, 1167 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, 1168 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, 1169 | {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, 1170 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, 1171 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, 1172 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, 1173 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, 1174 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, 1175 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, 1176 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, 1177 | {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, 1178 | {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, 1179 | ] 1180 | 1181 | [package.dependencies] 1182 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 1183 | 1184 | [[package]] 1185 | name = "pydantic-settings" 1186 | version = "2.5.2" 1187 | description = "Settings management using Pydantic" 1188 | optional = false 1189 | python-versions = ">=3.8" 1190 | files = [ 1191 | {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, 1192 | {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, 1193 | ] 1194 | 1195 | [package.dependencies] 1196 | pydantic = ">=2.7.0" 1197 | python-dotenv = ">=0.21.0" 1198 | 1199 | [package.extras] 1200 | azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] 1201 | toml = ["tomli (>=2.0.1)"] 1202 | yaml = ["pyyaml (>=6.0.1)"] 1203 | 1204 | [[package]] 1205 | name = "pyjwt" 1206 | version = "2.9.0" 1207 | description = "JSON Web Token implementation in Python" 1208 | optional = false 1209 | python-versions = ">=3.8" 1210 | files = [ 1211 | {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, 1212 | {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, 1213 | ] 1214 | 1215 | [package.extras] 1216 | crypto = ["cryptography (>=3.4.0)"] 1217 | dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] 1218 | docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] 1219 | tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] 1220 | 1221 | [[package]] 1222 | name = "pytest" 1223 | version = "8.3.3" 1224 | description = "pytest: simple powerful testing with Python" 1225 | optional = false 1226 | python-versions = ">=3.8" 1227 | files = [ 1228 | {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, 1229 | {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, 1230 | ] 1231 | 1232 | [package.dependencies] 1233 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 1234 | iniconfig = "*" 1235 | packaging = "*" 1236 | pluggy = ">=1.5,<2" 1237 | 1238 | [package.extras] 1239 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 1240 | 1241 | [[package]] 1242 | name = "pytest-celery" 1243 | version = "1.1.2" 1244 | description = "Pytest plugin for Celery" 1245 | optional = false 1246 | python-versions = "<4.0,>=3.8" 1247 | files = [ 1248 | {file = "pytest_celery-1.1.2-py3-none-any.whl", hash = "sha256:e117cf2aa5164f99d7ac8296bd3dee16413faa0153d9b5de2c9f9242c60f350b"}, 1249 | {file = "pytest_celery-1.1.2.tar.gz", hash = "sha256:b02cfab5775dd74af99e03ab963047fe1d980dc40f1da19934bf1142d11fa398"}, 1250 | ] 1251 | 1252 | [package.dependencies] 1253 | celery = "*" 1254 | debugpy = ">=1.8.5,<2.0.0" 1255 | docker = ">=7.1.0,<8.0.0" 1256 | psutil = ">=6.0.0" 1257 | pytest-docker-tools = ">=3.1.3" 1258 | setuptools = ">=74.1.2" 1259 | tenacity = ">=9.0.0" 1260 | 1261 | [package.extras] 1262 | all = ["boto3", "botocore", "pycurl", "python-memcached", "redis", "urllib3"] 1263 | memcached = ["python-memcached"] 1264 | redis = ["redis"] 1265 | sqs = ["boto3", "botocore", "pycurl", "urllib3"] 1266 | 1267 | [[package]] 1268 | name = "pytest-cov" 1269 | version = "5.0.0" 1270 | description = "Pytest plugin for measuring coverage." 1271 | optional = false 1272 | python-versions = ">=3.8" 1273 | files = [ 1274 | {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, 1275 | {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, 1276 | ] 1277 | 1278 | [package.dependencies] 1279 | coverage = {version = ">=5.2.1", extras = ["toml"]} 1280 | pytest = ">=4.6" 1281 | 1282 | [package.extras] 1283 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] 1284 | 1285 | [[package]] 1286 | name = "pytest-docker-tools" 1287 | version = "3.1.3" 1288 | description = "Docker integration tests for pytest" 1289 | optional = false 1290 | python-versions = ">=3.7.0,<4.0.0" 1291 | files = [ 1292 | {file = "pytest_docker_tools-3.1.3-py3-none-any.whl", hash = "sha256:63e659043160f41d89f94ea42616102594bcc85682aac394fcbc14f14cd1b189"}, 1293 | {file = "pytest_docker_tools-3.1.3.tar.gz", hash = "sha256:c7e28841839d67b3ac80ad7b345b953701d5ae61ffda97586114244292aeacc0"}, 1294 | ] 1295 | 1296 | [package.dependencies] 1297 | docker = ">=4.3.1" 1298 | pytest = ">=6.0.1" 1299 | 1300 | [[package]] 1301 | name = "pytest-sugar" 1302 | version = "1.0.0" 1303 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 1304 | optional = false 1305 | python-versions = "*" 1306 | files = [ 1307 | {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, 1308 | {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, 1309 | ] 1310 | 1311 | [package.dependencies] 1312 | packaging = ">=21.3" 1313 | pytest = ">=6.2.0" 1314 | termcolor = ">=2.1.0" 1315 | 1316 | [package.extras] 1317 | dev = ["black", "flake8", "pre-commit"] 1318 | 1319 | [[package]] 1320 | name = "python-dateutil" 1321 | version = "2.9.0.post0" 1322 | description = "Extensions to the standard Python datetime module" 1323 | optional = false 1324 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1325 | files = [ 1326 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 1327 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 1328 | ] 1329 | 1330 | [package.dependencies] 1331 | six = ">=1.5" 1332 | 1333 | [[package]] 1334 | name = "python-dotenv" 1335 | version = "1.0.1" 1336 | description = "Read key-value pairs from a .env file and set them as environment variables" 1337 | optional = false 1338 | python-versions = ">=3.8" 1339 | files = [ 1340 | {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, 1341 | {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, 1342 | ] 1343 | 1344 | [package.extras] 1345 | cli = ["click (>=5.0)"] 1346 | 1347 | [[package]] 1348 | name = "python-multipart" 1349 | version = "0.0.16" 1350 | description = "A streaming multipart parser for Python" 1351 | optional = false 1352 | python-versions = ">=3.8" 1353 | files = [ 1354 | {file = "python_multipart-0.0.16-py3-none-any.whl", hash = "sha256:c2759b7b976ef3937214dfb592446b59dfaa5f04682a076f78b117c94776d87a"}, 1355 | {file = "python_multipart-0.0.16.tar.gz", hash = "sha256:8dee37b88dab9b59922ca173c35acb627cc12ec74019f5cd4578369c6df36554"}, 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "pywin32" 1360 | version = "306" 1361 | description = "Python for Window Extensions" 1362 | optional = false 1363 | python-versions = "*" 1364 | files = [ 1365 | {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, 1366 | {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, 1367 | {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, 1368 | {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, 1369 | {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, 1370 | {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, 1371 | {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, 1372 | {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, 1373 | {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, 1374 | {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, 1375 | {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, 1376 | {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, 1377 | {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, 1378 | {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "redis" 1383 | version = "5.0.8" 1384 | description = "Python client for Redis database and key-value store" 1385 | optional = false 1386 | python-versions = ">=3.7" 1387 | files = [ 1388 | {file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"}, 1389 | {file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"}, 1390 | ] 1391 | 1392 | [package.extras] 1393 | hiredis = ["hiredis (>1.0.0)"] 1394 | ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] 1395 | 1396 | [[package]] 1397 | name = "requests" 1398 | version = "2.32.3" 1399 | description = "Python HTTP for Humans." 1400 | optional = false 1401 | python-versions = ">=3.8" 1402 | files = [ 1403 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 1404 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 1405 | ] 1406 | 1407 | [package.dependencies] 1408 | certifi = ">=2017.4.17" 1409 | charset-normalizer = ">=2,<4" 1410 | idna = ">=2.5,<4" 1411 | urllib3 = ">=1.21.1,<3" 1412 | 1413 | [package.extras] 1414 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1415 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1416 | 1417 | [[package]] 1418 | name = "ruff" 1419 | version = "0.6.5" 1420 | description = "An extremely fast Python linter and code formatter, written in Rust." 1421 | optional = false 1422 | python-versions = ">=3.7" 1423 | files = [ 1424 | {file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"}, 1425 | {file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"}, 1426 | {file = "ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680"}, 1427 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f"}, 1428 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972"}, 1429 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200"}, 1430 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276"}, 1431 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810"}, 1432 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178"}, 1433 | {file = "ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253"}, 1434 | {file = "ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19"}, 1435 | {file = "ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c"}, 1436 | {file = "ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae"}, 1437 | {file = "ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc"}, 1438 | {file = "ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5"}, 1439 | {file = "ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9"}, 1440 | {file = "ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0"}, 1441 | {file = "ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb"}, 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "setuptools" 1446 | version = "75.0.0" 1447 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1448 | optional = false 1449 | python-versions = ">=3.8" 1450 | files = [ 1451 | {file = "setuptools-75.0.0-py3-none-any.whl", hash = "sha256:791ae94f04f78c880b5e614e560dd32d4b4af5d151bd9e7483e3377846caf90a"}, 1452 | {file = "setuptools-75.0.0.tar.gz", hash = "sha256:25af69c809d9334cd8e653d385277abeb5a102dca255954005a7092d282575ea"}, 1453 | ] 1454 | 1455 | [package.extras] 1456 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] 1457 | core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] 1458 | cover = ["pytest-cov"] 1459 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] 1460 | enabler = ["pytest-enabler (>=2.2)"] 1461 | test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] 1462 | type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] 1463 | 1464 | [[package]] 1465 | name = "six" 1466 | version = "1.16.0" 1467 | description = "Python 2 and 3 compatibility utilities" 1468 | optional = false 1469 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1470 | files = [ 1471 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1472 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "sniffio" 1477 | version = "1.3.1" 1478 | description = "Sniff out which async library your code is running under" 1479 | optional = false 1480 | python-versions = ">=3.7" 1481 | files = [ 1482 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 1483 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "sqlalchemy" 1488 | version = "2.0.36" 1489 | description = "Database Abstraction Library" 1490 | optional = false 1491 | python-versions = ">=3.7" 1492 | files = [ 1493 | {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, 1494 | {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, 1495 | {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, 1496 | {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, 1497 | {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, 1498 | {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, 1499 | {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, 1500 | {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, 1501 | {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, 1502 | {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, 1503 | {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, 1504 | {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, 1505 | {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, 1506 | {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, 1507 | {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, 1508 | {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, 1509 | {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, 1510 | {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, 1511 | {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, 1512 | {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, 1513 | {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, 1514 | {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, 1515 | {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, 1516 | {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, 1517 | {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, 1518 | {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, 1519 | {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, 1520 | {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, 1521 | {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, 1522 | {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, 1523 | {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, 1524 | {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, 1525 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, 1526 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, 1527 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, 1528 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, 1529 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, 1530 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, 1531 | {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, 1532 | {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, 1533 | {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, 1534 | {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, 1535 | {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, 1536 | {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, 1537 | {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, 1538 | {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, 1539 | {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, 1540 | {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, 1541 | {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, 1542 | {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, 1543 | {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, 1544 | {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, 1545 | {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, 1546 | {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, 1547 | {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, 1548 | {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, 1549 | {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, 1550 | ] 1551 | 1552 | [package.dependencies] 1553 | greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (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\")"} 1554 | mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""} 1555 | typing-extensions = ">=4.6.0" 1556 | 1557 | [package.extras] 1558 | aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] 1559 | aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] 1560 | aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] 1561 | asyncio = ["greenlet (!=0.4.17)"] 1562 | asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] 1563 | mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] 1564 | mssql = ["pyodbc"] 1565 | mssql-pymssql = ["pymssql"] 1566 | mssql-pyodbc = ["pyodbc"] 1567 | mypy = ["mypy (>=0.910)"] 1568 | mysql = ["mysqlclient (>=1.4.0)"] 1569 | mysql-connector = ["mysql-connector-python"] 1570 | oracle = ["cx_oracle (>=8)"] 1571 | oracle-oracledb = ["oracledb (>=1.0.1)"] 1572 | postgresql = ["psycopg2 (>=2.7)"] 1573 | postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] 1574 | postgresql-pg8000 = ["pg8000 (>=1.29.1)"] 1575 | postgresql-psycopg = ["psycopg (>=3.0.7)"] 1576 | postgresql-psycopg2binary = ["psycopg2-binary"] 1577 | postgresql-psycopg2cffi = ["psycopg2cffi"] 1578 | postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] 1579 | pymysql = ["pymysql"] 1580 | sqlcipher = ["sqlcipher3_binary"] 1581 | 1582 | [[package]] 1583 | name = "starlette" 1584 | version = "0.38.5" 1585 | description = "The little ASGI library that shines." 1586 | optional = false 1587 | python-versions = ">=3.8" 1588 | files = [ 1589 | {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, 1590 | {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, 1591 | ] 1592 | 1593 | [package.dependencies] 1594 | anyio = ">=3.4.0,<5" 1595 | 1596 | [package.extras] 1597 | full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] 1598 | 1599 | [[package]] 1600 | name = "tenacity" 1601 | version = "9.0.0" 1602 | description = "Retry code until it succeeds" 1603 | optional = false 1604 | python-versions = ">=3.8" 1605 | files = [ 1606 | {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, 1607 | {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, 1608 | ] 1609 | 1610 | [package.extras] 1611 | doc = ["reno", "sphinx"] 1612 | test = ["pytest", "tornado (>=4.5)", "typeguard"] 1613 | 1614 | [[package]] 1615 | name = "termcolor" 1616 | version = "2.4.0" 1617 | description = "ANSI color formatting for output in terminal" 1618 | optional = false 1619 | python-versions = ">=3.8" 1620 | files = [ 1621 | {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, 1622 | {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, 1623 | ] 1624 | 1625 | [package.extras] 1626 | tests = ["pytest", "pytest-cov"] 1627 | 1628 | [[package]] 1629 | name = "types-cryptography" 1630 | version = "3.3.23.2" 1631 | description = "Typing stubs for cryptography" 1632 | optional = false 1633 | python-versions = "*" 1634 | files = [ 1635 | {file = "types-cryptography-3.3.23.2.tar.gz", hash = "sha256:09cc53f273dd4d8c29fa7ad11fefd9b734126d467960162397bc5e3e604dea75"}, 1636 | {file = "types_cryptography-3.3.23.2-py3-none-any.whl", hash = "sha256:b965d548f148f8e87f353ccf2b7bd92719fdf6c845ff7cedf2abb393a0643e4f"}, 1637 | ] 1638 | 1639 | [[package]] 1640 | name = "types-jwt" 1641 | version = "0.1.3" 1642 | description = "Typing stubs for jwt" 1643 | optional = false 1644 | python-versions = "*" 1645 | files = [ 1646 | {file = "types_jwt-0.1.3-py2.py3-none-any.whl", hash = "sha256:36a1d5f99b349428f9ca5695e5caf3378398ae31d7aaed1c668c81451ba79b08"}, 1647 | ] 1648 | 1649 | [package.dependencies] 1650 | types-cryptography = "*" 1651 | 1652 | [[package]] 1653 | name = "types-passlib" 1654 | version = "1.7.7.20240819" 1655 | description = "Typing stubs for passlib" 1656 | optional = false 1657 | python-versions = ">=3.8" 1658 | files = [ 1659 | {file = "types-passlib-1.7.7.20240819.tar.gz", hash = "sha256:8fc8df71623845032293d5cf7f8091f0adfeba02d387a2888684b8413f14b3d0"}, 1660 | {file = "types_passlib-1.7.7.20240819-py3-none-any.whl", hash = "sha256:c4d299083497b66e12258c7b77c08952574213fdf7009da3135d8181a6a25f23"}, 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "typing-extensions" 1665 | version = "4.12.2" 1666 | description = "Backported and Experimental Type Hints for Python 3.8+" 1667 | optional = false 1668 | python-versions = ">=3.8" 1669 | files = [ 1670 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 1671 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "tzdata" 1676 | version = "2024.1" 1677 | description = "Provider of IANA time zone data" 1678 | optional = false 1679 | python-versions = ">=2" 1680 | files = [ 1681 | {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, 1682 | {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "urllib3" 1687 | version = "2.2.3" 1688 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1689 | optional = false 1690 | python-versions = ">=3.8" 1691 | files = [ 1692 | {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, 1693 | {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, 1694 | ] 1695 | 1696 | [package.extras] 1697 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1698 | h2 = ["h2 (>=4,<5)"] 1699 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1700 | zstd = ["zstandard (>=0.18.0)"] 1701 | 1702 | [[package]] 1703 | name = "uvicorn" 1704 | version = "0.30.6" 1705 | description = "The lightning-fast ASGI server." 1706 | optional = false 1707 | python-versions = ">=3.8" 1708 | files = [ 1709 | {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, 1710 | {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, 1711 | ] 1712 | 1713 | [package.dependencies] 1714 | click = ">=7.0" 1715 | h11 = ">=0.8" 1716 | 1717 | [package.extras] 1718 | 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)"] 1719 | 1720 | [[package]] 1721 | name = "vine" 1722 | version = "5.1.0" 1723 | description = "Python promises." 1724 | optional = false 1725 | python-versions = ">=3.6" 1726 | files = [ 1727 | {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, 1728 | {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, 1729 | ] 1730 | 1731 | [[package]] 1732 | name = "wcwidth" 1733 | version = "0.2.13" 1734 | description = "Measures the displayed width of unicode strings in a terminal" 1735 | optional = false 1736 | python-versions = "*" 1737 | files = [ 1738 | {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, 1739 | {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, 1740 | ] 1741 | 1742 | [metadata] 1743 | lock-version = "2.0" 1744 | python-versions = "^3.12" 1745 | content-hash = "7460f51364c36b505df967f690e1ffeef38662fcc1ec43ba6fc683594648aa1a" 1746 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi-template" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["valon "] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.12" 11 | fastapi = "^0.112.2" 12 | uvicorn = "^0.30.6" 13 | pydantic-settings = "^2.4.0" 14 | sqlalchemy = {extras = ["mypy"], version = "^2.0.36"} 15 | alembic = "^1.13.2" 16 | psycopg = {version = "^3.1.13", extras = ["binary"]} 17 | celery = {extras = ["redis"], version = "^5.4.0"} 18 | gunicorn = "^23.0.0" 19 | inflection = "^0.5.1" 20 | passlib = "^1.7.4" 21 | pydantic = {extras = ["email"], version = "^2.9.2"} 22 | python-multipart = "^0.0.16" 23 | pyjwt = "^2.9.0" 24 | 25 | [tool.poetry.group.dev.dependencies] 26 | pytest = "^8.3.2" 27 | pytest-cov = "^5.0.0" 28 | httpx = "^0.27.2" 29 | ruff = "^0.6.3" 30 | pytest-sugar = "^1.0.0" 31 | mypy = "^1.11.2" 32 | pytest-celery = "^1.1.2" 33 | types-jwt = "^0.1.3" 34 | types-passlib = "^1.7.7.20240819" 35 | 36 | [build-system] 37 | requires = ["poetry-core"] 38 | build-backend = "poetry.core.masonry.api" 39 | 40 | 41 | [tool.mypy] 42 | python_version = "3.12" 43 | ignore_missing_imports = true 44 | strict = true 45 | exclude = ["venv", ".venv", "alembic"] 46 | plugins = ["sqlalchemy.ext.mypy.plugin"] 47 | 48 | [tool.ruff] 49 | target-version = "py312" 50 | exclude = ["alembic"] 51 | 52 | [tool.ruff.lint] 53 | select = [ 54 | "B", # flake8-bugbear 55 | "W", # pycodestyle warnings 56 | "I", # isort 57 | "E", # pycodestyle errors 58 | "C4", # flake8-comprehensions 59 | "UP", # pyupgrade 60 | "F", # pyflakes 61 | "ARG001", # unused arguments in functions 62 | ] 63 | ignore = [ 64 | "E501", # line too long 65 | "B008", # do not perform function calls in argument defaults 66 | "W191", # indentation contains tabs 67 | "B904", # Allow raising exceptions without from e, for HTTPException 68 | ] 69 | 70 | [tool.ruff.lint.pyupgrade] 71 | keep-runtime-typing = true 72 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/__init__.py -------------------------------------------------------------------------------- /src/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /src/alembic/env.py: -------------------------------------------------------------------------------- 1 | from logging.config import fileConfig 2 | 3 | from sqlalchemy import engine_from_config 4 | from sqlalchemy import pool 5 | 6 | from alembic import context 7 | 8 | from src.core.config import settings 9 | from src.core.db import Base 10 | from src.models import * 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | config.set_main_option('sqlalchemy.url', str(settings.SQLALCHEMY_DATABASE_URI)) 15 | 16 | # Interpret the config file for Python logging. 17 | # This line sets up loggers basically. 18 | if config.config_file_name is not None: 19 | fileConfig(config.config_file_name) 20 | 21 | # add your model's MetaData object here 22 | # for 'autogenerate' support 23 | # from myapp import mymodel 24 | # target_metadata = mymodel.Base.metadata 25 | target_metadata = Base.metadata 26 | 27 | 28 | # other values from the config, defined by the needs of env.py, 29 | # can be acquired: 30 | # my_important_option = config.get_main_option("my_important_option") 31 | # ... etc. 32 | 33 | 34 | def run_migrations_offline() -> None: 35 | """Run migrations in 'offline' mode. 36 | 37 | This configures the context with just a URL 38 | and not an Engine, though an Engine is acceptable 39 | here as well. By skipping the Engine creation 40 | we don't even need a DBAPI to be available. 41 | 42 | Calls to context.execute() here emit the given string to the 43 | script output. 44 | 45 | """ 46 | url = config.get_main_option("sqlalchemy.url") 47 | context.configure( 48 | url=url, 49 | target_metadata=target_metadata, 50 | literal_binds=True, 51 | dialect_opts={"paramstyle": "named"}, 52 | ) 53 | 54 | with context.begin_transaction(): 55 | context.run_migrations() 56 | 57 | 58 | def run_migrations_online() -> None: 59 | """Run migrations in 'online' mode. 60 | 61 | In this scenario we need to create an Engine 62 | and associate a connection with the context. 63 | 64 | """ 65 | connectable = engine_from_config( 66 | config.get_section(config.config_ini_section, {}), 67 | prefix="sqlalchemy.", 68 | poolclass=pool.NullPool, 69 | ) 70 | 71 | with connectable.connect() as connection: 72 | context.configure( 73 | connection=connection, target_metadata=target_metadata 74 | ) 75 | 76 | with context.begin_transaction(): 77 | context.run_migrations() 78 | 79 | 80 | if context.is_offline_mode(): 81 | run_migrations_offline() 82 | else: 83 | run_migrations_online() 84 | -------------------------------------------------------------------------------- /src/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: Union[str, None] = ${repr(down_revision)} 17 | branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} 18 | depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | ${upgrades if upgrades else "pass"} 23 | 24 | 25 | def downgrade() -> None: 26 | ${downgrades if downgrades else "pass"} 27 | -------------------------------------------------------------------------------- /src/alembic/versions/5cfe696e2a6f_add_initial_migration.py: -------------------------------------------------------------------------------- 1 | """add initial migration 2 | 3 | Revision ID: 5cfe696e2a6f 4 | Revises: 5 | Create Date: 2024-10-27 21:36:45.231381 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = '5cfe696e2a6f' 16 | down_revision: Union[str, None] = None 17 | branch_labels: Union[str, Sequence[str], None] = None 18 | depends_on: Union[str, Sequence[str], None] = None 19 | 20 | 21 | def upgrade() -> None: 22 | # ### commands auto generated by Alembic - please adjust! ### 23 | op.create_table('users', 24 | sa.Column('name', sa.String(), nullable=False), 25 | sa.Column('email', sa.String(), nullable=False), 26 | sa.Column('password_hash', sa.String(), nullable=False), 27 | sa.Column('is_active', sa.Boolean(), nullable=False), 28 | sa.Column('user_type', sa.Enum('ADMIN', 'USER', name='usertype'), nullable=False), 29 | sa.Column('id', sa.UUID(), nullable=False), 30 | sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), 31 | sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), 32 | sa.PrimaryKeyConstraint('id'), 33 | sa.UniqueConstraint('email'), 34 | sa.UniqueConstraint('id') 35 | ) 36 | op.create_table('items', 37 | sa.Column('name', sa.String(), nullable=True), 38 | sa.Column('user_id', sa.UUID(), nullable=False), 39 | sa.Column('id', sa.UUID(), nullable=False), 40 | sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), 41 | sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), 42 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 43 | sa.PrimaryKeyConstraint('id'), 44 | sa.UniqueConstraint('id') 45 | ) 46 | # ### end Alembic commands ### 47 | 48 | 49 | def downgrade() -> None: 50 | # ### commands auto generated by Alembic - please adjust! ### 51 | op.drop_table('items') 52 | op.drop_table('users') 53 | # ### end Alembic commands ### 54 | -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/api/__init__.py -------------------------------------------------------------------------------- /src/api/dependencies.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | 3 | import jwt 4 | from fastapi import Depends, HTTPException 5 | from passlib.exc import InvalidTokenError 6 | from pydantic import ValidationError 7 | from sqlalchemy.orm import Session 8 | from starlette import status 9 | 10 | from src.core.config import settings 11 | from src.core.db import SessionLocal 12 | from src.core.enums import UserType 13 | from src.core.security import oauth2_scheme 14 | from src.models import User 15 | from src.repositories.item import ItemRepository 16 | from src.repositories.user import UserRepository 17 | from src.services.auth import AuthService 18 | from src.services.item import ItemService 19 | from src.services.user import UserService 20 | 21 | 22 | def get_db() -> Generator[Session, None, None]: 23 | """ 24 | Provide a SQLAlchemy session for database operations. 25 | 26 | This generator function yields a SQLAlchemy session object, ensuring 27 | that the session is properly closed after use. It is used to manage 28 | database connections and ensure they are cleanly closed to prevent 29 | resource leaks. 30 | 31 | Yields: 32 | Session: A SQLAlchemy session object. 33 | """ 34 | db = SessionLocal() 35 | try: 36 | yield db 37 | finally: 38 | db.close() 39 | 40 | 41 | def get_auth_service(db: Session = Depends(get_db)) -> AuthService: 42 | return AuthService(db) 43 | 44 | 45 | def get_user_service(db: Session = Depends(get_db)) -> UserService: 46 | user_repository = UserRepository(db) 47 | return UserService(user_repository) 48 | 49 | 50 | def get_item_service(db: Session = Depends(get_db)) -> ItemService: 51 | item_repository = ItemRepository(db) 52 | return ItemService(item_repository) 53 | 54 | 55 | def get_current_user( 56 | token: str = Depends(oauth2_scheme), db: Session = Depends(get_db) 57 | ) -> User: 58 | try: 59 | payload = jwt.decode( 60 | token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] 61 | ) 62 | except (InvalidTokenError, ValidationError): 63 | raise HTTPException( 64 | status_code=status.HTTP_403_FORBIDDEN, 65 | detail="Could not validate credentials", 66 | ) 67 | user = db.query(User).filter(User.email == payload.get("sub")).first() 68 | if not user: 69 | raise HTTPException(status_code=404, detail="User not found") 70 | if not user.is_active: 71 | raise HTTPException(status_code=400, detail="Inactive user") 72 | return user 73 | 74 | 75 | def get_current_active_superuser( 76 | current_user: User = Depends(get_current_user), 77 | ) -> User: 78 | if current_user.user_type != UserType.ADMIN: 79 | raise HTTPException(status_code=403, detail="Admin access required") 80 | return current_user 81 | -------------------------------------------------------------------------------- /src/api/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from src.api.routes import auth_router, tasks_router, users_router 4 | 5 | api_router = APIRouter(prefix="/api/v1") 6 | 7 | api_router.include_router(tasks_router) 8 | api_router.include_router(users_router) 9 | api_router.include_router(auth_router) 10 | -------------------------------------------------------------------------------- /src/api/routes/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import router as auth_router 2 | from .tasks import tasks_router as tasks_router 3 | from .users import router as users_router 4 | 5 | __all__ = ["auth_router", "tasks_router", "users_router"] 6 | -------------------------------------------------------------------------------- /src/api/routes/auth.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | from fastapi.security import OAuth2PasswordRequestForm 3 | 4 | from src.api.dependencies import get_auth_service 5 | from src.schemas.auth import UserCreate, UserLoginResponse, UserSignUpResponse 6 | from src.services.auth import AuthService 7 | 8 | router = APIRouter(prefix="/auth", tags=["auth"]) 9 | 10 | 11 | @router.post("/signup", response_model=UserSignUpResponse) 12 | def signup( 13 | user: UserCreate, auth_service: AuthService = Depends(get_auth_service) 14 | ) -> dict[str, str]: 15 | new_user = auth_service.signup(user) 16 | return {"message": "User created successfully", "user_id": str(new_user.id)} 17 | 18 | 19 | @router.post("/login", response_model=UserLoginResponse) 20 | def login( 21 | form_data: OAuth2PasswordRequestForm = Depends(), 22 | auth_service: AuthService = Depends(get_auth_service), 23 | ) -> dict[str, str]: 24 | access_token = auth_service.authenticate(form_data.username, form_data.password) 25 | return {"access_token": access_token, "token_type": "bearer"} 26 | -------------------------------------------------------------------------------- /src/api/routes/tasks.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter 4 | 5 | from src.tasks import reverse 6 | 7 | tasks_router = APIRouter(prefix="/tasks", tags=["tasks"]) 8 | 9 | 10 | @tasks_router.get("/") 11 | def run_reverse_task(name: str) -> dict[str, Any]: 12 | """ 13 | Run a celery task called reverse 14 | 15 | Args: 16 | name (str): The string to reverse 17 | 18 | Returns: 19 | dict[str, Any]: A dictionary containing the task_id and the status of the task 20 | """ 21 | task = reverse.delay(name) 22 | return {"task_id": task.id, "status": "Task is running"} 23 | -------------------------------------------------------------------------------- /src/api/routes/users.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from fastapi import APIRouter, Depends, HTTPException 4 | 5 | from src.api.dependencies import ( 6 | get_current_active_superuser, 7 | get_current_user, 8 | get_item_service, 9 | get_user_service, 10 | ) 11 | from src.models import Item 12 | from src.models.users import User 13 | from src.schemas.auth import UserRead 14 | from src.schemas.items import ItemCreate, ItemRead 15 | from src.services.item import ItemService 16 | from src.services.user import UserService 17 | 18 | router = APIRouter(prefix="/auth", tags=["auth"]) 19 | 20 | 21 | @router.get("/users", response_model=list[UserRead]) 22 | def users( 23 | user_service: UserService = Depends(get_user_service), 24 | _: None = Depends(get_current_active_superuser), 25 | ) -> list[User]: 26 | return user_service.get_all() 27 | 28 | 29 | @router.post("/users/{user_id}/items", response_model=ItemRead) 30 | def create_user_item( 31 | user_id: UUID, 32 | item: ItemCreate, 33 | item_service: ItemService = Depends(get_item_service), 34 | current_user: User = Depends(get_current_user), 35 | ) -> Item: 36 | if str(current_user.id) != str(user_id): 37 | raise HTTPException(status_code=403, detail="Forbidden") 38 | return item_service.create_item(user_id=user_id, item=item) 39 | 40 | 41 | @router.get("/users/{user_id}/items", response_model=list[ItemRead]) 42 | def get_user_items( 43 | user_id: UUID, 44 | current_user: User = Depends(get_current_user), 45 | ) -> list[Item]: 46 | if str(current_user.id) != str(user_id): 47 | raise HTTPException(status_code=403, detail="Forbidden") 48 | return current_user.items 49 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/core/__init__.py -------------------------------------------------------------------------------- /src/core/celery_config.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | from src.core.config import settings 4 | 5 | celery_app = Celery( 6 | "tasks", broker=settings.CELERY_BROKER_URL, backend=settings.CELERY_RESULT_BACKEND 7 | ) 8 | 9 | # Import the tasks module to ensure the worker recognizes the tasks or add autodiscover_tasks to the Celery app. 10 | from src.tasks import * # noqa 11 | -------------------------------------------------------------------------------- /src/core/config.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | from typing import Annotated, Any, Literal 3 | 4 | from pydantic import ( 5 | AnyUrl, 6 | BeforeValidator, 7 | PostgresDsn, 8 | computed_field, 9 | ) 10 | from pydantic_core import MultiHostUrl 11 | from pydantic_settings import BaseSettings, SettingsConfigDict 12 | 13 | 14 | def parse_cors(v: Any) -> list[str] | str: 15 | """ 16 | Parse and normalize CORS origins. 17 | 18 | Converts a comma-separated string or a list of CORS origins into a 19 | list of strings. If the input is a string that does not start with 20 | '[', it splits by commas. If the input is a list or already a list 21 | of strings, it returns it as-is. 22 | 23 | Args: 24 | v (Any): The CORS origins as a string or list. 25 | 26 | Returns: 27 | list[str] | str: A list of CORS origins or a single string if input is already in the correct format. 28 | 29 | Raises: 30 | ValueError: If the input is neither a string nor a list. 31 | """ 32 | if isinstance(v, str) and not v.startswith("["): 33 | return [i.strip() for i in v.split(",")] 34 | elif isinstance(v, list | str): 35 | return v 36 | raise ValueError(v) 37 | 38 | 39 | class Settings(BaseSettings): 40 | """ 41 | Application settings loaded from environment variables and a .env file. 42 | 43 | This class is used to load and validate application settings, including 44 | CORS origins, database configurations, and environment-specific settings. 45 | """ 46 | 47 | model_config = SettingsConfigDict( 48 | env_file=".env", env_ignore_empty=True, extra="ignore" 49 | ) 50 | 51 | PROJECT_NAME: str = "FastAPI Kickstart" 52 | API_V1_STR: str = "/api/v1" 53 | SECRET_KEY: str = secrets.token_urlsafe(32) 54 | ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 55 | ENVIRONMENT: Literal["local", "staging", "production"] = "local" 56 | BACKEND_CORS_ORIGINS: Annotated[ 57 | list[AnyUrl] | str, BeforeValidator(parse_cors) 58 | ] = [] 59 | 60 | POSTGRES_HOST: str = "localhost" 61 | POSTGRES_PORT: int = 5432 62 | POSTGRES_USER: str = "postgres" 63 | POSTGRES_PASSWORD: str = "postgres" 64 | POSTGRES_DB: str = "fastapi-kickstart" 65 | 66 | REDIS_HOST: str = "localhost" 67 | REDIS_PORT: int = 6379 68 | 69 | ALGORITHM: str = "HS256" 70 | 71 | @computed_field # type: ignore[prop-decorator] 72 | @property 73 | def REDIS_URL(self) -> str: 74 | """ 75 | Compute the Redis URL. 76 | 77 | Constructs the Redis URL using Redis host and port. 78 | 79 | Returns: 80 | str: The Redis URL. 81 | """ 82 | return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/0" 83 | 84 | CELERY_BROKER_URL = REDIS_URL 85 | CELERY_RESULT_BACKEND = REDIS_URL 86 | 87 | @computed_field # type: ignore[prop-decorator] 88 | @property 89 | def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn: 90 | """ 91 | Compute the SQLAlchemy database URI. 92 | 93 | Constructs the database URI for SQLAlchemy using PostgresSQL connection 94 | details. 95 | 96 | Returns: 97 | PostgresDsn: The database URI for SQLAlchemy. 98 | """ 99 | return MultiHostUrl.build( 100 | scheme="postgresql+psycopg", 101 | username=self.POSTGRES_USER, 102 | password=self.POSTGRES_PASSWORD, 103 | host=self.POSTGRES_HOST, 104 | port=self.POSTGRES_PORT, 105 | path=self.POSTGRES_DB, 106 | ) 107 | 108 | 109 | settings = Settings() 110 | -------------------------------------------------------------------------------- /src/core/db.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import inflection 4 | from sqlalchemy import Column, DateTime, create_engine 5 | from sqlalchemy.dialects.postgresql import UUID 6 | from sqlalchemy.orm import DeclarativeBase, declared_attr, sessionmaker 7 | from sqlalchemy.sql import func 8 | 9 | from src.core.config import settings 10 | 11 | engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI)) 12 | 13 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 14 | 15 | 16 | class Base(DeclarativeBase): 17 | pass 18 | 19 | 20 | class DatabaseModel(Base): 21 | __abstract__ = True 22 | 23 | @declared_attr # type: ignore 24 | def __tablename__(cls) -> str: 25 | return inflection.pluralize(inflection.underscore(cls.__name__)) 26 | 27 | id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4) 28 | created_at = Column( 29 | DateTime(timezone=True), server_default=func.now(), nullable=False 30 | ) 31 | updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True) 32 | -------------------------------------------------------------------------------- /src/core/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class UserType(Enum): 5 | ADMIN = "admin" 6 | USER = "user" 7 | -------------------------------------------------------------------------------- /src/core/error_messages.py: -------------------------------------------------------------------------------- 1 | class AuthErrorMessages: 2 | INVALID_CREDENTIALS = "Invalid credentials" 3 | EMAIL_REGISTERED = "Email already registered" 4 | 5 | 6 | class UserErrorMessages: 7 | USER_NOT_FOUND = "User not found" 8 | USER_CREATION_FAILED = "User creation failed" 9 | -------------------------------------------------------------------------------- /src/core/exceptions.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | 3 | from src.core.error_messages import AuthErrorMessages 4 | 5 | 6 | class UserAlreadyExistsException(HTTPException): 7 | def __init__(self) -> None: 8 | super().__init__(status_code=400, detail=AuthErrorMessages.EMAIL_REGISTERED) 9 | 10 | 11 | class InvalidCredentialsException(HTTPException): 12 | def __init__(self) -> None: 13 | super().__init__(status_code=401, detail=AuthErrorMessages.INVALID_CREDENTIALS) 14 | -------------------------------------------------------------------------------- /src/core/security.py: -------------------------------------------------------------------------------- 1 | from datetime import UTC, datetime, timedelta 2 | from typing import Any 3 | 4 | import jwt 5 | from fastapi.security import OAuth2PasswordBearer 6 | from passlib.context import CryptContext 7 | 8 | from src.core.config import settings 9 | 10 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") 11 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 12 | 13 | 14 | def create_access_token( 15 | data: dict[str, Any], expires_delta: timedelta | None = None 16 | ) -> str: 17 | to_encode = data.copy() 18 | if expires_delta: 19 | expire = datetime.now(UTC) + expires_delta 20 | else: 21 | expire = datetime.now(UTC) + timedelta(minutes=15) 22 | to_encode.update({"exp": expire}) 23 | encoded_jwt = jwt.encode( 24 | to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM 25 | ) 26 | return str(encoded_jwt) 27 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from starlette.middleware.cors import CORSMiddleware 3 | 4 | from src.api.main import api_router 5 | from src.core.config import settings 6 | 7 | app = FastAPI( 8 | title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json" 9 | ) 10 | app.include_router(api_router) 11 | 12 | if settings.BACKEND_CORS_ORIGINS: 13 | app.add_middleware( 14 | CORSMiddleware, 15 | allow_origins=[ 16 | str(origin).strip("/") for origin in settings.BACKEND_CORS_ORIGINS 17 | ], 18 | allow_credentials=True, 19 | allow_methods=["*"], 20 | allow_headers=["*"], 21 | ) 22 | -------------------------------------------------------------------------------- /src/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .items import Item 2 | from .users import User 3 | 4 | __all__ = ["User", "Item"] 5 | -------------------------------------------------------------------------------- /src/models/items.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, ForeignKey, String 2 | from sqlalchemy.dialects.postgresql import UUID 3 | from sqlalchemy.orm import relationship 4 | 5 | from src.core.db import DatabaseModel 6 | 7 | 8 | class Item(DatabaseModel): 9 | name = Column(String) 10 | user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) 11 | 12 | owner = relationship("User", back_populates="items") 13 | -------------------------------------------------------------------------------- /src/models/users.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from sqlalchemy import Boolean, String 4 | from sqlalchemy import Enum as SQLAlchemyEnum 5 | from sqlalchemy.orm import Mapped, mapped_column, relationship 6 | 7 | from src.core.db import DatabaseModel 8 | from src.core.enums import UserType 9 | 10 | 11 | class User(DatabaseModel): 12 | name: Mapped[str] = mapped_column(String, nullable=False) 13 | email: Mapped[str] = mapped_column(String, unique=True, nullable=False) 14 | password_hash: Mapped[str] = mapped_column(String, nullable=False) 15 | is_active: Mapped[bool] = mapped_column(Boolean, default=True) 16 | user_type: Mapped[UserType] = mapped_column( 17 | SQLAlchemyEnum(UserType), nullable=False, default=UserType.USER 18 | ) 19 | 20 | items: Mapped[list[Any]] = relationship( 21 | "Item", back_populates="owner", cascade="all, delete-orphan" 22 | ) 23 | 24 | def __repr__(self) -> str: 25 | return f"" 26 | 27 | def __str__(self) -> str: 28 | return f"{self.name} ({self.user_type})" 29 | -------------------------------------------------------------------------------- /src/repositories/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/repositories/__init__.py -------------------------------------------------------------------------------- /src/repositories/base.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | from uuid import UUID 3 | 4 | from sqlalchemy.exc import NoResultFound 5 | from sqlalchemy.orm import Session 6 | 7 | from src.core.db import DatabaseModel 8 | 9 | ModelType = TypeVar("ModelType", bound=DatabaseModel) 10 | 11 | 12 | class BaseRepository(Generic[ModelType]): 13 | def __init__(self, db: Session, model: type[ModelType]): 14 | self.db = db 15 | self.model = model 16 | 17 | def get(self, id: UUID) -> ModelType | None: 18 | try: 19 | return self.db.query(self.model).filter(self.model.id == id).one() 20 | except NoResultFound: 21 | return None 22 | 23 | def get_all(self) -> list[ModelType]: 24 | return self.db.query(self.model).all() 25 | 26 | def create(self, obj: ModelType) -> ModelType: 27 | self.db.add(obj) 28 | self.db.commit() 29 | self.db.refresh(obj) 30 | return obj 31 | 32 | def update(self, obj: ModelType) -> ModelType: 33 | self.db.commit() 34 | return obj 35 | 36 | def delete(self, id: UUID) -> None: 37 | obj = self.get(id) 38 | if obj: 39 | self.db.delete(obj) 40 | self.db.commit() 41 | -------------------------------------------------------------------------------- /src/repositories/item.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | from src.models import Item 4 | from src.repositories.base import BaseRepository 5 | 6 | 7 | class ItemRepository(BaseRepository[Item]): 8 | def __init__(self, db: Session): 9 | super().__init__(db, Item) 10 | -------------------------------------------------------------------------------- /src/repositories/user.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | from src.models import User 4 | from src.repositories.base import BaseRepository 5 | 6 | 7 | class UserRepository(BaseRepository[User]): 8 | def __init__(self, db: Session): 9 | super().__init__(db, User) 10 | 11 | def get_by_email(self, email: str) -> User | None: 12 | return self.db.query(User).filter(User.email == email).first() 13 | -------------------------------------------------------------------------------- /src/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/schemas/__init__.py -------------------------------------------------------------------------------- /src/schemas/auth.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from pydantic import EmailStr, Field 4 | 5 | from src.schemas.base import BaseSchema 6 | 7 | 8 | class UserLoginResponse(BaseSchema): 9 | access_token: str 10 | token_type: str 11 | 12 | 13 | class UserBase(BaseSchema): 14 | name: str 15 | email: EmailStr 16 | 17 | 18 | class UserCreate(UserBase): 19 | password: str = Field(..., min_length=6) 20 | 21 | 22 | class UserRead(UserBase): 23 | id: UUID 24 | 25 | 26 | class UserSignUpResponse(BaseSchema): 27 | message: str 28 | user_id: str 29 | -------------------------------------------------------------------------------- /src/schemas/base.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConfigDict 2 | 3 | 4 | class BaseSchema(BaseModel): 5 | model_config = ConfigDict(from_attributes=True) 6 | -------------------------------------------------------------------------------- /src/schemas/items.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from src.schemas.base import BaseSchema 4 | 5 | 6 | class ItemBase(BaseSchema): 7 | name: str 8 | 9 | 10 | class ItemCreate(ItemBase): 11 | pass 12 | 13 | 14 | class ItemRead(ItemBase): 15 | id: uuid.UUID 16 | -------------------------------------------------------------------------------- /src/services/auth.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sqlalchemy.orm import Session 4 | 5 | from src.core.exceptions import InvalidCredentialsException, UserAlreadyExistsException 6 | from src.core.security import create_access_token, pwd_context 7 | from src.models import User 8 | from src.repositories.user import UserRepository 9 | from src.schemas.auth import UserCreate 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class HashingMixin: 15 | @staticmethod 16 | def verify_password(plain_password: str, hashed_password: str) -> bool: 17 | return pwd_context.verify(plain_password, hashed_password) 18 | 19 | @staticmethod 20 | def hash_password(password: str) -> str: 21 | return pwd_context.hash(password) 22 | 23 | 24 | class AuthService(HashingMixin): 25 | def __init__(self, db: Session): 26 | self.user_repository = UserRepository(db) 27 | 28 | def signup(self, user: UserCreate) -> User: 29 | logger.info(f"Attempting signup for user: {user.email}") 30 | existing_user = self.user_repository.get_by_email(user.email) 31 | if existing_user: 32 | raise UserAlreadyExistsException() 33 | 34 | new_user = User( 35 | name=user.name, 36 | email=user.email, 37 | password_hash=self.hash_password(user.password), 38 | ) 39 | return self.user_repository.create(new_user) 40 | 41 | def authenticate(self, email: str, password: str) -> str: 42 | logger.info(f"User login attempt: {email}") 43 | user = self.user_repository.get_by_email(email) 44 | if not user or not self.verify_password(password, user.password_hash): 45 | raise InvalidCredentialsException() 46 | access_token = create_access_token(data={"sub": user.email}) 47 | return access_token 48 | -------------------------------------------------------------------------------- /src/services/base.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | from uuid import UUID 3 | 4 | from src.core.db import DatabaseModel 5 | from src.repositories.base import BaseRepository 6 | 7 | ServiceModelType = TypeVar("ServiceModelType", bound=DatabaseModel) 8 | 9 | 10 | class BaseService(Generic[ServiceModelType]): 11 | def __init__(self, repository: BaseRepository[ServiceModelType]): 12 | self.repository = repository 13 | 14 | def get(self, id: UUID) -> ServiceModelType: 15 | return self.repository.get(id) # type: ignore 16 | 17 | def get_all(self) -> list[ServiceModelType]: 18 | return self.repository.get_all() 19 | 20 | def create(self, obj: ServiceModelType) -> ServiceModelType: 21 | return self.repository.create(obj) 22 | 23 | def update(self, obj: ServiceModelType) -> ServiceModelType: 24 | return self.repository.update(obj) 25 | 26 | def delete(self, id: UUID) -> bool: 27 | try: 28 | self.repository.delete(id) 29 | return True 30 | except Exception: 31 | return False 32 | -------------------------------------------------------------------------------- /src/services/item.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from src.models import Item 4 | from src.repositories.item import ItemRepository 5 | from src.schemas.items import ItemCreate 6 | from src.services.base import BaseService 7 | 8 | 9 | class ItemService(BaseService[Item]): 10 | def __init__(self, item_repository: ItemRepository): 11 | super().__init__(item_repository) 12 | self.item_repository = item_repository 13 | 14 | def create_item(self, user_id: uuid.UUID, item: ItemCreate) -> Item: 15 | item_obj = Item(**item.dict(), user_id=user_id) 16 | return self.item_repository.create(item_obj) 17 | -------------------------------------------------------------------------------- /src/services/item_service.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | from src.models.items import Item 4 | from src.schemas.items import ItemCreate 5 | 6 | 7 | def get_item(db: Session, item_id: int) -> Item | None: 8 | """ 9 | Retrieve an item from the database by its ID. 10 | 11 | Args: 12 | db (Session): The SQLAlchemy session object. 13 | item_id (int): The ID of the item to retrieve. 14 | 15 | Returns: 16 | Item | None: The item object if found, otherwise None. 17 | """ 18 | return db.query(Item).filter(Item.id == item_id).first() 19 | 20 | 21 | def get_items(db: Session, skip: int = 0, limit: int = 10) -> list[Item]: 22 | """ 23 | Retrieve a list of items from the database with optional pagination. 24 | 25 | Args: 26 | db (Session): The SQLAlchemy session object. 27 | skip (int, optional): The number of items to skip. Defaults to 0. 28 | limit (int, optional): The maximum number of items to retrieve. Defaults to 10. 29 | 30 | Returns: 31 | list[Item]: A list of item objects. 32 | """ 33 | return db.query(Item).offset(skip).limit(limit).all() 34 | 35 | 36 | def create_item(db: Session, item: ItemCreate) -> Item: 37 | """ 38 | Create a new item in the database. 39 | 40 | Args: 41 | db (Session): The SQLAlchemy session object. 42 | item (ItemCreate): The item data to create. 43 | 44 | Returns: 45 | Item: The created item object. 46 | """ 47 | item_obj = Item(name=item.name) 48 | db.add(item_obj) 49 | db.commit() 50 | db.refresh(item_obj) 51 | return item_obj 52 | -------------------------------------------------------------------------------- /src/services/user.py: -------------------------------------------------------------------------------- 1 | from src.models import User 2 | from src.repositories.user import UserRepository 3 | from src.services.base import BaseService 4 | 5 | 6 | class UserService(BaseService[User]): 7 | def __init__(self, user_repository: UserRepository): 8 | super().__init__(user_repository) 9 | self.user_repository = user_repository 10 | -------------------------------------------------------------------------------- /src/tasks.py: -------------------------------------------------------------------------------- 1 | from src.core.celery_config import celery_app 2 | 3 | 4 | @celery_app.task # type: ignore 5 | def reverse(name: str) -> str: 6 | return name[::-1] 7 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/tests/__init__.py -------------------------------------------------------------------------------- /src/tests/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/tests/api/__init__.py -------------------------------------------------------------------------------- /src/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections.abc import Generator 3 | from typing import Any 4 | 5 | import pytest 6 | from celery import Celery 7 | from fastapi.testclient import TestClient 8 | from sqlalchemy import create_engine 9 | from sqlalchemy.orm import Session, sessionmaker 10 | 11 | from src.api.dependencies import get_db 12 | from src.core.db import Base 13 | from src.main import app 14 | from src.tests.factories import item # noqa 15 | 16 | SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" 17 | engine = create_engine( 18 | SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} 19 | ) 20 | TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 21 | 22 | 23 | @pytest.fixture(scope="session", autouse=True) 24 | def setup_test_environment() -> Generator[None, None, None]: 25 | """ 26 | Fixture to set up the test environment. 27 | 28 | This fixture creates all database tables before the test session starts and drops them after the session ends. 29 | It also removes the test database file if it exists. 30 | 31 | Yields: 32 | None 33 | """ 34 | Base.metadata.create_all(bind=engine) 35 | yield 36 | Base.metadata.drop_all(bind=engine) 37 | if os.path.exists("./test.db"): 38 | os.remove("./test.db") 39 | 40 | 41 | def get_test_db() -> Generator[Session, None, None]: 42 | """ 43 | Generator function to provide a SQLAlchemy session for testing. 44 | 45 | This function yields a database session and ensures it is closed after use. 46 | 47 | Yields: 48 | A SQLAlchemy session object. 49 | """ 50 | db = TestingSessionLocal() 51 | try: 52 | yield db 53 | finally: 54 | db.close() 55 | 56 | 57 | @pytest.fixture(scope="module") 58 | def test_client() -> TestClient: 59 | """ 60 | Fixture to provide a FastAPI test client. 61 | 62 | This fixture overrides the database dependency with a test database session and returns a FastAPI TestClient instance. 63 | 64 | Returns: 65 | A FastAPI TestClient instance. 66 | """ 67 | app.dependency_overrides[get_db] = get_test_db 68 | return TestClient(app) 69 | 70 | 71 | @pytest.fixture(scope="module") 72 | def db_session() -> Generator[Session, None, None]: 73 | """ 74 | Fixture to create a database session for testing purposes. 75 | 76 | This fixture yields a SQLAlchemy session object and ensures it is closed after use. 77 | 78 | Yields: 79 | A SQLAlchemy session object. 80 | """ 81 | session = TestingSessionLocal() 82 | try: 83 | yield session 84 | finally: 85 | session.close() 86 | 87 | 88 | @pytest.fixture(scope="session") 89 | def celery_config() -> dict[str, Any]: 90 | """ 91 | Fixture for configuring Celery settings for testing. 92 | """ 93 | return { 94 | "broker_url": "memory://", 95 | "result_backend": "memory://", 96 | "task_always_eager": True, 97 | } 98 | 99 | 100 | @pytest.fixture(scope="session") 101 | def celery_worker(celery_config: dict[str, Any]) -> Celery: 102 | """ 103 | Fixture to start a Celery worker for testing. 104 | """ 105 | from src.core.celery_config import celery_app 106 | 107 | celery_app.config_from_object(celery_config) 108 | return celery_app 109 | -------------------------------------------------------------------------------- /src/tests/factories.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import pytest 4 | from sqlalchemy.orm import Session 5 | 6 | from src.models import Item 7 | from src.schemas.items import ItemCreate 8 | from src.services.item_service import create_item 9 | 10 | 11 | @pytest.fixture 12 | def item(db_session: Session) -> Item: 13 | random_name = f"Test Item {uuid.uuid4().hex[:6]}" 14 | item_data = ItemCreate(name=random_name) 15 | return create_item(db=db_session, item=item_data) 16 | -------------------------------------------------------------------------------- /src/tests/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjanz/fastapi-kickstart/01c555141c63b76e9d032cad893f017fa1b25a3d/src/tests/services/__init__.py -------------------------------------------------------------------------------- /src/tests/services/test_celery.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | from src.tasks import reverse 4 | 5 | 6 | def test_celery_reverse_task(celery_worker: Celery) -> None: # noqa: ARG001 7 | """ 8 | Test the reverse task in Celery. 9 | 10 | Args: 11 | celery_worker: The Celery worker fixture. 12 | """ 13 | result = reverse.delay("vjanz") 14 | assert result.get() == "znajv" 15 | --------------------------------------------------------------------------------