├── .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 | [](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 |
--------------------------------------------------------------------------------