├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── CONTRIBUTING.md ├── assets │ ├── endpoints-favicon.ico │ ├── logo-black.png │ └── logo.png ├── auto-discovery.md ├── exclude-routers.md ├── index.md ├── overrides │ └── main.html ├── quick-start.md └── stylesheets │ └── extra.css ├── example ├── README.md ├── __init__.py ├── app.py └── routers │ ├── __init__.py │ └── api_v1 │ ├── __init__.py │ ├── data.py │ └── probes.py ├── mkdocs.yml ├── poetry.lock ├── poetry.toml ├── pyproject.toml ├── requirements-build.txt ├── src └── fastapi_endpoints │ ├── __init__.py │ ├── constants.py │ ├── exceptions.py │ ├── router.py │ └── utils.py └── tests ├── conftest.py ├── test_router.py └── test_utils.py /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: 'read' 13 | strategy: 14 | matrix: 15 | python-version: [ '3.8', '3.9' ] 16 | steps: 17 | - name: Check out code 18 | uses: actions/checkout@v2 19 | 20 | - name: Setup Python 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | 25 | - name: Install dependencies 26 | run: | 27 | pip install --upgrade pip 28 | pip install -r requirements-build.txt 29 | poetry install 30 | 31 | - name: Lint check 32 | run: | 33 | python -m ruff format --check src/ 34 | 35 | test: 36 | runs-on: ubuntu-latest 37 | permissions: 38 | contents: 'read' 39 | strategy: 40 | matrix: 41 | python-version: [ '3.8', '3.9' ] 42 | steps: 43 | - name: Check out code 44 | uses: actions/checkout@v2 45 | 46 | - name: Setup Python 47 | uses: actions/setup-python@v2 48 | with: 49 | python-version: ${{ matrix.python-version }} 50 | 51 | - name: Install dependencies 52 | run: | 53 | pip install --upgrade pip 54 | pip install -r requirements-build.txt 55 | poetry install 56 | 57 | - name: Run tests 58 | run: | 59 | python -m pytest 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created with help from .ignore support plugin (hsz.mobi) 2 | 3 | ### Python template 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | .ruff_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # pytype static type analyzer 139 | .pytype/ 140 | 141 | # Cython debug symbols 142 | cython_debug/ 143 | 144 | 145 | ### Linux template 146 | *~ 147 | 148 | # temporary files which can be created if a process still has a handle open of a deleted file 149 | .fuse_hidden* 150 | 151 | # KDE directory preferences 152 | .directory 153 | 154 | # Linux trash folder which might appear on any partition or disk 155 | .Trash-* 156 | 157 | # .nfs files are created when an open file is removed but is still being accessed 158 | .nfs* 159 | 160 | 161 | ### macOS template 162 | # General 163 | .DS_Store 164 | .AppleDouble 165 | .LSOverride 166 | 167 | # Icon must end with two \r 168 | Icon 169 | 170 | # Thumbnails 171 | ._* 172 | 173 | # Files that might appear in the root of a volume 174 | .DocumentRevisions-V100 175 | .fseventsd 176 | .Spotlight-V100 177 | .TemporaryItems 178 | .Trashes 179 | .VolumeIcon.icns 180 | .com.apple.timemachine.donotpresent 181 | 182 | # Directories potentially created on remote AFP share 183 | .AppleDB 184 | .AppleDesktop 185 | Network Trash Folder 186 | Temporary Items 187 | .apdisk 188 | 189 | 190 | ### JetBrains 191 | # For a more granular version: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 192 | .idea 193 | 194 | 195 | ### Google App Engine 196 | # https://cloud.google.com/appengine/docs/standard/python3/runtime#dependencies 197 | Pipfile 198 | Pipfile.lock 199 | 200 | ### Local development 201 | 202 | # direnv 203 | .envrc 204 | 205 | # tool configs 206 | .editorconfig 207 | 208 | ### Build system 209 | 210 | # Poetry 211 | /requirements.txt 212 | 213 | # IDE 214 | /.vscode 215 | /.idea 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vlad Nedelcu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI Endpoints 2 | 3 | [![CI checks](https://github.com/vladNed/fastapi-endpoints/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](https://github.com/vladNed/fastapi-endpoints/actions/workflows/ci.yaml) 4 | 5 | This is a file-based router for FastAPI that automatically discovers and registers route files based on their filenames. 6 | This tool simplifies the organization and scaling of your FastAPI projects by allowing you to structure your endpoints in a modular way. 7 | With `fastapi-endpoints`, you can easily manage complex applications by keeping your routes clean, intuitive, and maintainable. 8 | This project is inspired from [SettleAPI/settle-fastapi-extensions](https://github.com/SettleAPI/settle-fastapi-extensions) 9 | 10 | Please refer to the documentation here: 11 | 12 | - **[Documentation](https://vladned.github.io/fastapi-endpoints/)** 13 | 14 | ## Installation 15 | 16 | ```bash 17 | pip install fastapi-endpoints 18 | ``` 19 | 20 | ## Usage 21 | 22 | ### Auto discovery 23 | 24 | The auto discovery feature allows you to organize your routes in separate files and directories and will be automatically registered by the router. 25 | All routers should be under the `routers` directory within your FastAPI application. 26 | 27 | ```python 28 | # src/app.py 29 | 30 | from fastapi import FastAPI 31 | from fastapi_endpoints import auto_include_routers 32 | 33 | from . import routers 34 | 35 | app = FastAPI() 36 | auto_include_routers(app, routers) 37 | ``` 38 | 39 | ```python 40 | # src/routers/users.py 41 | from fastapi import APIRouter 42 | 43 | users_router = APIRouter() 44 | 45 | # Define your routes here 46 | ``` 47 | 48 | The routes under `src/routers/users.py` will be automatically registered by the router. The prefix to the routes will be the path to the file relative to the `routers` directory. 49 | For the example above, the routes will be available under `/users`. 50 | 51 | The auto discovery feature also supports nested directories. For example, if you have the following directory structure: 52 | 53 | ``` 54 | routers 55 | |── __init__.py 56 | ├── api_v1 57 | │ ├── __init__.py 58 | │ ├── users.py 59 | │ └── posts.py 60 | └── api_v2 61 | ├── __init__.py 62 | ├── users.py 63 | └── posts.py 64 | app.py 65 | ``` 66 | 67 | The routes under `src/routers/api_v1/users.py` will be available under `/api/v1/users`. 68 | The same applies to the other files. The routes under `src/routers/api_v2/users.py` will be available under `/api/v2/users`. 69 | 70 | ### Exclude Routers 71 | 72 | You can exclude routers from being registered by the router by defining the `EXCLUDED_ROUTERS` variable in the `__init__.py` file of any submodule of the `routers` module. All excluded routers will be bundled together and excluded from the registration process. 73 | 74 | For example, if you have the following directory structure: 75 | ``` 76 | routers 77 | |── __init__.py 78 | ├── api_v1 79 | │ ├── __init__.py 80 | │ ├── users.py 81 | │ └── posts.py 82 | app.py 83 | ``` 84 | 85 | You can exclude the `users.py` router from being registered by defining the `EXCLUDED_ROUTERS` variable in the `api_v1/__init__.py` file. 86 | 87 | ```python 88 | # src/routers/api_v1/__init__.py 89 | from . import users 90 | 91 | EXCLUDED_ROUTERS = [users] 92 | ``` 93 | 94 | You can also exclude an entire directory by defining the `EXCLUDED_ROUTERS` variable in the `__init__.py` file of the directory. 95 | 96 | ```python 97 | # src/routers/__init__.py 98 | from . import api_v1 99 | 100 | EXCLUDED_ROUTERS = [api_v1] 101 | ``` 102 | 103 | 104 | ## Development 105 | 106 | You are required to have [Poetry](https://python-poetry.org/) installed in your system to manage the dependencies. 107 | 108 | ### Setup 109 | 110 | ```bash 111 | poetry install 112 | ``` 113 | 114 | ### Running tests 115 | 116 | ```bash 117 | pytest 118 | ``` 119 | 120 | ### Linting & Formatting 121 | 122 | To check the format: 123 | ```bash 124 | ruff format --check src/ 125 | ``` 126 | 127 | To check the lint: 128 | ```bash 129 | ruff check src/ 130 | ``` 131 | 132 | After running both commands, the format errors can be applied by running the formatter without the `--check` flag. 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to FastAPI-Endpoints 2 | 3 | I am very eager to see what you can contribute to **FastAPI-Endpoints**! All contributors are welcomed here. Below is a guide to help you get started. 4 | 5 | ## How to Contribute 6 | 7 | ### Reporting Bugs 8 | 9 | If you've found a bug, please open an issue and provide as much detail as possible: 10 | 11 | - A clear and descriptive title. 12 | - Steps to reproduce the bug. 13 | - Expected and actual behavior. 14 | - Any error messages or logs. 15 | - Add the `bug` tag. 16 | - Assign the issue to a maintainer. 17 | 18 | ### Suggesting Features 19 | 20 | We appreciate feature suggestions! Please open an issue with the following: 21 | 22 | - A clear and descriptive title. 23 | - A detailed explanation of the feature. 24 | - Potential use cases or examples. 25 | - Any relevant screenshots or mockups. 26 | - Add the `feature-request` tag. 27 | - Assign the issue to a maintainer. 28 | 29 | ### Submitting Code Changes 30 | 31 | If you want to contribute code: 32 | 33 | 1. Fork the repository. 34 | 2. Create a new branch (`git checkout -b feature/my-new-feature`). It is very important to use the `feature/` prefix to the branch. 35 | 3. Commit your changes (`git commit -am 'Add some feature'`). 36 | 4. Push to the branch (`git push origin feature/my-new-feature`). 37 | 5. Open a pull request and provide details about What, Why, How things got changed. 38 | 39 | ## Development Setup 40 | 41 | Here’s how to set up the project on your local machine. The project is heavily using [Poetry](https://python-poetry.org/) so its expected to have that installed. 42 | Create a new environment using either [venv](https://docs.python.org/3/library/venv.html) or [pyenv](https://github.com/pyenv/pyenv) for all the supported python versions of `fastapi-endpoints`. 43 | 44 | 1. Clone the repository: `git clone https://github.com/yourusername/yourprojectname.git` 45 | 2. Navigate to the project directory: `cd yourprojectname` 46 | 3. Install dependencies: `poetry install` 47 | 4. Make changes to the project. 48 | 5. Check formatting using `ruff format --check` if there are any issues resolve them with `ruff format` 49 | 6. Check lint using `ruff check` if there are any issues resolve them in the code. 50 | 7. Run the tests `pytest` 51 | 52 | ## Style Guide 53 | 54 | - **Imports**: 55 | - Place all imports at the top of the file, just after any module comments and docstrings. 56 | - Use absolute imports whenever possible. 57 | - Group imports into three sections, in this order: standard library imports, third-party imports, and local application/library-specific imports. Each group should be separated by a blank line. 58 | - Example: 59 | ```python 60 | import os 61 | import sys 62 | 63 | import requests 64 | 65 | from my_project import my_module 66 | ``` 67 | - **Docstrings**: 68 | - Use docstrings to describe modules, classes, and functions. 69 | - Follow the [PEP 257](https://www.python.org/dev/peps/pep-0257/) conventions for docstrings. 70 | 71 | - **Type Annotations**: 72 | - Use type annotations to specify the expected data types of function arguments and return values. 73 | - Example: 74 | ```python 75 | def add(a: int, b: int) -> int: 76 | return a + b 77 | ``` 78 | 79 | ## Commit Messages 80 | 81 | Use meaningful commit messages to describe the changes you have made. Follow this format: 82 | 83 | -------------------------------------------------------------------------------- /docs/assets/endpoints-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladNed/fastapi-endpoints/d7fd2519a408300fc3f597920fdf3d3a5ba1be58/docs/assets/endpoints-favicon.ico -------------------------------------------------------------------------------- /docs/assets/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladNed/fastapi-endpoints/d7fd2519a408300fc3f597920fdf3d3a5ba1be58/docs/assets/logo-black.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladNed/fastapi-endpoints/d7fd2519a408300fc3f597920fdf3d3a5ba1be58/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/auto-discovery.md: -------------------------------------------------------------------------------- 1 | # Auto Discovery 2 | 3 | The auto discovery feature works by calling the `auto_include_routers` function within the project, with the FastAPI application and the routers module as paramters. 4 | 5 | !!! note 6 | 7 | It is mandatory, for now, to have a directory called `routers` where all the endpoints are defined and instanciated with an `fastapi.APIRouter` object.i 8 | 9 | ## File-based routing 10 | 11 | When a project is launched with `auto_include_routers` called, fastapi-endpoints walks through each module under the `routers` module of the app. 12 | 13 | Python modules found with an `APIRouter` instance will be registered in the application. 14 | 15 | !!! info "Keep routers directory clean" 16 | 17 | There should not be any python module (beside `__init__.py`) without an instance of a router. The app will raise an error if the case is applicable. 18 | 19 | ## Prefix format 20 | 21 | The prefix for any router is obtained by the path to the file relative to the `routers` directory. 22 | 23 | For example, if you have the following directory structure: 24 | 25 | ``` 26 | routers 27 | |── __init__.py 28 | ├── api_v1 29 | │ ├── __init__.py 30 | │ ├── users.py 31 | │ └── posts.py 32 | |── api_v2 33 | | ├── __init__.py 34 | | ├── users.py 35 | | └── posts.py 36 | app.py 37 | ``` 38 | 39 | For each router file, the prefix will be as follows: 40 | 41 | - `src/routers/api_v1/users.py` -> `/api/v1/users` 42 | - `src/routers/api_v1/posts.py` -> `/api/v1/posts` 43 | - `src/routers/api_v2/users.py` -> `/api/v2/users` 44 | - `src/routers/api_v2/posts.py` -> `/api/v2/posts` 45 | -------------------------------------------------------------------------------- /docs/exclude-routers.md: -------------------------------------------------------------------------------- 1 | # Exclude Routers 2 | 3 | You can exclude routers from being registered by the router by defining the `EXCLUDED_ROUTERS` variable in the `__init__.py` file of any submodule of the `routers` module. All excluded routers will be bundled together and excluded from the registration process. 4 | 5 | !!! note 6 | The `EXCLUDED_ROUTERS` constant variable is a keyword that the `auto_include_routers` function looks for when registering routers. It is important to define the variable in the `__init__.py` file of the submodule you want to exclude. 7 | 8 | !!! info 9 | It is important to note that the `EXCLUDED_ROUTERS` variable should be a list of the routers you want to exclude from the registration process. 10 | 11 | For example, if you have the following directory structure: 12 | ``` 13 | routers 14 | ├── __init__.py 15 | ├── api_v1 16 | │ ├── __init__.py 17 | │ ├── users.py 18 | │ └── posts.py 19 | app.py 20 | ``` 21 | 22 | You can exclude the `users.py` router from being registered by defining the `EXCLUDED_ROUTERS` variable in the `api_v1/__init__.py` file. 23 | 24 | ```python title="src/routers/api_v1/__init__.py" 25 | from . import users 26 | 27 | EXCLUDED_ROUTERS = [users] 28 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

fastapi-endpoints

6 |

Effortless routing, simplified

7 | 8 | --- 9 | 10 | fastapi-endpoints is a lightweight and simple file-based router for FastAPI framework. 11 | 12 | The key features are: 13 | 14 | * **Simple**: Organize and define routes directly through a clear and simple file structure. 15 | * **Auto Discovery**: Automatically detects and registers all defined routers, reducing manual setup. 16 | * **Effortless Integration**: Seamlessly integrates with existing FastAPI projects, with minimal configuration required. 17 | * **Selective Routing**: Include or exclude specific routers by specifying Python modules, giving full control over active routes. 18 | 19 | --- 20 | 21 | ## Requirements 22 | 23 | fastapi-endpoints behaves as a plugin for: 24 | 25 | * FastAPI Framework 26 | 27 | --- 28 | 29 | ## Installation 30 | 31 | Create and activate a virtual environment. Some options are: 32 | 33 | * **[venv](https://docs.python.org/3/library/venv.html)** 34 | * **[pyenv](https://github.com/pyenv/pyenv)**, which has a modern approach 35 | 36 | ### Using `pip` 37 | 38 | ```bash 39 | pip install fastapi-endpoints 40 | ``` 41 | 42 | ### Using `poetry` 43 | 44 | ```bash 45 | poetry add fastapi-endpoints 46 | ``` 47 | 48 | --- 49 | 50 | ## Examples 51 | 52 | ### Basic usage 53 | 54 | The project structure should contain the directory `routers/`, for example this: 55 | 56 | ```bash hl_lines="1" 57 | routers 58 | ├── __init__.py 59 | ├── api_v1 60 | │ ├── __init__.py 61 | │ ├── users.py 62 | │ └── posts.py 63 | └── dev 64 | ├── __init__.py 65 | └── probes.py 66 | app.py 67 | ``` 68 | 69 | The `app.py` should contain the following code: 70 | 71 | ```py title="app.py" 72 | from fastapi import FastAPI 73 | from fastapi_endpoints import auto_include_routers 74 | 75 | from . import routers 76 | 77 | app = FastAPI() 78 | 79 | auto_include_routers(app, routers) 80 | ``` 81 | 82 | And for the routers, define it as this: 83 | 84 | ```py title="routers/api_v1/users.py" 85 | from fastapi import APIRouter 86 | 87 | users_router = APIRouter() 88 | 89 | @users_router.get("/") 90 | async def get_users(): 91 | ... 92 | ``` 93 | 94 | For the example above, the `get_users` endpoint will be located at path `http://localhost/api/v1/users/`. 95 | 96 | All the other routes will be automatically disovered, and they will be included with a prefix following 97 | the file structure. 98 | 99 | ### Exclude routers 100 | 101 | In this example, the `routers/dev/posts.py` router will be excluded. 102 | 103 | ``` hl_lines="7 8 9" 104 | routers 105 | ├── __init__.py 106 | ├── api_v1 107 | │ ├── __init__.py 108 | │ ├── users.py 109 | │ └── posts.py 110 | └── dev 111 | ├── __init__.py 112 | └── posts.py 113 | app.py 114 | ``` 115 | 116 | Excluding routers is entirely done by specifying which module to exclude in the `__init__.py` file. 117 | 118 | In `routers/dev/__init__.py`, import the `posts` module and add it to `EXCLUDED_ROUTERS` constant which is used 119 | for excluding scanning for routers: 120 | 121 | ```py title="routers/dev/__init__.py" 122 | from . import posts 123 | 124 | EXCLUDED_ROUTERS = [posts] 125 | ``` 126 | 127 | The entire `routers/dev` module can be excluded by importing `dev` in the `routers/__init__.py` 128 | 129 | ```py title="routers/__init__.py" 130 | from . import dev 131 | 132 | EXCLUDED_ROUTERS = [dev] 133 | ``` 134 | 135 | --- 136 | 137 | ## License 138 | 139 | The project is licensed under the terms of the MIT license. 140 | 141 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block extrahead %} 2 | 6 | 7 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /docs/quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | ## New Project 4 | 5 | First, be sure you have a python environment created and activated. 6 | 7 | Create a project with the following structure: 8 | ``` 9 | routers 10 | ├── items.py 11 | └── __init__.py 12 | app.py 13 | __init__.py 14 | ``` 15 | 16 | In the `app.py` file, add the following code: 17 | 18 | ```py 19 | from fastapi import FastAPI 20 | from fastapi_endpoints import auto_include_routers 21 | 22 | from . import routers 23 | 24 | app = FastAPI() 25 | 26 | auto_include_routers(app, routers) 27 | ``` 28 | 29 | In the `routers/items.py` file, add the following code: 30 | 31 | ```py 32 | from fastapi import APIRouter 33 | 34 | items_router = APIRouter() 35 | 36 | @items_router.get("/") 37 | def get_items(): 38 | return {"item1": "my-item"} 39 | 40 | 41 | @items_router.get("/{item_id}/") 42 | def get_item(item_id: int): 43 | return {"item1": "my-item", "id": item_id} 44 | ``` 45 | 46 | Start the FastAPI webserver using `uvicorn` 47 | 48 | The items endpoints should now appear if you go to 49 | 50 | [http://localhost:8000/docs](http://localhost:8000/docs) 51 | 52 | --- 53 | 54 | ## Existing Project 55 | 56 | An example of an existing project structure: 57 | 58 | ``` 59 | routers 60 | ├── items.py 61 | └── __init__.py 62 | app.py 63 | __init__.py 64 | ``` 65 | 66 | ```py title="app.py" 67 | from fastapi import FastAPI 68 | 69 | from .routers import items 70 | 71 | app = FastAPI() 72 | 73 | app.include_router(items.router, prefix="/api/v1/items") 74 | ``` 75 | 76 | ```py title="routers/items.py" 77 | from fastapi import APIRouter 78 | 79 | items_router = APIRouter() 80 | 81 | @items_router.get("/") 82 | def get_items(): 83 | return {"item1": "my-item"} 84 | 85 | 86 | @items_router.get("/{item_id}/") 87 | def get_item(item_id: int): 88 | return {"item1": "my-item", "id": item_id} 89 | ``` 90 | 91 | First, import `auto_include_routers` in `app.py` and give the `routers` module and `app` as parameters. 92 | 93 | Then, remove the `app.include_router` from the file. 94 | 95 | ```py title="app.py" hl_lines="2 7" 96 | from fastapi import FastAPI 97 | from fastapi_endpoints import auto_include_routers 98 | 99 | from . import routers 100 | 101 | app = FastAPI() 102 | auto_include_routers(app, routers) 103 | ``` 104 | 105 | To keep the same prefix as specified in the `app.include_router`, move `items.py` to `routers/api_v1/items.py`. 106 | 107 | The project structure should look like this now: 108 | 109 | ``` hl_lines="2 3 4" 110 | routers 111 | |── api_v1 112 | | |── items.py 113 | | └── __init__.py 114 | └── __init__.py 115 | app.py 116 | __init__.py 117 | ``` 118 | 119 | Now, the same behaviour is kept and now creating new routers is just adding files to the `api_v1` directory. 120 | 121 | --- 122 | 123 | ## Adding a new router 124 | 125 | A new router can be added to the FastAPI app as simple as creating a new file. Actually, this is how you add 126 | a new router to an existing project. 127 | 128 | For example, having a project structure like this: 129 | 130 | ``` 131 | routers 132 | |── api_v1 133 | | |── items.py 134 | | └── __init__.py 135 | └── __init__.py 136 | app.py 137 | __init__.py 138 | ``` 139 | 140 | Create a new file, like `new_router.py` and add it anywhere within the `routers` project. First, let's try 141 | adding it under `routers/api_v1`. 142 | 143 | ``` hl_lines="3" 144 | routers 145 | |── api_v1 146 | | |── new_router.py 147 | | |── items.py 148 | | └── __init__.py 149 | └── __init__.py 150 | app.py 151 | __init__.py 152 | ``` 153 | 154 | Create a new `APIRouter` instance, and add your routes: 155 | 156 | ```py title="routers/api_v1/new_router.py" 157 | from fastapi import APIRouter 158 | 159 | router = APIRouter() 160 | 161 | # Add routes here 162 | # ... 163 | ``` 164 | 165 | Start the project normally, and now observe that the new endpoints routes have paths `/api/v1/new_router/`. 166 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example app with auto-discovery feature 2 | 3 | This is an example app that demonstrates the auto-discovery feature of the router. 4 | 5 | Using the auto-discovery feature, you can organize your routers in a directory structure and have them automatically registered by the router. 6 | 7 | The `app.py` file contains the main application code, and the `routers` directory contains the router modules. 8 | 9 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladNed/fastapi-endpoints/d7fd2519a408300fc3f597920fdf3d3a5ba1be58/example/__init__.py -------------------------------------------------------------------------------- /example/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi_endpoints import auto_include_routers 3 | 4 | from . import routers 5 | 6 | app = FastAPI() 7 | auto_include_routers(app, routers) 8 | -------------------------------------------------------------------------------- /example/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladNed/fastapi-endpoints/d7fd2519a408300fc3f597920fdf3d3a5ba1be58/example/routers/__init__.py -------------------------------------------------------------------------------- /example/routers/api_v1/__init__.py: -------------------------------------------------------------------------------- 1 | from . import data 2 | 3 | # Remove this line if you want to include the router 4 | EXCLUDED_ROUTERS = [data] 5 | -------------------------------------------------------------------------------- /example/routers/api_v1/data.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(tags=["data"]) 4 | 5 | 6 | @router.get("/") 7 | async def read_root(): 8 | return {"Hello": "World"} 9 | 10 | 11 | @router.get("/items/{item_id}") 12 | async def read_item(item_id: int): 13 | return {"item_id": item_id} 14 | -------------------------------------------------------------------------------- /example/routers/api_v1/probes.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(tags=["probes"]) 4 | 5 | 6 | @router.get("/") 7 | async def read_root(): 8 | return {"msg": "ok"} 9 | 10 | 11 | @router.get("/{msg}/") 12 | async def read_msg(msg: str): 13 | return {"msg": msg} 14 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Site Settings 2 | site_name: fastapi-endpoints 3 | site_url: https://vladNed.github.com/fastapi-endpoints/ 4 | site_author: Vlad Nedelcu 5 | 6 | # Repo Settings 7 | repo_url: https://github.com/vladNed/fastapi-endpoints/ 8 | repo_name: vladNed/fastapi-endpoints 9 | 10 | # Navigation and content 11 | nav: 12 | - Introduction: index.md 13 | - Quick Start: quick-start.md 14 | - Guides: 15 | Auto Discovery: auto-discovery.md 16 | Exclude Routers: exclude-routers.md 17 | - Contributing: CONTRIBUTING.md 18 | 19 | # Theme Customization Settings 20 | theme: 21 | name: material 22 | language: en 23 | custom_dir: docs/overrides 24 | palette: 25 | - scheme: default 26 | primary: deep orange 27 | accent: red 28 | toggle: 29 | icon: material/brightness-7 30 | name: Dark Mode 31 | 32 | - scheme: slate 33 | primary: deep orange 34 | accent: red 35 | toggle: 36 | icon: material/brightness-4 37 | name: Light Mode 38 | font: 39 | text: Inter 40 | code: IBM Plex Mono 41 | logo: assets/logo.png 42 | favicon: assets/endpoints-favicon.ico 43 | features: 44 | - navigation.footer 45 | - content.code.copy 46 | - content.code.select 47 | 48 | extra: 49 | social: 50 | - icon: fontawesome/solid/globe 51 | link: https://safefiles.app 52 | name: SafeFiles - Secure File Sharing 53 | - icon: fontawesome/brands/threads 54 | link: https://www.threads.net/@vladmeisteer 55 | name: vladNed on Threads 56 | - icon: fontawesome/brands/github 57 | link: https://github.com/vladNed 58 | name: vladNed on GitHub 59 | - icon: fontawesome/solid/envelope 60 | link: mailto:vlad@indexone.co 61 | name: Email me at work 62 | 63 | extra_css: 64 | - stylesheets/extra.css 65 | 66 | markdown_extensions: 67 | - pymdownx.highlight: 68 | anchor_linenums: true 69 | line_spans: __span 70 | pygments_lang_class: true 71 | - pymdownx.inlinehilite 72 | - pymdownx.snippets 73 | - pymdownx.superfences 74 | - admonition 75 | - pymdownx.details 76 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 11 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 12 | ] 13 | 14 | [package.dependencies] 15 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} 16 | 17 | [[package]] 18 | name = "anyio" 19 | version = "4.5.2" 20 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 21 | optional = false 22 | python-versions = ">=3.8" 23 | files = [ 24 | {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, 25 | {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, 26 | ] 27 | 28 | [package.dependencies] 29 | exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} 30 | idna = ">=2.8" 31 | sniffio = ">=1.1" 32 | typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} 33 | 34 | [package.extras] 35 | doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 36 | 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", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] 37 | trio = ["trio (>=0.26.1)"] 38 | 39 | [[package]] 40 | name = "babel" 41 | version = "2.17.0" 42 | description = "Internationalization utilities" 43 | optional = false 44 | python-versions = ">=3.8" 45 | files = [ 46 | {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, 47 | {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, 48 | ] 49 | 50 | [package.dependencies] 51 | pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} 52 | 53 | [package.extras] 54 | dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] 55 | 56 | [[package]] 57 | name = "backrefs" 58 | version = "5.7.post1" 59 | description = "A wrapper around re and regex that adds additional back references." 60 | optional = false 61 | python-versions = ">=3.8" 62 | files = [ 63 | {file = "backrefs-5.7.post1-py310-none-any.whl", hash = "sha256:c5e3fd8fd185607a7cb1fefe878cfb09c34c0be3c18328f12c574245f1c0287e"}, 64 | {file = "backrefs-5.7.post1-py311-none-any.whl", hash = "sha256:712ea7e494c5bf3291156e28954dd96d04dc44681d0e5c030adf2623d5606d51"}, 65 | {file = "backrefs-5.7.post1-py312-none-any.whl", hash = "sha256:a6142201c8293e75bce7577ac29e1a9438c12e730d73a59efdd1b75528d1a6c5"}, 66 | {file = "backrefs-5.7.post1-py38-none-any.whl", hash = "sha256:ec61b1ee0a4bfa24267f6b67d0f8c5ffdc8e0d7dc2f18a2685fd1d8d9187054a"}, 67 | {file = "backrefs-5.7.post1-py39-none-any.whl", hash = "sha256:05c04af2bf752bb9a6c9dcebb2aff2fab372d3d9d311f2a138540e307756bd3a"}, 68 | {file = "backrefs-5.7.post1.tar.gz", hash = "sha256:8b0f83b770332ee2f1c8244f4e03c77d127a0fa529328e6a0e77fa25bee99678"}, 69 | ] 70 | 71 | [package.extras] 72 | extras = ["regex"] 73 | 74 | [[package]] 75 | name = "certifi" 76 | version = "2025.1.31" 77 | description = "Python package for providing Mozilla's CA Bundle." 78 | optional = false 79 | python-versions = ">=3.6" 80 | files = [ 81 | {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, 82 | {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, 83 | ] 84 | 85 | [[package]] 86 | name = "charset-normalizer" 87 | version = "3.4.1" 88 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 89 | optional = false 90 | python-versions = ">=3.7" 91 | files = [ 92 | {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, 93 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, 94 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, 95 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, 96 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, 97 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, 98 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, 99 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, 100 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, 101 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, 102 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, 103 | {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, 104 | {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, 105 | {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, 106 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, 107 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, 108 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, 109 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, 110 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, 111 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, 112 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, 113 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, 114 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, 115 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, 116 | {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, 117 | {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, 118 | {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, 119 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, 120 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, 121 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, 122 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, 123 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, 124 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, 125 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, 126 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, 127 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, 128 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, 129 | {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, 130 | {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, 131 | {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, 132 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, 133 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, 134 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, 135 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, 136 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, 137 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, 138 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, 139 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, 140 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, 141 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, 142 | {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, 143 | {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, 144 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, 145 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, 146 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, 147 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, 148 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, 149 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, 150 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, 151 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, 152 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, 153 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, 154 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, 155 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, 156 | {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, 157 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, 158 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, 159 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, 160 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, 161 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, 162 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, 163 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, 164 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, 165 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, 166 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, 167 | {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, 168 | {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, 169 | {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, 170 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, 171 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, 172 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, 173 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, 174 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, 175 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, 176 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, 177 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, 178 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, 179 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, 180 | {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, 181 | {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, 182 | {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, 183 | {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, 184 | ] 185 | 186 | [[package]] 187 | name = "click" 188 | version = "8.1.8" 189 | description = "Composable command line interface toolkit" 190 | optional = false 191 | python-versions = ">=3.7" 192 | files = [ 193 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 194 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 195 | ] 196 | 197 | [package.dependencies] 198 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 199 | 200 | [[package]] 201 | name = "colorama" 202 | version = "0.4.6" 203 | description = "Cross-platform colored terminal text." 204 | optional = false 205 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 206 | files = [ 207 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 208 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 209 | ] 210 | 211 | [[package]] 212 | name = "coverage" 213 | version = "7.6.1" 214 | description = "Code coverage measurement for Python" 215 | optional = false 216 | python-versions = ">=3.8" 217 | files = [ 218 | {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, 219 | {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, 220 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, 221 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, 222 | {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"}, 223 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, 224 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, 225 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, 226 | {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, 227 | {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, 228 | {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, 229 | {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, 230 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, 231 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, 232 | {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"}, 233 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, 234 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, 235 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, 236 | {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, 237 | {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, 238 | {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, 239 | {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, 240 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, 241 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, 242 | {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"}, 243 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, 244 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, 245 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, 246 | {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, 247 | {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, 248 | {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, 249 | {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, 250 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, 251 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, 252 | {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"}, 253 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, 254 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, 255 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, 256 | {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, 257 | {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, 258 | {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, 259 | {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, 260 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, 261 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, 262 | {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"}, 263 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, 264 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, 265 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, 266 | {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, 267 | {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, 268 | {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, 269 | {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, 270 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, 271 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, 272 | {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"}, 273 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, 274 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, 275 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, 276 | {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, 277 | {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, 278 | {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, 279 | {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, 280 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, 281 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, 282 | {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"}, 283 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, 284 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, 285 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, 286 | {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, 287 | {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, 288 | {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, 289 | {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, 290 | ] 291 | 292 | [package.dependencies] 293 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 294 | 295 | [package.extras] 296 | toml = ["tomli"] 297 | 298 | [[package]] 299 | name = "exceptiongroup" 300 | version = "1.2.2" 301 | description = "Backport of PEP 654 (exception groups)" 302 | optional = false 303 | python-versions = ">=3.7" 304 | files = [ 305 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 306 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 307 | ] 308 | 309 | [package.extras] 310 | test = ["pytest (>=6)"] 311 | 312 | [[package]] 313 | name = "fastapi" 314 | version = "0.115.12" 315 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 316 | optional = false 317 | python-versions = ">=3.8" 318 | files = [ 319 | {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, 320 | {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, 321 | ] 322 | 323 | [package.dependencies] 324 | 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" 325 | starlette = ">=0.40.0,<0.47.0" 326 | typing-extensions = ">=4.8.0" 327 | 328 | [package.extras] 329 | all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "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)"] 330 | standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] 331 | 332 | [[package]] 333 | name = "ghp-import" 334 | version = "2.1.0" 335 | description = "Copy your docs directly to the gh-pages branch." 336 | optional = false 337 | python-versions = "*" 338 | files = [ 339 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 340 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 341 | ] 342 | 343 | [package.dependencies] 344 | python-dateutil = ">=2.8.1" 345 | 346 | [package.extras] 347 | dev = ["flake8", "markdown", "twine", "wheel"] 348 | 349 | [[package]] 350 | name = "idna" 351 | version = "3.10" 352 | description = "Internationalized Domain Names in Applications (IDNA)" 353 | optional = false 354 | python-versions = ">=3.6" 355 | files = [ 356 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 357 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 358 | ] 359 | 360 | [package.extras] 361 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 362 | 363 | [[package]] 364 | name = "importlib-metadata" 365 | version = "8.5.0" 366 | description = "Read metadata from Python packages" 367 | optional = false 368 | python-versions = ">=3.8" 369 | files = [ 370 | {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, 371 | {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, 372 | ] 373 | 374 | [package.dependencies] 375 | zipp = ">=3.20" 376 | 377 | [package.extras] 378 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 379 | cover = ["pytest-cov"] 380 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 381 | enabler = ["pytest-enabler (>=2.2)"] 382 | perf = ["ipython"] 383 | test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] 384 | type = ["pytest-mypy"] 385 | 386 | [[package]] 387 | name = "iniconfig" 388 | version = "2.1.0" 389 | description = "brain-dead simple config-ini parsing" 390 | optional = false 391 | python-versions = ">=3.8" 392 | files = [ 393 | {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, 394 | {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, 395 | ] 396 | 397 | [[package]] 398 | name = "isort" 399 | version = "5.13.2" 400 | description = "A Python utility / library to sort Python imports." 401 | optional = false 402 | python-versions = ">=3.8.0" 403 | files = [ 404 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 405 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 406 | ] 407 | 408 | [package.extras] 409 | colors = ["colorama (>=0.4.6)"] 410 | 411 | [[package]] 412 | name = "jinja2" 413 | version = "3.1.6" 414 | description = "A very fast and expressive template engine." 415 | optional = false 416 | python-versions = ">=3.7" 417 | files = [ 418 | {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, 419 | {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, 420 | ] 421 | 422 | [package.dependencies] 423 | MarkupSafe = ">=2.0" 424 | 425 | [package.extras] 426 | i18n = ["Babel (>=2.7)"] 427 | 428 | [[package]] 429 | name = "markdown" 430 | version = "3.7" 431 | description = "Python implementation of John Gruber's Markdown." 432 | optional = false 433 | python-versions = ">=3.8" 434 | files = [ 435 | {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, 436 | {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, 437 | ] 438 | 439 | [package.dependencies] 440 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 441 | 442 | [package.extras] 443 | docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] 444 | testing = ["coverage", "pyyaml"] 445 | 446 | [[package]] 447 | name = "markupsafe" 448 | version = "2.1.5" 449 | description = "Safely add untrusted strings to HTML/XML markup." 450 | optional = false 451 | python-versions = ">=3.7" 452 | files = [ 453 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 454 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 455 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 456 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 457 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 458 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 459 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 460 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 461 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 462 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 463 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 464 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 465 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 466 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 467 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 468 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 469 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 470 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 471 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 472 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 473 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 474 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 475 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 476 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 477 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 478 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 479 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 480 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 481 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 482 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 483 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 484 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 485 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 486 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 487 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 488 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 489 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 490 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 491 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 492 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 493 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 494 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 495 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 496 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 497 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 498 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 499 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 500 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 501 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 502 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 503 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 504 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 505 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 506 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 507 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 508 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 509 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 510 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 511 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 512 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 513 | ] 514 | 515 | [[package]] 516 | name = "mergedeep" 517 | version = "1.3.4" 518 | description = "A deep merge function for 🐍." 519 | optional = false 520 | python-versions = ">=3.6" 521 | files = [ 522 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 523 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 524 | ] 525 | 526 | [[package]] 527 | name = "mkdocs" 528 | version = "1.6.1" 529 | description = "Project documentation with Markdown." 530 | optional = false 531 | python-versions = ">=3.8" 532 | files = [ 533 | {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, 534 | {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, 535 | ] 536 | 537 | [package.dependencies] 538 | click = ">=7.0" 539 | colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} 540 | ghp-import = ">=1.0" 541 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 542 | jinja2 = ">=2.11.1" 543 | markdown = ">=3.3.6" 544 | markupsafe = ">=2.0.1" 545 | mergedeep = ">=1.3.4" 546 | mkdocs-get-deps = ">=0.2.0" 547 | packaging = ">=20.5" 548 | pathspec = ">=0.11.1" 549 | pyyaml = ">=5.1" 550 | pyyaml-env-tag = ">=0.1" 551 | watchdog = ">=2.0" 552 | 553 | [package.extras] 554 | i18n = ["babel (>=2.9.0)"] 555 | min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] 556 | 557 | [[package]] 558 | name = "mkdocs-get-deps" 559 | version = "0.2.0" 560 | description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" 561 | optional = false 562 | python-versions = ">=3.8" 563 | files = [ 564 | {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, 565 | {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, 566 | ] 567 | 568 | [package.dependencies] 569 | importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} 570 | mergedeep = ">=1.3.4" 571 | platformdirs = ">=2.2.0" 572 | pyyaml = ">=5.1" 573 | 574 | [[package]] 575 | name = "mkdocs-material" 576 | version = "9.6.11" 577 | description = "Documentation that simply works" 578 | optional = false 579 | python-versions = ">=3.8" 580 | files = [ 581 | {file = "mkdocs_material-9.6.11-py3-none-any.whl", hash = "sha256:47f21ef9cbf4f0ebdce78a2ceecaa5d413581a55141e4464902224ebbc0b1263"}, 582 | {file = "mkdocs_material-9.6.11.tar.gz", hash = "sha256:0b7f4a0145c5074cdd692e4362d232fb25ef5b23328d0ec1ab287af77cc0deff"}, 583 | ] 584 | 585 | [package.dependencies] 586 | babel = ">=2.10,<3.0" 587 | backrefs = ">=5.7.post1,<6.0" 588 | colorama = ">=0.4,<1.0" 589 | jinja2 = ">=3.1,<4.0" 590 | markdown = ">=3.2,<4.0" 591 | mkdocs = ">=1.6,<2.0" 592 | mkdocs-material-extensions = ">=1.3,<2.0" 593 | paginate = ">=0.5,<1.0" 594 | pygments = ">=2.16,<3.0" 595 | pymdown-extensions = ">=10.2,<11.0" 596 | requests = ">=2.26,<3.0" 597 | 598 | [package.extras] 599 | git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] 600 | imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] 601 | recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] 602 | 603 | [[package]] 604 | name = "mkdocs-material-extensions" 605 | version = "1.3.1" 606 | description = "Extension pack for Python Markdown and MkDocs Material." 607 | optional = false 608 | python-versions = ">=3.8" 609 | files = [ 610 | {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, 611 | {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, 612 | ] 613 | 614 | [[package]] 615 | name = "packaging" 616 | version = "24.2" 617 | description = "Core utilities for Python packages" 618 | optional = false 619 | python-versions = ">=3.8" 620 | files = [ 621 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 622 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 623 | ] 624 | 625 | [[package]] 626 | name = "paginate" 627 | version = "0.5.7" 628 | description = "Divides large result sets into pages for easier browsing" 629 | optional = false 630 | python-versions = "*" 631 | files = [ 632 | {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, 633 | {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, 634 | ] 635 | 636 | [package.extras] 637 | dev = ["pytest", "tox"] 638 | lint = ["black"] 639 | 640 | [[package]] 641 | name = "pathspec" 642 | version = "0.12.1" 643 | description = "Utility library for gitignore style pattern matching of file paths." 644 | optional = false 645 | python-versions = ">=3.8" 646 | files = [ 647 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 648 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 649 | ] 650 | 651 | [[package]] 652 | name = "platformdirs" 653 | version = "4.3.6" 654 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 655 | optional = false 656 | python-versions = ">=3.8" 657 | files = [ 658 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 659 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 660 | ] 661 | 662 | [package.extras] 663 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 664 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 665 | type = ["mypy (>=1.11.2)"] 666 | 667 | [[package]] 668 | name = "pluggy" 669 | version = "1.5.0" 670 | description = "plugin and hook calling mechanisms for python" 671 | optional = false 672 | python-versions = ">=3.8" 673 | files = [ 674 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 675 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 676 | ] 677 | 678 | [package.extras] 679 | dev = ["pre-commit", "tox"] 680 | testing = ["pytest", "pytest-benchmark"] 681 | 682 | [[package]] 683 | name = "pydantic" 684 | version = "2.10.6" 685 | description = "Data validation using Python type hints" 686 | optional = false 687 | python-versions = ">=3.8" 688 | files = [ 689 | {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, 690 | {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, 691 | ] 692 | 693 | [package.dependencies] 694 | annotated-types = ">=0.6.0" 695 | pydantic-core = "2.27.2" 696 | typing-extensions = ">=4.12.2" 697 | 698 | [package.extras] 699 | email = ["email-validator (>=2.0.0)"] 700 | timezone = ["tzdata"] 701 | 702 | [[package]] 703 | name = "pydantic-core" 704 | version = "2.27.2" 705 | description = "Core functionality for Pydantic validation and serialization" 706 | optional = false 707 | python-versions = ">=3.8" 708 | files = [ 709 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, 710 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, 711 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, 712 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, 713 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, 714 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, 715 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, 716 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, 717 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, 718 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, 719 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, 720 | {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, 721 | {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, 722 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, 723 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, 724 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, 725 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, 726 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, 727 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, 728 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, 729 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, 730 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, 731 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, 732 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, 733 | {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, 734 | {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, 735 | {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, 736 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, 737 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, 738 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, 739 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, 740 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, 741 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, 742 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, 743 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, 744 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, 745 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, 746 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, 747 | {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, 748 | {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, 749 | {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, 750 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, 751 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, 752 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, 753 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, 754 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, 755 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, 756 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, 757 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, 758 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, 759 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, 760 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, 761 | {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, 762 | {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, 763 | {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, 764 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, 765 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, 766 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, 767 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, 768 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, 769 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, 770 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, 771 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, 772 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, 773 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, 774 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, 775 | {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, 776 | {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, 777 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, 778 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, 779 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, 780 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, 781 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, 782 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, 783 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, 784 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, 785 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, 786 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, 787 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, 788 | {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, 789 | {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, 790 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, 791 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, 792 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, 793 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, 794 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, 795 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, 796 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, 797 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, 798 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, 799 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, 800 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, 801 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, 802 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, 803 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, 804 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, 805 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, 806 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, 807 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, 808 | {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, 809 | ] 810 | 811 | [package.dependencies] 812 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 813 | 814 | [[package]] 815 | name = "pygments" 816 | version = "2.19.1" 817 | description = "Pygments is a syntax highlighting package written in Python." 818 | optional = false 819 | python-versions = ">=3.8" 820 | files = [ 821 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 822 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 823 | ] 824 | 825 | [package.extras] 826 | windows-terminal = ["colorama (>=0.4.6)"] 827 | 828 | [[package]] 829 | name = "pymdown-extensions" 830 | version = "10.14.3" 831 | description = "Extension pack for Python Markdown." 832 | optional = false 833 | python-versions = ">=3.8" 834 | files = [ 835 | {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, 836 | {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, 837 | ] 838 | 839 | [package.dependencies] 840 | markdown = ">=3.6" 841 | pyyaml = "*" 842 | 843 | [package.extras] 844 | extra = ["pygments (>=2.19.1)"] 845 | 846 | [[package]] 847 | name = "pytest" 848 | version = "8.3.5" 849 | description = "pytest: simple powerful testing with Python" 850 | optional = false 851 | python-versions = ">=3.8" 852 | files = [ 853 | {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, 854 | {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, 855 | ] 856 | 857 | [package.dependencies] 858 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 859 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 860 | iniconfig = "*" 861 | packaging = "*" 862 | pluggy = ">=1.5,<2" 863 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 864 | 865 | [package.extras] 866 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 867 | 868 | [[package]] 869 | name = "pytest-cov" 870 | version = "5.0.0" 871 | description = "Pytest plugin for measuring coverage." 872 | optional = false 873 | python-versions = ">=3.8" 874 | files = [ 875 | {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, 876 | {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, 877 | ] 878 | 879 | [package.dependencies] 880 | coverage = {version = ">=5.2.1", extras = ["toml"]} 881 | pytest = ">=4.6" 882 | 883 | [package.extras] 884 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] 885 | 886 | [[package]] 887 | name = "python-dateutil" 888 | version = "2.9.0.post0" 889 | description = "Extensions to the standard Python datetime module" 890 | optional = false 891 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 892 | files = [ 893 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 894 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 895 | ] 896 | 897 | [package.dependencies] 898 | six = ">=1.5" 899 | 900 | [[package]] 901 | name = "pytz" 902 | version = "2025.2" 903 | description = "World timezone definitions, modern and historical" 904 | optional = false 905 | python-versions = "*" 906 | files = [ 907 | {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, 908 | {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, 909 | ] 910 | 911 | [[package]] 912 | name = "pyyaml" 913 | version = "6.0.2" 914 | description = "YAML parser and emitter for Python" 915 | optional = false 916 | python-versions = ">=3.8" 917 | files = [ 918 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 919 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 920 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 921 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 922 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 923 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 924 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 925 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 926 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 927 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 928 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 929 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 930 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 931 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 932 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 933 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 934 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 935 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 936 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 937 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 938 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 939 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 940 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 941 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 942 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 943 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 944 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 945 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 946 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 947 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 948 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 949 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 950 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 951 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 952 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 953 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 954 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 955 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 956 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 957 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 958 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 959 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 960 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 961 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 962 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 963 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 964 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 965 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 966 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 967 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 968 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 969 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 970 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 971 | ] 972 | 973 | [[package]] 974 | name = "pyyaml-env-tag" 975 | version = "0.1" 976 | description = "A custom YAML tag for referencing environment variables in YAML files. " 977 | optional = false 978 | python-versions = ">=3.6" 979 | files = [ 980 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 981 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 982 | ] 983 | 984 | [package.dependencies] 985 | pyyaml = "*" 986 | 987 | [[package]] 988 | name = "requests" 989 | version = "2.32.3" 990 | description = "Python HTTP for Humans." 991 | optional = false 992 | python-versions = ">=3.8" 993 | files = [ 994 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 995 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 996 | ] 997 | 998 | [package.dependencies] 999 | certifi = ">=2017.4.17" 1000 | charset-normalizer = ">=2,<4" 1001 | idna = ">=2.5,<4" 1002 | urllib3 = ">=1.21.1,<3" 1003 | 1004 | [package.extras] 1005 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1006 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1007 | 1008 | [[package]] 1009 | name = "ruff" 1010 | version = "0.6.9" 1011 | description = "An extremely fast Python linter and code formatter, written in Rust." 1012 | optional = false 1013 | python-versions = ">=3.7" 1014 | files = [ 1015 | {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, 1016 | {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, 1017 | {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, 1018 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, 1019 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, 1020 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, 1021 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, 1022 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, 1023 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, 1024 | {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, 1025 | {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, 1026 | {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, 1027 | {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, 1028 | {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, 1029 | {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, 1030 | {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, 1031 | {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, 1032 | {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "six" 1037 | version = "1.17.0" 1038 | description = "Python 2 and 3 compatibility utilities" 1039 | optional = false 1040 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1041 | files = [ 1042 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 1043 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "sniffio" 1048 | version = "1.3.1" 1049 | description = "Sniff out which async library your code is running under" 1050 | optional = false 1051 | python-versions = ">=3.7" 1052 | files = [ 1053 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 1054 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "starlette" 1059 | version = "0.44.0" 1060 | description = "The little ASGI library that shines." 1061 | optional = false 1062 | python-versions = ">=3.8" 1063 | files = [ 1064 | {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, 1065 | {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, 1066 | ] 1067 | 1068 | [package.dependencies] 1069 | anyio = ">=3.4.0,<5" 1070 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 1071 | 1072 | [package.extras] 1073 | full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] 1074 | 1075 | [[package]] 1076 | name = "tomli" 1077 | version = "2.2.1" 1078 | description = "A lil' TOML parser" 1079 | optional = false 1080 | python-versions = ">=3.8" 1081 | files = [ 1082 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 1083 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 1084 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 1085 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 1086 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 1087 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 1088 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 1089 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 1090 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 1091 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 1092 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 1093 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 1094 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 1095 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 1096 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 1097 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 1098 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 1099 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 1100 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 1101 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 1102 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 1103 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 1104 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 1105 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 1106 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 1107 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 1108 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 1109 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 1110 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 1111 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 1112 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 1113 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "typing-extensions" 1118 | version = "4.13.0" 1119 | description = "Backported and Experimental Type Hints for Python 3.8+" 1120 | optional = false 1121 | python-versions = ">=3.8" 1122 | files = [ 1123 | {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, 1124 | {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "urllib3" 1129 | version = "2.2.3" 1130 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1131 | optional = false 1132 | python-versions = ">=3.8" 1133 | files = [ 1134 | {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, 1135 | {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, 1136 | ] 1137 | 1138 | [package.extras] 1139 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1140 | h2 = ["h2 (>=4,<5)"] 1141 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1142 | zstd = ["zstandard (>=0.18.0)"] 1143 | 1144 | [[package]] 1145 | name = "watchdog" 1146 | version = "4.0.2" 1147 | description = "Filesystem events monitoring" 1148 | optional = false 1149 | python-versions = ">=3.8" 1150 | files = [ 1151 | {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, 1152 | {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, 1153 | {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, 1154 | {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, 1155 | {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, 1156 | {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, 1157 | {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, 1158 | {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, 1159 | {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, 1160 | {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, 1161 | {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, 1162 | {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, 1163 | {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, 1164 | {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, 1165 | {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, 1166 | {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, 1167 | {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, 1168 | {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, 1169 | {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, 1170 | {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, 1171 | {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, 1172 | {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, 1173 | {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, 1174 | {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, 1175 | {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, 1176 | {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, 1177 | {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, 1178 | {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, 1179 | {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, 1180 | {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, 1181 | {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, 1182 | {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, 1183 | {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, 1184 | {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, 1185 | {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, 1186 | ] 1187 | 1188 | [package.extras] 1189 | watchmedo = ["PyYAML (>=3.10)"] 1190 | 1191 | [[package]] 1192 | name = "zipp" 1193 | version = "3.20.2" 1194 | description = "Backport of pathlib-compatible object wrapper for zip files" 1195 | optional = false 1196 | python-versions = ">=3.8" 1197 | files = [ 1198 | {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, 1199 | {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, 1200 | ] 1201 | 1202 | [package.extras] 1203 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 1204 | cover = ["pytest-cov"] 1205 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1206 | enabler = ["pytest-enabler (>=2.2)"] 1207 | test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] 1208 | type = ["pytest-mypy"] 1209 | 1210 | [metadata] 1211 | lock-version = "2.0" 1212 | python-versions = "^3.8" 1213 | content-hash = "6249353dc2e9f5d3a1b7b7bb5e2af527b59fa5d742db139c4098be59be505e8e" 1214 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | create = false 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi-endpoints" 3 | version = "0.1.0" 4 | description = "A file based router for FastAPI that helps with defining endpoints" 5 | authors = ["Vlad Nedelcu "] 6 | license = "MIT" 7 | readme = "README.md" 8 | packages = [ 9 | { include="fastapi_endpoints", from="src" }, 10 | ] 11 | 12 | [tool.poetry.dependencies] 13 | python = "^3.8" 14 | fastapi = ">=0.112.4" 15 | 16 | 17 | [tool.poetry.group.dev.dependencies] 18 | ruff = "^0.6.2" 19 | isort = "^5.13.2" 20 | pytest = "^8.3.2" 21 | pytest-cov = "^5.0.0" 22 | mkdocs = "^1.6.0" 23 | mkdocs-material = "^9.5.33" 24 | 25 | 26 | [tool.pytest.ini_options] 27 | addopts = "-r a --durations 5 -vv --cov=src/fastapi_endpoints --cov-report term-missing --no-cov-on-fail --cov-fail-under 95" 28 | console_output_style = "count" 29 | log_cli = "1" 30 | log_level = "DEBUG" 31 | log_format = "%(levelname)-8s | %(name)s:%(lineno)d | %(message)s" 32 | testpaths = [ 33 | "tests" 34 | ] 35 | 36 | 37 | [tool.ruff] 38 | exclude = [ 39 | ".git", 40 | ".pytest_cache", 41 | ".ruff_cache" 42 | ] 43 | 44 | line-length = 120 45 | indent-width = 4 46 | 47 | 48 | [tool.ruff.format] 49 | quote-style = "double" 50 | indent-style = "space" 51 | 52 | [build-system] 53 | requires = ["poetry-core"] 54 | build-backend = "poetry.core.masonry.api" 55 | -------------------------------------------------------------------------------- /requirements-build.txt: -------------------------------------------------------------------------------- 1 | pip==23.3.1 2 | setuptools==69.0.2 3 | poetry~=1.6.1 4 | -------------------------------------------------------------------------------- /src/fastapi_endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | # fastapi-endpoints 2 | # Copyright (c) 2024 Vlad Nedelcu 3 | # Licensed under the MIT License 4 | 5 | from .router import auto_include_routers 6 | 7 | __all__ = ["auto_include_routers"] 8 | -------------------------------------------------------------------------------- /src/fastapi_endpoints/constants.py: -------------------------------------------------------------------------------- 1 | # fastapi-endpoints 2 | # Copyright (c) 2024 Vlad Nedelcu 3 | # Licensed under the MIT License 4 | 5 | DEFAULT_ENDPOINTS_ROOT = "routers" 6 | DEFAULT_EXCLUDED_ROUTERS = "EXCLUDED_ROUTERS" 7 | -------------------------------------------------------------------------------- /src/fastapi_endpoints/exceptions.py: -------------------------------------------------------------------------------- 1 | # fastapi-endpoints 2 | # Copyright (c) 2024 Vlad Nedelcu 3 | # Licensed under the MIT License 4 | 5 | 6 | class BaseOSException(Exception): 7 | """The base exception used to manage the message in a dynamic way""" 8 | 9 | message: str 10 | 11 | def __init__(self): 12 | super().__init__(self.message) 13 | 14 | 15 | class InitializationError(BaseOSException): 16 | """Error for any issue that comes when the routers are being fetched 17 | and included in the app 18 | """ 19 | 20 | message = "Could not initialize routes as modules are not defined correctly" 21 | 22 | 23 | class RouterNotFound(BaseOSException): 24 | """FastAPI APIRouter instance is not found in the router file 25 | module. 26 | """ 27 | 28 | message = "Router module does not have an instance of APIRouter" 29 | -------------------------------------------------------------------------------- /src/fastapi_endpoints/router.py: -------------------------------------------------------------------------------- 1 | # fastapi-endpoints 2 | # Copyright (c) 2024 Vlad Nedelcu 3 | # Licensed under the MIT License 4 | 5 | import importlib 6 | import pkgutil 7 | from types import ModuleType 8 | from typing import Set 9 | 10 | import fastapi 11 | 12 | from . import exceptions, utils 13 | 14 | Excluded = Set[ModuleType] 15 | 16 | 17 | def _handle_package_module(module: ModuleType, excluded_routers: Excluded) -> None: 18 | """Handles the packages modules. 19 | 20 | It scans the `__init__.py` file of the package and fetches the excluded 21 | routers. 22 | 23 | If the module is in the excluded routers, it will not be included in the 24 | application. This means that the excluded routers will also not be included. 25 | """ 26 | if module in excluded_routers: 27 | return 28 | 29 | excluded_routers.update(utils.fetch_excluded_routers(module)) 30 | 31 | 32 | def _handle_non_package_module(module: ModuleType, excluded_routers: Excluded, application: fastapi.FastAPI) -> None: 33 | """Handles the non-package modules and files. 34 | 35 | The function scans the module for an `fastapi.APIRouter` instance. If it 36 | finds one, it includes it in the application. 37 | 38 | If the module is in the excluded routers, it will not be included in the 39 | application. 40 | 41 | :raises RouterNotFound: If the module does not contain an `APIRouter` 42 | :raises InitializationError: If the module is not defined correctly within 43 | the `routers` module. 44 | """ 45 | package = importlib.import_module(module.__package__) # type: ignore 46 | if module in excluded_routers or package in excluded_routers: 47 | return 48 | 49 | route_path = utils.extract_route_path(module.__name__) 50 | route_prefix = utils.format_prefix(route_path) 51 | module_router = utils.get_module_router(module) 52 | if module_router is None: 53 | raise exceptions.RouterNotFound() 54 | 55 | application.include_router(module_router, prefix=route_prefix) 56 | 57 | 58 | def _process_module(module: ModuleType, is_pkg: bool, excluded_routers: Excluded, application: fastapi.FastAPI) -> None: 59 | """Processes the module based on its type. 60 | 61 | If the module is a package, it will call the `_handle_package_module` 62 | function. Otherwise, it will call the `_handle_non_package_module` function. 63 | 64 | :raises InitializationError: If the module is not defined correctly within 65 | :raises RouterNotFound: If the module does not contain an `APIRouter` 66 | """ 67 | if is_pkg: 68 | _handle_package_module(module, excluded_routers) 69 | else: 70 | _handle_non_package_module(module, excluded_routers, application) 71 | 72 | 73 | def auto_include_routers(application: fastapi.FastAPI, router_module: ModuleType) -> None: 74 | """Include all routers in the router module.""" 75 | 76 | excluded_routers = utils.fetch_excluded_routers(router_module) 77 | packages = list(pkgutil.walk_packages(router_module.__path__, router_module.__name__ + ".")) 78 | if len(packages) == 0: 79 | raise exceptions.InitializationError() 80 | 81 | for _, module_name, is_pkg in packages: 82 | module = importlib.import_module(module_name) 83 | _process_module(module, is_pkg, excluded_routers, application) 84 | -------------------------------------------------------------------------------- /src/fastapi_endpoints/utils.py: -------------------------------------------------------------------------------- 1 | # fastapi-endpoints 2 | # Copyright (c) 2024 Vlad Nedelcu 3 | # Licensed under the MIT License 4 | 5 | from types import ModuleType 6 | from typing import Optional, Set 7 | 8 | import fastapi 9 | 10 | from . import constants, exceptions 11 | 12 | 13 | def format_prefix(route_path: str) -> str: 14 | return route_path.replace("_", "/").replace(".", "/") 15 | 16 | 17 | def get_module_router(module: ModuleType) -> Optional[fastapi.APIRouter]: 18 | for attr in dir(module): 19 | attr_value = getattr(module, attr) 20 | if isinstance(attr_value, fastapi.APIRouter): 21 | return attr_value 22 | 23 | return None 24 | 25 | 26 | def extract_route_path(module_name: str) -> str: 27 | try: 28 | _, endpoint_path = module_name.split(constants.DEFAULT_ENDPOINTS_ROOT) 29 | except ValueError: 30 | raise exceptions.InitializationError() 31 | 32 | return endpoint_path 33 | 34 | 35 | def fetch_excluded_routers(router_module: ModuleType) -> Set[ModuleType]: 36 | excluded_routers = set() 37 | if hasattr(router_module, constants.DEFAULT_EXCLUDED_ROUTERS): 38 | excluded_routers.update(getattr(router_module, constants.DEFAULT_EXCLUDED_ROUTERS)) 39 | 40 | return excluded_routers 41 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from types import ModuleType 2 | from unittest import mock 3 | 4 | import fastapi 5 | import pytest 6 | 7 | 8 | @pytest.fixture 9 | def mock_application() -> mock.Mock: 10 | return mock.Mock(spec=fastapi.FastAPI()) 11 | 12 | 13 | @pytest.fixture 14 | def mock_router_one(): 15 | 16 | mock_router1_module = mock.Mock(spec=ModuleType) 17 | mock_router1_module.router = mock.Mock(spec=fastapi.APIRouter) 18 | mock_router1_module.__name__ = "test_app.routers.api.one" 19 | mock_router1_module.__package__ = "test_app.routers.api" 20 | 21 | return mock_router1_module 22 | 23 | 24 | @pytest.fixture 25 | def mock_router_two(): 26 | mock_router2_module = mock.Mock(spec=ModuleType) 27 | mock_router2_module.router = mock.Mock(spec=fastapi.APIRouter) 28 | mock_router2_module.__name__ = "test_app.routers.api.two" 29 | mock_router2_module.__package__ = "test_app.routers.api" 30 | 31 | return mock_router2_module 32 | 33 | 34 | @pytest.fixture 35 | def mock_routers_module(): 36 | test_routers_module = mock.Mock(spec=ModuleType) 37 | test_routers_module.__path__ = ["test_app"] 38 | test_routers_module.__name__ = "test_app" 39 | 40 | return test_routers_module 41 | 42 | 43 | @pytest.fixture 44 | def mock_module_without_router(): 45 | test_routers_module = mock.Mock(spec=ModuleType) 46 | test_routers_module.__name__ = "test_app.routers.api.three" 47 | test_routers_module.__package__ = "test_app.routers.api" 48 | 49 | return test_routers_module 50 | 51 | 52 | @pytest.fixture 53 | def mock_incorrect_routers_module(): 54 | test_routers_module = mock.Mock(spec=ModuleType) 55 | test_routers_module.__path__ = ["test_app"] 56 | test_routers_module.__name__ = "test_app" 57 | 58 | with mock.patch("pkgutil.walk_packages", return_value=[]): 59 | return test_routers_module 60 | 61 | 62 | @pytest.fixture 63 | def mock_excluded_routers_router(): 64 | mock_excluded_router_module = mock.Mock(spec=ModuleType) 65 | mock_excluded_router_module.__name__ = "test_app.routers.api" 66 | mock_excluded_router_module.__package__ = "test_app.routers" 67 | mock_excluded_router_module.EXCLUDED_ROUTERS = ["test_app.routers.api.one"] 68 | 69 | return mock_excluded_router_module 70 | -------------------------------------------------------------------------------- /tests/test_router.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | import pytest 4 | 5 | from fastapi_endpoints import auto_include_routers, exceptions 6 | 7 | 8 | def test_auto_include_routers_incorrect_module(mock_incorrect_routers_module, mock_application): 9 | with pytest.raises(exceptions.InitializationError): 10 | auto_include_routers(mock_application, mock_incorrect_routers_module) 11 | 12 | 13 | def test_auto_include_routers(mock_routers_module, mock_application, mock_router_one, mock_router_two): 14 | calls = [ 15 | mock.call(mock_router_one.router, prefix="/api/one"), 16 | mock.call(mock_router_two.router, prefix="/api/two"), 17 | ] 18 | with mock.patch( 19 | "pkgutil.walk_packages", 20 | return_value=[ 21 | (None, mock_routers_module.__name__, True), 22 | (None, mock_router_one.__name__, False), 23 | (None, mock_router_two.__name__, False), 24 | ], 25 | ): 26 | with mock.patch( 27 | "importlib.import_module", 28 | side_effect=[ 29 | mock_routers_module, 30 | mock_router_one, 31 | mock_routers_module, 32 | mock_router_two, 33 | mock_routers_module, 34 | ], 35 | ) as mock_import: 36 | auto_include_routers(mock_application, mock_routers_module) 37 | assert mock_import.call_count == 5 38 | assert mock_application.include_router.call_count == 2 39 | mock_application.include_router.assert_has_calls(calls) 40 | 41 | 42 | def test_auto_include_router_module_with_no_router( 43 | mock_routers_module, 44 | mock_application, 45 | mock_module_without_router, 46 | ): 47 | with mock.patch( 48 | "pkgutil.walk_packages", 49 | return_value=[ 50 | (None, mock_routers_module.__name__, True), 51 | (None, mock_module_without_router.__name__, False), 52 | ], 53 | ): 54 | with mock.patch( 55 | "importlib.import_module", 56 | side_effect=[mock_routers_module, mock_module_without_router, mock_routers_module], 57 | ): 58 | with pytest.raises(exceptions.RouterNotFound): 59 | auto_include_routers(mock_application, mock_routers_module) 60 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from types import ModuleType 2 | from unittest import mock 3 | 4 | import fastapi 5 | import pytest 6 | 7 | from fastapi_endpoints import constants, exceptions, utils 8 | 9 | FORMAT_TEST_CASES = { 10 | "UNDERSCORE_REPLACE": ("foo_bar", "foo/bar"), 11 | "MODULE_TO_PATH_REPLACE": ("foo.bar", "foo/bar"), 12 | "UNDERSCORE_AND_MODULE_REPLACE": ("foo_bar.baz.qux", "foo/bar/baz/qux"), 13 | } 14 | 15 | 16 | @pytest.mark.parametrize("input, expected", FORMAT_TEST_CASES.values(), ids=FORMAT_TEST_CASES.keys()) 17 | def test_format_prefix(input: str, expected: str): 18 | assert utils.format_prefix(input) == expected 19 | 20 | 21 | def test_get_module_router(): 22 | mock_router = fastapi.APIRouter() 23 | mock_module = mock.Mock(spec=ModuleType) 24 | mock_module.router = mock_router 25 | 26 | assert utils.get_module_router(mock_module) == mock_router 27 | 28 | 29 | def test_get_module_router_no_router(): 30 | mock_module = mock.Mock(spec=ModuleType) 31 | 32 | assert utils.get_module_router(mock_module) is None 33 | 34 | 35 | def test_extract_route_path(): 36 | module_name = f"some_module.{constants.DEFAULT_ENDPOINTS_ROOT}.endpoint" 37 | assert utils.extract_route_path(module_name) == ".endpoint" 38 | 39 | with pytest.raises(exceptions.InitializationError): 40 | utils.extract_route_path("invalid_module_name") 41 | 42 | 43 | def test_get_excluded_routers_no_routers(mock_router_one): 44 | assert utils.fetch_excluded_routers(mock_router_one) == set() 45 | 46 | 47 | def test_get_excluded_routers(mock_excluded_routers_router): 48 | assert utils.fetch_excluded_routers(mock_excluded_routers_router) == {"test_app.routers.api.one"} 49 | --------------------------------------------------------------------------------