diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index 7833a718..00000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[bumpversion] -current_version = 0.19.2 -tag = True -tag_name = {new_version} -commit = True -message = Version {new_version} -parse = (?P\d+)\.(?P\d+)\.(?P\d+) -serialize = - {major}.{minor}.{patch} - -[bumpversion:file:openapi_core/__init__.py] - -[bumpversion:file:pyproject.toml] -search = version = "{current_version}" -replace = version = "{new_version}" diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 528267a2..a39b13f9 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -1,4 +1,4 @@ -name: Build documentation +name: CI / Docs on: push: @@ -6,47 +6,51 @@ on: types: [opened, synchronize] jobs: - build: + docs_build: + name: "Build" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Set up Python 3.9 - uses: actions/setup-python@v5 + - name: Set up Python + uses: actions/setup-python@v6 with: - python-version: 3.9 + python-version: "3.10" - name: Get full Python version id: full-python-version - run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") + run: python -c "import sys; print(f\"version={'-'.join(str(v) for v in sys.version_info)}\")" >> "$GITHUB_OUTPUT" - name: Set up poetry uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.3.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache with: - path: .venv - key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Ensure cache is healthy - if: steps.cache.outputs.cache-hit == 'true' - run: timeout 10s poetry run pip --version || rm -rf .venv + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-docs-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-docs- + deps-${{ runner.os }}-3.10- - name: Install dependencies run: poetry install --with docs - name: Build documentation run: | - poetry run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs docs/_build/html -n -W + poetry run python -m mkdocs build --clean --site-dir ./_build/html --config-file mkdocs.yml - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 name: Upload docs as artifact with: name: docs-html - path: './docs/_build/html' + path: './_build/html' if-no-files-found: error diff --git a/.github/workflows/contrib-tests.yml b/.github/workflows/contrib-tests.yml new file mode 100644 index 00000000..dce26752 --- /dev/null +++ b/.github/workflows/contrib-tests.yml @@ -0,0 +1,84 @@ +name: CI / Contrib / Tests + +on: + push: + paths: + - openapi_core/** + - tests/integration/contrib/** + - tests/unit/contrib/** + - tests/integration/conftest.py + - tests/integration/data/** + - pyproject.toml + - poetry.lock + - .github/workflows/contrib-tests.yml + pull_request: + types: [opened, synchronize] + paths: + - openapi_core/** + - tests/integration/contrib/** + - tests/unit/contrib/** + - tests/integration/conftest.py + - tests/integration/data/** + - pyproject.toml + - poetry.lock + - .github/workflows/contrib-tests.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write + +jobs: + contrib_matrix: + name: "py${{ matrix.python-version }}" + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + fail-fast: false + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Get full Python version + id: full-python-version + run: python -c "import sys; print(f\"version={'-'.join(str(v) for v in sys.version_info)}\")" >> "$GITHUB_OUTPUT" + + - name: Set up poetry + uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.3.1" + + - name: Set up cache + uses: actions/cache@v5 + id: cache + with: + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-contrib-${{ hashFiles('**/poetry.lock', 'pyproject.toml') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-contrib- + deps-${{ runner.os }}-${{ matrix.python-version }}- + + - name: Install tox + run: python -m pip install tox + + - name: Test + env: + TOX_SKIP_ENV: ${{ contains(fromJSON('["3.10", "3.11"]'), matrix.python-version) && '^contrib-django-6x$' || '' }} + run: tox -m contrib -p auto + + - name: Upload coverage + uses: codecov/codecov-action@v6 + with: + files: reports/coverage-*.xml + flags: contrib,py${{ matrix.python-version }} + name: contrib-py${{ matrix.python-version }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e6b1ef54..478232ef 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,7 +1,7 @@ # This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -name: Publish python packages +name: CI / Publish on: workflow_dispatch: @@ -10,20 +10,23 @@ on: - published jobs: - publish: + publish_pypi: + name: "PyPI" runs-on: ubuntu-latest permissions: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.x' - name: Set up poetry uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.3.1" - name: Build run: poetry build diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml deleted file mode 100644 index 52611409..00000000 --- a/.github/workflows/python-test.yml +++ /dev/null @@ -1,102 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Test python code - -on: - push: - pull_request: - types: [opened, synchronize] - -jobs: - test: - name: "Tests" - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - fail-fast: false - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Get full Python version - id: full-python-version - run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - - - name: Set up poetry - uses: Gr1N/setup-poetry@v9 - - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - - name: Set up cache - uses: actions/cache@v4 - id: cache - with: - path: .venv - key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Ensure cache is healthy - if: steps.cache.outputs.cache-hit == 'true' - run: timeout 10s poetry run pip --version || rm -rf .venv - - - name: Install dependencies - run: poetry install --all-extras - - - name: Test - env: - PYTEST_ADDOPTS: "--color=yes" - run: poetry run pytest - - - name: Static type check - run: poetry run mypy - - - name: Check dependencies - run: poetry run deptry . - - - name: Upload coverage - uses: codecov/codecov-action@v4 - - static-checks: - name: "Static checks" - runs-on: ubuntu-latest - steps: - - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 - - - name: "Setup Python" - uses: actions/setup-python@v5 - with: - python-version: 3.9 - - - name: Get full Python version - id: full-python-version - run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - - - name: Set up poetry - uses: Gr1N/setup-poetry@v9 - - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - - name: Set up cache - uses: actions/cache@v4 - id: cache - with: - path: .venv - key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Ensure cache is healthy - if: steps.cache.outputs.cache-hit == 'true' - run: timeout 10s poetry run pip --version || rm -rf .venv - - - name: Install dependencies - run: poetry install - - - name: Run static checks - run: poetry run pre-commit run -a diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 00000000..b4cc13fa --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,125 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: CI / Core / Tests + +on: + push: + paths-ignore: + - README.md + - openapi_core/contrib/** + - tests/integration/contrib/** + - tests/unit/contrib/** + - docs/integrations/** + - .github/workflows/contrib-tests.yml + pull_request: + types: [opened, synchronize] + paths-ignore: + - README.md + - openapi_core/contrib/** + - tests/integration/contrib/** + - tests/unit/contrib/** + - docs/integrations/** + - .github/workflows/contrib-tests.yml + +permissions: + contents: read + +jobs: + core_tests: + name: "py${{ matrix.python-version }}" + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + fail-fast: false + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Get full Python version + id: full-python-version + run: python -c "import sys; print(f\"version={'-'.join(str(v) for v in sys.version_info)}\")" >> "$GITHUB_OUTPUT" + + - name: Set up poetry + uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.3.1" + + - name: Configure poetry + run: poetry config virtualenvs.in-project true + + - name: Set up cache + uses: actions/cache@v5 + id: cache + with: + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-core-tests-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-core-tests- + deps-${{ runner.os }}-${{ matrix.python-version }}- + + - name: Install dependencies + run: poetry install --all-extras + + - name: Test + env: + PYTEST_ADDOPTS: "--color=yes" + run: poetry run pytest --ignore=tests/integration/contrib --ignore=tests/unit/contrib + + - name: Static type check + run: poetry run mypy + + - name: Check dependencies + run: poetry run deptry . + + - name: Upload coverage + uses: codecov/codecov-action@v6 + + static_checks: + name: "Core / Static Checks" + runs-on: ubuntu-latest + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v6 + + - name: "Setup Python" + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Get full Python version + id: full-python-version + run: python -c "import sys; print(f\"version={'-'.join(str(v) for v in sys.version_info)}\")" >> "$GITHUB_OUTPUT" + + - name: Set up poetry + uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.3.1" + + - name: Configure poetry + run: poetry config virtualenvs.in-project true + + - name: Set up cache + uses: actions/cache@v5 + id: cache + with: + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-static-checks-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-static-checks- + deps-${{ runner.os }}-3.10- + + - name: Install dependencies + run: poetry install + + - name: Run static checks + run: poetry run pre-commit run -a diff --git a/.gitignore b/.gitignore index 2df6767e..8ae61294 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Byte-compiled / optimized / DLL files -__pycache__/ +**/__pycache__/ *.py[cod] *$py.class .pytest_cache/ @@ -63,7 +63,7 @@ instance/ .scrapy # Sphinx documentation -docs/_build/ +docs_build/ # PyBuilder target/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 29f8d503..fe7c6754 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -2,23 +2,21 @@ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py +# Build documentation with Mkdocs +mkdocs: + configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF and ePub formats: all build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.9" + python: "3.12" jobs: - post_create_environment: - # Install poetry - - pip install poetry - # Tell poetry to not use a virtual environment - - poetry config virtualenvs.create false + post_system_dependencies: + - asdf plugin-add poetry + - asdf install poetry 2.2.1 + - asdf global poetry 2.2.1 post_install: - # Install dependencies - - poetry install --with docs + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --no-interaction --with docs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e983224d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: python -sudo: false -matrix: - include: - - python: 3.8 - - python: 3.9 - - python: 3.10 - - python: nightly - - python: pypy3 - allow_failures: - - python: nightly -before_install: -- curl -sL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - -y -- export PATH=$PATH:$HOME/.local/bin -install: -- poetry install -script: -- poetry run pytest -after_success: -- codecov diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 12bdd471..63eae2c8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1 +1 @@ -Please read the `Contributing `__ guidelines in the documentation site. +Please read the `Contributing `__ guidelines in the documentation site. diff --git a/Makefile b/Makefile index f9ead3cd..56da44fa 100644 --- a/Makefile +++ b/Makefile @@ -32,18 +32,12 @@ reports-cleanup: test-cleanup: test-cache-cleanup reports-cleanup docs-html: - sphinx-build -b html docs docs/_build + python -m mkdocs build --clean --site-dir docs_build --config-file mkdocs.yml docs-cleanup: - @rm -rf docs/_build + @rm -rf docs_build cleanup: dist-cleanup test-cleanup -release/patch: - @bump2version patch - -release/minor: - @bump2version minor - -release/major: - @bump2version major +bench-paths: + @PYTHONHASHSEED=0 python tests/benchmarks/bench_paths.py --paths 500 --templates-ratio 0.7 --lookups 2000 --output bench-paths.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..98e575d3 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# openapi-core + + + Package version + + + Continuous Integration + + + Tests coverage + + + Python versions + + + Package format + + + Development status + + +## About + +Openapi-core is a Python library that provides client-side and server-side support +for the [OpenAPI v3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) +and [OpenAPI v3.1](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) +and [OpenAPI v3.2](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md) specifications. + + +## Key features + +- **Validation** and **unmarshalling** of request and response data (including webhooks) +- **Integration** with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) +- Customization with media type **deserializers** and format **unmarshallers** +- **Security** data providers (API keys, Cookie, Basic, and Bearer HTTP authentications) + + +## Documentation + +Check documentation to see more details about the features. All documentation is in the "docs" directory and online at [openapi-core.readthedocs.io](https://openapi-core.readthedocs.io) + +## Integrations compatibility matrix + +Supported [Integrations](https://openapi-core.readthedocs.io/en/latest/integrations/) and it's versions: + +| Integration | Version(s) | Level | +| --- | --- | --- | +| [AIOHTTP](https://openapi-core.readthedocs.io/en/latest/integrations/aiohttp/) | versions 3.8+ and 3.11+ tracks | Low-level request and response classes | +| [Django](https://openapi-core.readthedocs.io/en/latest/integrations/django/) | versions 4, 5, and 6 | Middleware, decorator and low level | +| [Falcon](https://openapi-core.readthedocs.io/en/latest/integrations/falcon/) | version 4 | Middleware and low-level classes | +| [FastAPI](https://openapi-core.readthedocs.io/en/latest/integrations/fastapi/) | versions 0.11x, 0.12x, and 0.13x | Middleware (low-level via Starlette integration) | +| [Flask](https://openapi-core.readthedocs.io/en/latest/integrations/flask/) | versions 2 and 3 | View decorator, class-based view, and low-level classes | +| [Requests](https://openapi-core.readthedocs.io/en/latest/integrations/requests/) | default dependency set | Low-level request, webhook request, and response classes | +| [Starlette](https://openapi-core.readthedocs.io/en/latest/integrations/starlette/) | versions 0.4x, 0.5x, and 1.x | Middleware and low-level classes | +| [Werkzeug](https://openapi-core.readthedocs.io/en/latest/integrations/werkzeug/) | default dependency set | Low-level request and response classes | + +## Installation + +Recommended way (via pip): + +``` console +pip install openapi-core +``` + +Alternatively you can download the code and install from the repository: + +``` console +pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core +``` + + +## First steps + +First, create your OpenAPI object. + +``` python +from openapi_core import OpenAPI + +openapi = OpenAPI.from_file_path('openapi.json') +``` + +Now you can use it to validate and unmarshal against requests and/or responses. + +``` python +# raises an error if the request is invalid +result = openapi.unmarshal_request(request) +``` + +Retrieve validated and unmarshalled request data. + +``` python +# get parameters +path_params = result.parameters.path +query_params = result.parameters.query +cookies_params = result.parameters.cookies +headers_params = result.parameters.headers +# get body +body = result.body +# get security data +security = result.security +``` + +The request object should implement the OpenAPI Request protocol. Check [Integrations](https://openapi-core.readthedocs.io/en/latest/integrations/) to find officially supported implementations. + +For more details read about the [Unmarshalling](https://openapi-core.readthedocs.io/en/latest/unmarshalling/) process. + +If you just want to validate your request/response data without unmarshalling, read about [Validation](https://openapi-core.readthedocs.io/en/latest/validation/) instead. + + +## Related projects + +- [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator) + : A Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0, OpenAPI 3.1, and OpenAPI 3.2 specification. The validator aims to check for full compliance with the Specification. +- [openapi-schema-validator](https://github.com/python-openapi/openapi-schema-validator) + : A Python library that validates schema against the OpenAPI Schema Specification v3.0, v3.1, and v3.2. +- [bottle-openapi-3](https://github.com/cope-systems/bottle-openapi-3) + : OpenAPI 3.0 Support for the Bottle Web Framework +- [pyramid_openapi3](https://github.com/niteoweb/pyramid_openapi3) + : Pyramid addon for OpenAPI3 validation of requests and responses. +- [tornado-openapi3](https://github.com/correl/tornado-openapi3) + : Tornado OpenAPI 3 request and response validation library. + +## License + +The project is under the terms of the BSD 3-Clause License. diff --git a/README.rst b/README.rst deleted file mode 100644 index 13d74816..00000000 --- a/README.rst +++ /dev/null @@ -1,113 +0,0 @@ -************ -openapi-core -************ - -.. image:: https://img.shields.io/pypi/v/openapi-core.svg - :target: https://pypi.python.org/pypi/openapi-core -.. image:: https://travis-ci.org/python-openapi/openapi-core.svg?branch=master - :target: https://travis-ci.org/python-openapi/openapi-core -.. image:: https://img.shields.io/codecov/c/github/python-openapi/openapi-core/master.svg?style=flat - :target: https://codecov.io/github/python-openapi/openapi-core?branch=master -.. image:: https://img.shields.io/pypi/pyversions/openapi-core.svg - :target: https://pypi.python.org/pypi/openapi-core -.. image:: https://img.shields.io/pypi/format/openapi-core.svg - :target: https://pypi.python.org/pypi/openapi-core -.. image:: https://img.shields.io/pypi/status/openapi-core.svg - :target: https://pypi.python.org/pypi/openapi-core - -About -##### - -Openapi-core is a Python library that adds client-side and server-side support -for the `OpenAPI v3.0 `__ -and `OpenAPI v3.1 `__ specification. - - -Key features -############ - -* **Validation** and **unmarshalling** of request and response data (including webhooks) -* **Integration** with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) -* Customization with media type **deserializers** and format **unmarshallers** -* **Security** data providers (API keys, Cookie, Basic and Bearer HTTP authentications) - - -Documentation -############# - -Check documentation to see more details about the features. All documentation is in the "docs" directory and online at `openapi-core.readthedocs.io `__ - - -Installation -############ - -Recommended way (via pip): - -.. code-block:: console - - pip install openapi-core - -Alternatively you can download the code and install from the repository: - -.. code-block:: console - - pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core - - -First steps -########### - -Firstly create your OpenAPI object. - -.. code-block:: python - - from openapi_core import OpenAPI - - openapi = OpenAPI.from_file_path('openapi.json') - -Now you can use it to validate and unmarshal against requests and/or responses. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(request) - -Retrieve validated and unmarshalled request data - -.. code-block:: python - - # get parameters - path_params = result.parameters.path - query_params = result.parameters.query - cookies_params = result.parameters.cookies - headers_params = result.parameters.headers - # get body - body = result.body - # get security data - security = result.security - -Request object should implement OpenAPI Request protocol. Check `Integrations `__ to find officially supported implementations. - -For more details read about `Unmarshalling `__ process. - -If you just want to validate your request/response data without unmarshalling, read about `Validation `__ instead. - - -Related projects -################ -* `openapi-spec-validator `__ - Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. -* `openapi-schema-validator `__ - Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. -* `bottle-openapi-3 `__ - OpenAPI 3.0 Support for the Bottle Web Framework -* `pyramid_openapi3 `__ - Pyramid addon for OpenAPI3 validation of requests and responses. -* `tornado-openapi3 `__ - Tornado OpenAPI 3 request and response validation library. - - -License -####### - -The project is under the terms of BSD 3-Clause License. diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index cb6623a2..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,105 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - -import openapi_core - -# -- Project information ----------------------------------------------------- - -project = "openapi-core" -copyright = "2021, Artur Maciag" -author = "Artur Maciag" - -# The full version, including alpha/beta/rc tags -release = openapi_core.__version__ - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.coverage", - "sphinx.ext.viewcode", - "sphinx_immaterial", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_immaterial" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] - -# Set link name generated in the top bar. -html_title = "openapi-core" - -# Material theme options (see theme.conf for more information) -html_theme_options = { - "analytics": { - "provider": "google", - "property": "G-J6T05Z51NY", - }, - "repo_url": "https://github.com/python-openapi/openapi-core/", - "repo_name": "openapi-core", - "icon": { - "repo": "fontawesome/brands/github-alt", - "edit": "material/file-edit-outline", - }, - "palette": [ - { - "media": "(prefers-color-scheme: dark)", - "scheme": "slate", - "primary": "lime", - "accent": "amber", - "scheme": "slate", - "toggle": { - "icon": "material/toggle-switch", - "name": "Switch to light mode", - }, - }, - { - "media": "(prefers-color-scheme: light)", - "scheme": "default", - "primary": "lime", - "accent": "amber", - "toggle": { - "icon": "material/toggle-switch-off-outline", - "name": "Switch to dark mode", - }, - }, - ], - # If False, expand all TOC entries - "globaltoc_collapse": False, -} diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..6489c2d6 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,232 @@ +--- +hide: + - navigation +--- + +# Configuration + +OpenAPI accepts a `Config` object that allows users to customize the behavior of validation and unmarshalling processes. + +## Specification Validation + +By default, when creating an OpenAPI instance, the provided specification is also validated. + +If you know that you have a valid specification already, disabling the validator can improve performance. + +``` python hl_lines="1 4 6" +from openapi_core import Config + +config = Config( + spec_validator_cls=None, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +``` + +## Request Validator + +By default, the request validator is selected based on the detected specification version. + +To explicitly validate a: + +- OpenAPI 3.0 spec, import `V30RequestValidator` +- OpenAPI 3.1 spec, import `V31RequestValidator` or `V31WebhookRequestValidator` +- OpenAPI 3.2 spec, import `V32RequestValidator` or `V32WebhookRequestValidator` + +``` python hl_lines="1 4" +from openapi_core import V31RequestValidator + +config = Config( + request_validator_cls=V31RequestValidator, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +openapi.validate_request(request) +``` + +You can also explicitly import `V3RequestValidator`, which is a shortcut to the latest OpenAPI v3 version. + +## Response Validator + +By default, the response validator is selected based on the detected specification version. + +To explicitly validate a: + +- OpenAPI 3.0 spec, import `V30ResponseValidator` +- OpenAPI 3.1 spec, import `V31ResponseValidator` or `V31WebhookResponseValidator` +- OpenAPI 3.2 spec, import `V32ResponseValidator` or `V32WebhookResponseValidator` + +``` python hl_lines="1 4" +from openapi_core import V31ResponseValidator + +config = Config( + response_validator_cls=V31ResponseValidator, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +openapi.validate_response(request, response) +``` + +You can also explicitly import `V3ResponseValidator`, which is a shortcut to the latest OpenAPI v3 version. + +## Request Unmarshaller + +By default, the request unmarshaller is selected based on the detected specification version. + +To explicitly validate and unmarshal a request for: + +- OpenAPI 3.0 spec, import `V30RequestUnmarshaller` +- OpenAPI 3.1 spec, import `V31RequestUnmarshaller` or `V31WebhookRequestUnmarshaller` +- OpenAPI 3.2 spec, import `V32RequestUnmarshaller` or `V32WebhookRequestUnmarshaller` + +``` python hl_lines="1 4" +from openapi_core import V31RequestUnmarshaller + +config = Config( + request_unmarshaller_cls=V31RequestUnmarshaller, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +result = openapi.unmarshal_request(request) +``` + +You can also explicitly import `V3RequestUnmarshaller`, which is a shortcut to the latest OpenAPI v3 version. + +## Response Unmarshaller + +To explicitly validate and unmarshal a response: + +- For OpenAPI 3.0 spec, import `V30ResponseUnmarshaller` +- For OpenAPI 3.1 spec, import `V31ResponseUnmarshaller` or `V31WebhookResponseUnmarshaller` +- For OpenAPI 3.2 spec, import `V32ResponseUnmarshaller` or `V32WebhookResponseUnmarshaller` + +``` python hl_lines="1 4" +from openapi_core import V31ResponseUnmarshaller + +config = Config( + response_unmarshaller_cls=V31ResponseUnmarshaller, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +result = openapi.unmarshal_response(request, response) +``` + +You can also explicitly import `V3ResponseUnmarshaller`, which is a shortcut to the latest OpenAPI v3 version. + +## Extra Media Type Deserializers + +The library comes with a set of built-in media type deserializers for formats such as `application/json`, `application/xml`, `application/x-www-form-urlencoded`, and `multipart/form-data`. + +You can also define your own deserializers. To do this, pass a dictionary of custom media type deserializers with the supported MIME types as keys to the `unmarshal_response` function: + +```python hl_lines="11" +def protobuf_deserializer(message): + feature = route_guide_pb2.Feature() + feature.ParseFromString(message) + return feature + +extra_media_type_deserializers = { + 'application/protobuf': protobuf_deserializer, +} + +config = Config( + extra_media_type_deserializers=extra_media_type_deserializers, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) + +result = openapi.unmarshal_response(request, response) +``` + +## Strict Additional Properties + +By default, OpenAPI follows JSON Schema behavior: when an object schema omits `additionalProperties`, extra keys are allowed. + +If you want stricter behavior, change `additional_properties_default_policy` to `forbid`. In this mode, omitted `additionalProperties` is treated as `false`. + +This mode is particularly useful for: +- **Preventing data leaks**: Ensuring your API doesn't accidentally expose internal or sensitive fields in responses that aren't explicitly documented. +- **Strict client validation**: Rejecting client requests that contain typos, extraneous data, or unsupported fields, forcing clients to adhere exactly to the defined schema. +- **Contract tightening**: Enforcing the exact shape of objects across your API boundaries. + +``` python hl_lines="4" +from openapi_core import Config +from openapi_core import OpenAPI + +config = Config( + additional_properties_default_policy="forbid", +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +``` + +When strict mode is enabled: + +- object schema with omitted `additionalProperties` rejects unknown fields +- object schema with `additionalProperties: true` still allows unknown fields + +## Response Properties Policy + +By default, OpenAPI follows JSON Schema behavior for `required`: response object properties are optional unless explicitly listed in `required`. + +If you want stricter response checks, change `response_properties_default_policy` to `required`. In this mode, response object schemas are validated as if all documented properties were required (except properties marked as `writeOnly` in OpenAPI 3.0). + +This mode is intentionally stricter than the OpenAPI default. It is particularly useful for: +- **Contract completeness checks in tests**: Ensuring that the backend actually returns all the properties documented in the OpenAPI specification. +- **Detecting API drift**: Catching bugs where a database schema change or serializer update inadvertently drops fields from the response. +- **Preventing silent failures**: Making sure clients aren't broken by missing data that they expect to be present according to the API documentation. + +``` python hl_lines="4" +from openapi_core import Config +from openapi_core import OpenAPI + +config = Config( + response_properties_default_policy="required", +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +``` + +## Extra Format Validators + +OpenAPI defines a `format` keyword that hints at how a value should be interpreted. For example, a `string` with the format `date` should conform to the RFC 3339 date format. + +OpenAPI comes with a set of built-in format validators, but it's also possible to add custom ones. + +Here's how you can add support for a `usdate` format that handles dates in the form MM/DD/YYYY: + +``` python hl_lines="11" +import re + +def validate_usdate(value): + return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) + +extra_format_validators = { + 'usdate': validate_usdate, +} + +config = Config( + extra_format_validators=extra_format_validators, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) + +openapi.validate_response(request, response) +``` + +## Extra Format Unmarshallers + +Based on the `format` keyword, openapi-core can also unmarshal values to specific formats. + +The library comes with a set of built-in format unmarshallers, but it's also possible to add custom ones. + +Here's an example with the `usdate` format that converts a value to a date object: + +``` python hl_lines="11" +from datetime import datetime + +def unmarshal_usdate(value): + return datetime.strptime(value, "%m/%d/%Y").date() + +extra_format_unmarshallers = { + 'usdate': unmarshal_usdate, +} + +config = Config( + extra_format_unmarshallers=extra_format_unmarshallers, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) + +result = openapi.unmarshal_response(request, response) +``` diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..11062036 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,86 @@ +--- +hide: + - navigation +--- + +# Contributing + +Firstly, thank you for taking the time to contribute. + +The following section describes how you can contribute to the openapi-core project on GitHub. + +## Reporting bugs + +### Before you report + +- Check whether your issue already exists in the [Issue tracker](https://github.com/python-openapi/openapi-core/issues). +- Make sure it is not a support request or question better suited for the [Discussion board](https://github.com/python-openapi/openapi-core/discussions). + +### How to submit a report + +- Include a clear title. +- Describe your runtime environment with the exact versions you use. +- Describe the exact steps to reproduce the problem, including minimal code snippets. +- Describe the behavior you observed after following the steps, including console outputs. +- Describe the expected behavior and why, including links to documentation. + +## Code contribution + +### Prerequisites + +Install [Poetry](https://python-poetry.org) by following the [official installation instructions](https://python-poetry.org/docs/#installation). Optionally (but recommended), configure Poetry to create a virtual environment in a folder named `.venv` within the root directory of the project: + +```console +poetry config virtualenvs.in-project true +``` + +### Setup + +To create a development environment and install the runtime and development dependencies, run: + +```console +poetry install +``` + +Then enter the virtual environment created by Poetry: + +```console +poetry shell +``` + +### Static checks + +The project uses static checks with the fantastic [pre-commit](https://pre-commit.com/). Every change is checked on CI, and if it does not pass the tests, it cannot be accepted. If you want to check locally, run the following command to install pre-commit. + +To enable pre-commit checks for commit operations in git, enter: + +```console +pre-commit install +``` + +To run all checks on your staged files, enter: + +```console +pre-commit run +``` + +To run all checks on all files, enter: + +```console +pre-commit run --all-files +``` + +Pre-commit check results are also attached to your PR through integration with GitHub Actions. + +### Integration compatibility matrix + +Contrib integrations are tested in CI against framework version variants and +Python versions. The matrix source of truth is: + +- `.github/workflows/integration-tests.yml` + +When changing integration compatibility, update both: + +- dependency constraints in `pyproject.toml` +- integration variants in `.github/workflows/integration-tests.yml` +- information in `docs/integrations` and `README.md` diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index 938bd688..00000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,76 +0,0 @@ -Contributing -============ - -Firstly, thank you all for taking the time to contribute. - -The following section describes how you can contribute to the openapi-core project on GitHub. - -Reporting bugs --------------- - -Before you report -^^^^^^^^^^^^^^^^^ - -* Check whether your issue does not already exist in the `Issue tracker `__. -* Make sure it is not a support request or question better suited for `Discussion board `__. - -How to submit a report -^^^^^^^^^^^^^^^^^^^^^^ - -* Include clear title. -* Describe your runtime environment with exact versions you use. -* Describe the exact steps which reproduce the problem, including minimal code snippets. -* Describe the behavior you observed after following the steps, pasting console outputs. -* Describe expected behavior to see and why, including links to documentations. - -Code contribution ------------------ - -Prerequisites -^^^^^^^^^^^^^ - -Install `Poetry `__ by following the `official installation instructions `__. Optionally (but recommended), configure Poetry to create a virtual environment in a folder named ``.venv`` within the root directory of the project: - -.. code-block:: console - - poetry config virtualenvs.in-project true - -Setup -^^^^^ - -To create a development environment and install the runtime and development dependencies, run: - -.. code-block:: console - - poetry install - -Then enter the virtual environment created by Poetry: - -.. code-block:: console - - poetry shell - -Static checks -^^^^^^^^^^^^^ - -The project uses static checks using fantastic `pre-commit `__. Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit. - -To turn on pre-commit checks for commit operations in git, enter: - -.. code-block:: console - - pre-commit install - -To run all checks on your staged files, enter: - -.. code-block:: console - - pre-commit run - -To run all checks on all files, enter: - -.. code-block:: console - - pre-commit run --all-files - -Pre-commit check results are also attached to your PR through integration with Github Action. diff --git a/docs/customizations/extra_format_unmarshallers.rst b/docs/customizations/extra_format_unmarshallers.rst deleted file mode 100644 index b4d52cca..00000000 --- a/docs/customizations/extra_format_unmarshallers.rst +++ /dev/null @@ -1,27 +0,0 @@ -Format unmarshallers -==================== - -Based on ``format`` keyword, openapi-core can also unmarshal values to specific formats. - -Openapi-core comes with a set of built-in format unmarshallers, but it's also possible to add custom ones. - -Here's an example with the ``usdate`` format that converts a value to date object: - -.. code-block:: python - :emphasize-lines: 11 - - from datetime import datetime - - def unmarshal_usdate(value): - return datetime.strptime(value, "%m/%d/%y").date - - extra_format_unmarshallers = { - 'usdate': unmarshal_usdate, - } - - config = Config( - extra_format_unmarshallers=extra_format_unmarshallers, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - result = openapi.unmarshal_response(request, response) diff --git a/docs/customizations/extra_format_validators.rst b/docs/customizations/extra_format_validators.rst deleted file mode 100644 index b984f39e..00000000 --- a/docs/customizations/extra_format_validators.rst +++ /dev/null @@ -1,27 +0,0 @@ -Format validators -================= - -OpenAPI defines a ``format`` keyword that hints at how a value should be interpreted, e.g. a ``string`` with the type ``date`` should conform to the RFC 3339 date format. - -OpenAPI comes with a set of built-in format validators, but it's also possible to add custom ones. - -Here's how you could add support for a ``usdate`` format that handles dates of the form MM/DD/YYYY: - -.. code-block:: python - :emphasize-lines: 11 - - import re - - def validate_usdate(value): - return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) - - extra_format_validators = { - 'usdate': validate_usdate, - } - - config = Config( - extra_format_validators=extra_format_validators, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - openapi.validate_response(request, response) diff --git a/docs/customizations/extra_media_type_deserializers.rst b/docs/customizations/extra_media_type_deserializers.rst deleted file mode 100644 index 02940b5f..00000000 --- a/docs/customizations/extra_media_type_deserializers.rst +++ /dev/null @@ -1,25 +0,0 @@ -Media type deserializers -======================== - -OpenAPI comes with a set of built-in media type deserializers such as: ``application/json``, ``application/xml``, ``application/x-www-form-urlencoded`` or ``multipart/form-data``. - -You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function: - -.. code-block:: python - :emphasize-lines: 11 - - def protobuf_deserializer(message): - feature = route_guide_pb2.Feature() - feature.ParseFromString(message) - return feature - - extra_media_type_deserializers = { - 'application/protobuf': protobuf_deserializer, - } - - config = Config( - extra_media_type_deserializers=extra_media_type_deserializers, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - result = openapi.unmarshal_response(request, response) diff --git a/docs/customizations/index.rst b/docs/customizations/index.rst deleted file mode 100644 index b8393abe..00000000 --- a/docs/customizations/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -Customizations -============== - -OpenAPI accepts ``Config`` object that allows users to customize the behavior validation and unmarshalling processes. - -.. toctree:: - :maxdepth: 1 - - spec_validator_cls - request_validator_cls - response_validator_cls - request_unmarshaller_cls - response_unmarshaller_cls - extra_media_type_deserializers - extra_format_validators - extra_format_unmarshallers diff --git a/docs/customizations/request_unmarshaller_cls.rst b/docs/customizations/request_unmarshaller_cls.rst deleted file mode 100644 index e09ab772..00000000 --- a/docs/customizations/request_unmarshaller_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Request unmarshaller -==================== - -By default, request unmarshaller is selected based on detected specification version. - -In order to explicitly validate and unmarshal a: - -* OpenAPI 3.0 spec, import ``V30RequestUnmarshaller`` -* OpenAPI 3.1 spec, import ``V31RequestUnmarshaller`` or ``V31WebhookRequestUnmarshaller`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31RequestUnmarshaller - - config = Config( - request_unmarshaller_cls=V31RequestUnmarshaller, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - result = openapi.unmarshal_request(request) - -You can also explicitly import ``V3RequestUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/request_validator_cls.rst b/docs/customizations/request_validator_cls.rst deleted file mode 100644 index d6dc48b9..00000000 --- a/docs/customizations/request_validator_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Request validator -================= - -By default, request validator is selected based on detected specification version. - -In order to explicitly validate a: - -* OpenAPI 3.0 spec, import ``V30RequestValidator`` -* OpenAPI 3.1 spec, import ``V31RequestValidator`` or ``V31WebhookRequestValidator`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31RequestValidator - - config = Config( - request_validator_cls=V31RequestValidator, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - openapi.validate_request(request) - -You can also explicitly import ``V3RequestValidator`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_unmarshaller_cls.rst b/docs/customizations/response_unmarshaller_cls.rst deleted file mode 100644 index 1ccf3997..00000000 --- a/docs/customizations/response_unmarshaller_cls.rst +++ /dev/null @@ -1,20 +0,0 @@ -Response unmarshaller -===================== - -In order to explicitly validate and unmarshal a: - -* OpenAPI 3.0 spec, import ``V30ResponseUnmarshaller`` -* OpenAPI 3.1 spec, import ``V31ResponseUnmarshaller`` or ``V31WebhookResponseUnmarshaller`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31ResponseUnmarshaller - - config = Config( - response_unmarshaller_cls=V31ResponseUnmarshaller, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - result = openapi.unmarshal_response(request, response) - -You can also explicitly import ``V3ResponseUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_validator_cls.rst b/docs/customizations/response_validator_cls.rst deleted file mode 100644 index e9249f48..00000000 --- a/docs/customizations/response_validator_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Response validator -================== - -By default, response validator is selected based on detected specification version. - -In order to explicitly validate a: - -* OpenAPI 3.0 spec, import ``V30ResponseValidator`` -* OpenAPI 3.1 spec, import ``V31ResponseValidator`` or ``V31WebhookResponseValidator`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31ResponseValidator - - config = Config( - response_validator_cls=V31ResponseValidator, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - openapi.validate_response(request, response) - -You can also explicitly import ``V3ResponseValidator`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/spec_validator_cls.rst b/docs/customizations/spec_validator_cls.rst deleted file mode 100644 index 0b912af7..00000000 --- a/docs/customizations/spec_validator_cls.rst +++ /dev/null @@ -1,16 +0,0 @@ -Specification validation -======================== - -By default, on OpenAPI creation time, the provided specification is also validated. - -If you know you have a valid specification already, disabling the validator can improve the performance. - -.. code-block:: python - :emphasize-lines: 1,4,6 - - from openapi_core import Config - - config = Config( - spec_validator_cls=None, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) diff --git a/docs/extensions.md b/docs/extensions.md new file mode 100644 index 00000000..f6f7886c --- /dev/null +++ b/docs/extensions.md @@ -0,0 +1,61 @@ +--- +hide: + - navigation +--- + +# Extensions + +## x-model + +By default, objects are unmarshalled to dictionaries. You can use dynamically created dataclasses by providing the `x-model` property inside the schema definition with the name of the model. + +``` yaml hl_lines="5" title="openapi.yaml" + # ... + components: + schemas: + Coordinates: + x-model: Coordinates + type: object + required: + - lat + - lon + properties: + lat: + type: number + lon: + type: number +``` + +As a result of the unmarshalling process, you will get a `Coordinates` class instance with `lat` and `lon` attributes. + +## x-model-path + +You can use your own dataclasses, pydantic models, or models generated by third-party generators (e.g., [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator)) by providing the `x-model-path` property inside the schema definition with the location of your class. + +``` yaml hl_lines="5" title="openapi.yaml" + # ... + components: + schemas: + Coordinates: + x-model-path: foo.bar.Coordinates + type: object + required: + - lat + - lon + properties: + lat: + type: number + lon: + type: number +``` + +``` python title="foo/bar.py" +from dataclasses import dataclass + +@dataclass +class Coordinates: + lat: float + lon: float +``` + +As a result of the unmarshalling process, you will get an instance of your own dataclass or model. diff --git a/docs/extensions.rst b/docs/extensions.rst deleted file mode 100644 index b93e95c9..00000000 --- a/docs/extensions.rst +++ /dev/null @@ -1,63 +0,0 @@ -Extensions -========== - -x-model -------- - -By default, objects are unmarshalled to dictionaries. You can use dynamically created dataclasses by providing ``x-model-path`` property inside schema definition with name of the model. - -.. code-block:: yaml - :emphasize-lines: 5 - - # ... - components: - schemas: - Coordinates: - x-model: Coordinates - type: object - required: - - lat - - lon - properties: - lat: - type: number - lon: - type: number - -As a result of unmarshalling process, you will get ``Coordinates`` class instance with ``lat`` and ``lon`` attributes. - - -x-model-path ------------- - -You can use your own dataclasses, pydantic models or models generated by third party generators (i.e. `datamodel-code-generator `__) by providing ``x-model-path`` property inside schema definition with location of your class. - -.. code-block:: yaml - :emphasize-lines: 5 - - ... - components: - schemas: - Coordinates: - x-model-path: foo.bar.Coordinates - type: object - required: - - lat - - lon - properties: - lat: - type: number - lon: - type: number - -.. code-block:: python - - # foo/bar.py - from dataclasses import dataclass - - @dataclass - class Coordinates: - lat: float - lon: float - -As a result of unmarshalling process, you will get instance of your own dataclasses or model. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..d1b7b11f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,80 @@ +--- +hide: + - navigation +--- + +# openapi-core + +Openapi-core is a Python library that provides client-side and server-side support +for the [OpenAPI v3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) +and [OpenAPI v3.1](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) +and [OpenAPI v3.2](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md) specifications. + +## Key features + +- [Validation](validation.md) and [Unmarshalling](unmarshalling.md) of request and response data (including webhooks) +- [Integrations](integrations/index.md) with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) +- [Configuration](configuration.md) with **media type deserializers** and **format unmarshallers** +- [Security](security.md) data providers (API keys, Cookie, Basic, and Bearer HTTP authentications) + +## Installation + +=== "Pip + PyPI (recommended)" + + ``` console + pip install openapi-core + ``` + +=== "Pip + the source" + + ``` console + pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core + ``` + +## First steps + +First, create your OpenAPI object. + +```python +from openapi_core import OpenAPI + +openapi = OpenAPI.from_file_path('openapi.json') +``` + +Now you can use it to validate and unmarshal your requests and/or responses. + +```python +# raises an error if the request is invalid +result = openapi.unmarshal_request(request) +``` + +Retrieve validated and unmarshalled request data: + +```python +# get parameters +path_params = result.parameters.path +query_params = result.parameters.query +cookies_params = result.parameters.cookies +headers_params = result.parameters.headers +# get body +body = result.body +# get security data +security = result.security +``` + +The request object should implement the OpenAPI Request protocol. Check [Integrations](integrations/index.md) to find officially supported implementations. + +For more details, read about the [Unmarshalling](unmarshalling.md) process. + +If you just want to validate your request/response data without unmarshalling, read about [Validation](validation.md) instead. + +## Related projects + +- [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator) + : A Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0, OpenAPI 3.1, and OpenAPI 3.2 specifications. The validator aims to check for full compliance with the Specification. +- [openapi-schema-validator](https://github.com/python-openapi/openapi-schema-validator) + : A Python library that validates schemas against the OpenAPI Schema Specification v3.0, v3.1, and v3.2. + +## License + +The project is under the terms of the BSD 3-Clause License. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 24eacced..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,94 +0,0 @@ -openapi-core -============ - -.. toctree:: - :hidden: - :maxdepth: 3 - - unmarshalling - validation - integrations/index - customizations/index - security - extensions - contributing - -Openapi-core is a Python library that adds client-side and server-side support -for the `OpenAPI v3.0 `__ -and `OpenAPI v3.1 `__ specification. - -Key features ------------- - -* :doc:`validation` and :doc:`unmarshalling ` of request and response data (including webhooks) -* :doc:`Integrations ` with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) -* :doc:`Customization ` with **media type deserializers** and **format unmarshallers** -* :doc:`Security ` data providers (API keys, Cookie, Basic and Bearer HTTP authentications) - -Installation ------------- - -.. md-tab-set:: - - .. md-tab-item:: Pip + PyPI (recommented) - - .. code-block:: console - - pip install openapi-core - - .. md-tab-item:: Pip + the source - - .. code-block:: console - - pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core - -First steps ------------ - -Firstly create your OpenAPI object. - -.. code-block:: python - - from openapi_core import OpenAPI - - openapi = OpenAPI.from_file_path('openapi.json') - -Now you can use it to validate and unmarshal your requests and/or responses. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(request) - -Retrieve validated and unmarshalled request data - -.. code-block:: python - - # get parameters - path_params = result.parameters.path - query_params = result.parameters.query - cookies_params = result.parameters.cookies - headers_params = result.parameters.headers - # get body - body = result.body - # get security data - security = result.security - -Request object should implement OpenAPI Request protocol. Check :doc:`integrations/index` to find oficially supported implementations. - -For more details read about :doc:`unmarshalling` process. - -If you just want to validate your request/response data without unmarshalling, read about :doc:`validation` instead. - -Related projects ----------------- - -* `openapi-spec-validator `__ - Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. -* `openapi-schema-validator `__ - Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. - -License -------- - -The project is under the terms of BSD 3-Clause License. diff --git a/docs/integrations/aiohttp.md b/docs/integrations/aiohttp.md new file mode 100644 index 00000000..932af817 --- /dev/null +++ b/docs/integrations/aiohttp.md @@ -0,0 +1,37 @@ +# aiohttp.web + +This section describes integration with [aiohttp.web](https://docs.aiohttp.org/en/stable/web.html) framework. The integration supports aiohttp version 3.8+. + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `AIOHTTPOpenAPIWebRequest` to create OpenAPI request from aiohttp.web request: + +``` python +from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest + +async def hello(request): + request_body = await request.text() + openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) + openapi.validate_request(openapi_request) + return web.Response(text="Hello, world") +``` + +### Response + +Use `AIOHTTPOpenAPIWebResponse` to create OpenAPI response from aiohttp.web response: + +``` python +from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebResponse + +async def hello(request): + request_body = await request.text() + response = web.Response(text="Hello, world") + openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) + openapi_response = AIOHTTPOpenAPIWebResponse(response) + result = openapi.unmarshal_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/aiohttp.rst b/docs/integrations/aiohttp.rst deleted file mode 100644 index 97c2cf7b..00000000 --- a/docs/integrations/aiohttp.rst +++ /dev/null @@ -1,41 +0,0 @@ -aiohttp.web -=========== - -This section describes integration with `aiohttp.web `__ framework. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``AIOHTTPOpenAPIWebRequest`` to create OpenAPI request from aiohttp.web request: - -.. code-block:: python - - from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest - - async def hello(request): - request_body = await request.text() - openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) - openapi.validate_request(openapi_request) - return web.Response(text="Hello, world") - -Response -^^^^^^^^ - -Use ``AIOHTTPOpenAPIWebResponse`` to create OpenAPI response from aiohttp.web response: - -.. code-block:: python - - from openapi_core.contrib.starlette import AIOHTTPOpenAPIWebResponse - - async def hello(request): - request_body = await request.text() - response = web.Response(text="Hello, world") - openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) - openapi_response = AIOHTTPOpenAPIWebResponse(response) - result = openapi.unmarshal_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/bottle.md b/docs/integrations/bottle.md new file mode 100644 index 00000000..9bfab6ab --- /dev/null +++ b/docs/integrations/bottle.md @@ -0,0 +1,3 @@ +# Bottle + +For more information, see the [bottle-openapi-3](https://github.com/cope-systems/bottle-openapi-3) project. diff --git a/docs/integrations/bottle.rst b/docs/integrations/bottle.rst deleted file mode 100644 index 5dd7f737..00000000 --- a/docs/integrations/bottle.rst +++ /dev/null @@ -1,4 +0,0 @@ -Bottle -====== - -See `bottle-openapi-3 `_ project. diff --git a/docs/integrations/django.md b/docs/integrations/django.md new file mode 100644 index 00000000..d730167f --- /dev/null +++ b/docs/integrations/django.md @@ -0,0 +1,131 @@ +# Django + +This section describes the integration with the [Django](https://www.djangoproject.com) web framework. +The integration supports Django version 4, 5, and 6. + +## Middleware + +Django can be integrated using [middleware](https://docs.djangoproject.com/en/5.0/topics/http/middleware/) to apply OpenAPI validation to your entire application. + +Add `DjangoOpenAPIMiddleware` to your `MIDDLEWARE` list and define `OPENAPI`. + +``` python hl_lines="5 8" title="settings.py" +from openapi_core import OpenAPI + +MIDDLEWARE = [ + # ... + 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', +] + +OPENAPI = OpenAPI.from_dict(spec_dict) +``` + +After that, all your requests and responses will be validated. + +You also have access to the unmarshalled result object with all unmarshalled request data through the `openapi` attribute of the request object. + +``` python +from django.views import View + +class MyView(View): + def get(self, request): + # Get parameters object with path, query, cookies, and headers parameters + unmarshalled_params = request.openapi.parameters + # Or specific location parameters + unmarshalled_path_params = request.openapi.parameters.path + + # Get body + unmarshalled_body = request.openapi.body + + # Get security data + unmarshalled_security = request.openapi.security +``` + +### Response validation + +You can skip the response validation process by setting `OPENAPI_RESPONSE_CLS` to `None`. + +``` python hl_lines="9" title="settings.py" +from openapi_core import OpenAPI + +MIDDLEWARE = [ + # ... + 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', +] + +OPENAPI = OpenAPI.from_dict(spec_dict) +OPENAPI_RESPONSE_CLS = None +``` + +## Decorator + +Django can be integrated using [view decorators](https://docs.djangoproject.com/en/5.1/topics/http/decorators/) to apply OpenAPI validation to your application's specific views. + +Use `DjangoOpenAPIViewDecorator` with the OpenAPI object to create the decorator. + +``` python hl_lines="1 3 6" +from openapi_core.contrib.django.decorators import DjangoOpenAPIViewDecorator + +openapi_validated = DjangoOpenAPIViewDecorator(openapi) + + +@openapi_validated +def home(): + return "Welcome home" +``` + +You can skip the response validation process by setting `response_cls` to `None`. + +``` python hl_lines="5" +from openapi_core.contrib.django.decorators import DjangoOpenAPIViewDecorator + +openapi_validated = DjangoOpenAPIViewDecorator( + openapi, + response_cls=None, +) +``` + +If you want to decorate a class-based view, you can use the `method_decorator` decorator: + +``` python hl_lines="3" +from django.utils.decorators import method_decorator + +@method_decorator(openapi_validated, name='dispatch') +class MyView(View): + + def get(self, request, *args, **kwargs): + return "Welcome home" +``` + +## Low level + +The integration defines classes useful for low-level integration. + +### Request + +Use `DjangoOpenAPIRequest` to create an OpenAPI request from a Django request: + +``` python +from openapi_core.contrib.django import DjangoOpenAPIRequest + +class MyView(View): + def get(self, request): + openapi_request = DjangoOpenAPIRequest(request) + openapi.validate_request(openapi_request) +``` + +### Response + +Use `DjangoOpenAPIResponse` to create an OpenAPI response from a Django response: + +``` python +from openapi_core.contrib.django import DjangoOpenAPIResponse + +class MyView(View): + def get(self, request): + response = JsonResponse({'hello': 'world'}) + openapi_request = DjangoOpenAPIRequest(request) + openapi_response = DjangoOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst deleted file mode 100644 index 80617bb6..00000000 --- a/docs/integrations/django.rst +++ /dev/null @@ -1,101 +0,0 @@ -Django -====== - -This section describes integration with `Django `__ web framework. -The integration supports Django from version 3.0 and above. - -Middleware ----------- - -Django can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``DjangoOpenAPIMiddleware`` to your ``MIDDLEWARE`` list and define ``OPENAPI``. - -.. code-block:: python - :emphasize-lines: 6,9 - - # settings.py - from openapi_core import OpenAPI - - MIDDLEWARE = [ - # ... - 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', - ] - - OPENAPI = OpenAPI.from_dict(spec_dict) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` attribute of request object. - -.. code-block:: python - - from django.views import View - - class MyView(View): - def get(self, request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.openapi.parameters - # or specific location parameters - unmarshalled_path_params = request.openapi.parameters.path - - # get body - unmarshalled_body = request.openapi.body - - # get security data - unmarshalled_security = request.openapi.security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``OPENAPI_RESPONSE_CLS`` to ``None`` - -.. code-block:: python - :emphasize-lines: 10 - - # settings.py - from openapi_core import OpenAPI - - MIDDLEWARE = [ - # ... - 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', - ] - - OPENAPI = OpenAPI.from_dict(spec_dict) - OPENAPI_RESPONSE_CLS = None - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``DjangoOpenAPIRequest`` to create OpenAPI request from Django request: - -.. code-block:: python - - from openapi_core.contrib.django import DjangoOpenAPIRequest - - class MyView(View): - def get(self, request): - openapi_request = DjangoOpenAPIRequest(request) - openapi.validate_request(openapi_request) - -Response -^^^^^^^^ - -Use ``DjangoOpenAPIResponse`` to create OpenAPI response from Django response: - -.. code-block:: python - - from openapi_core.contrib.django import DjangoOpenAPIResponse - - class MyView(View): - def get(self, request): - response = JsonResponse({'hello': 'world'}) - openapi_request = DjangoOpenAPIRequest(request) - openapi_response = DjangoOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/falcon.md b/docs/integrations/falcon.md new file mode 100644 index 00000000..e801d636 --- /dev/null +++ b/docs/integrations/falcon.md @@ -0,0 +1,109 @@ +# Falcon + +This section describes the integration with the [Falcon](https://falconframework.org) web framework. +The integration supports Falcon version 4. + +!!! warning + + This integration does not support multipart form body requests. + +## Middleware + +The Falcon API can be integrated using the `FalconOpenAPIMiddleware` middleware. +For explicit transport classes, use `FalconWSGIOpenAPIMiddleware` for +`falcon.App` and `FalconASGIOpenAPIMiddleware` for `falcon.asgi.App`. + +``` python hl_lines="1 3 7" +from openapi_core.contrib.falcon.middlewares import FalconWSGIOpenAPIMiddleware + +openapi_middleware = FalconWSGIOpenAPIMiddleware.from_spec(spec) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +`FalconOpenAPIMiddleware` supports both WSGI and ASGI Falcon apps. +For an explicit ASGI middleware class name, use +`FalconASGIOpenAPIMiddleware`. + +``` python hl_lines="1 3 7" +from openapi_core.contrib.falcon.middlewares import FalconASGIOpenAPIMiddleware + +openapi_middleware = FalconASGIOpenAPIMiddleware.from_spec(spec) + +app = falcon.asgi.App( + # ... + middleware=[openapi_middleware], +) +``` + +Additional customization parameters can be passed to the middleware. + +``` python hl_lines="5" +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + +openapi_middleware = FalconOpenAPIMiddleware.from_spec( + spec, + extra_format_validators=extra_format_validators, +) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +You can skip the response validation process by setting `response_cls` to `None`. + +``` python hl_lines="5" +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + +openapi_middleware = FalconOpenAPIMiddleware.from_spec( + spec, + response_cls=None, +) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +After that, you will have access to the validation result object with all validated request data from the Falcon view through the request context. + +``` python +class ThingsResource: + def on_get(self, req, resp): + # Get the parameters object with path, query, cookies, and headers parameters + validated_params = req.context.openapi.parameters + # Or specific location parameters + validated_path_params = req.context.openapi.parameters.path + + # Get the body + validated_body = req.context.openapi.body + + # Get security data + validated_security = req.context.openapi.security +``` + +## Low level + +You can use `FalconOpenAPIRequest` as a Falcon request factory: + +``` python +from openapi_core.contrib.falcon import FalconOpenAPIRequest + +openapi_request = FalconOpenAPIRequest(falcon_request) +result = openapi.unmarshal_request(openapi_request) +``` + +You can use `FalconOpenAPIResponse` as a Falcon response factory: + +``` python +from openapi_core.contrib.falcon import FalconOpenAPIResponse + +openapi_response = FalconOpenAPIResponse(falcon_response) +result = openapi.unmarshal_response(openapi_request, openapi_response) +``` diff --git a/docs/integrations/falcon.rst b/docs/integrations/falcon.rst deleted file mode 100644 index 78f95c0e..00000000 --- a/docs/integrations/falcon.rst +++ /dev/null @@ -1,94 +0,0 @@ -Falcon -====== - -This section describes integration with `Falcon `__ web framework. -The integration supports Falcon from version 3.0 and above. - -Middleware ----------- - -The Falcon API can be integrated by ``FalconOpenAPIMiddleware`` middleware. - -.. code-block:: python - :emphasize-lines: 1,3,7 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -Additional customization parameters can be passed to the middleware. - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec( - spec, - extra_format_validators=extra_format_validators, - ) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec( - spec, - response_cls=None, - ) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -After that you will have access to validation result object with all validated request data from Falcon view through request context. - -.. code-block:: python - - class ThingsResource: - def on_get(self, req, resp): - # get parameters object with path, query, cookies and headers parameters - validated_params = req.context.openapi.parameters - # or specific location parameters - validated_path_params = req.context.openapi.parameters.path - - # get body - validated_body = req.context.openapi.body - - # get security data - validated_security = req.context.openapi.security - -Low level ---------- - -You can use ``FalconOpenAPIRequest`` as a Falcon request factory: - -.. code-block:: python - - from openapi_core.contrib.falcon import FalconOpenAPIRequest - - openapi_request = FalconOpenAPIRequest(falcon_request) - result = openapi.unmarshal_request(openapi_request) - -You can use ``FalconOpenAPIResponse`` as a Falcon response factory: - -.. code-block:: python - - from openapi_core.contrib.falcon import FalconOpenAPIResponse - - openapi_response = FalconOpenAPIResponse(falcon_response) - result = openapi.unmarshal_response(openapi_request, openapi_response) diff --git a/docs/integrations/fastapi.md b/docs/integrations/fastapi.md new file mode 100644 index 00000000..d0a6b0a0 --- /dev/null +++ b/docs/integrations/fastapi.md @@ -0,0 +1,56 @@ +# FastAPI + +This section describes integration with [FastAPI](https://fastapi.tiangolo.com) ASGI framework. The integration supports FastAPI versions 0.11x, 0.12x and 0.13x. + +!!! note + + FastAPI also provides OpenAPI support. The main difference is that, unlike FastAPI's code-first approach, OpenAPI-core allows you to leverage your existing specification that aligns with the API-First approach. You can read more about API-first vs. code-first in the [Guide to API-first](https://www.postman.com/api-first/). + +## Middleware + +FastAPI can be integrated by [middleware](https://fastapi.tiangolo.com/tutorial/middleware/) to apply OpenAPI validation to your entire application. + +Add `FastAPIOpenAPIMiddleware` with the OpenAPI object to your `middleware` list. + +``` python hl_lines="2 5" +from fastapi import FastAPI +from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware + +app = FastAPI() +app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi) +``` + +After that, all your requests and responses will be validated. + +You also have access to the unmarshal result object with all unmarshalled request data through the `openapi` scope of the request object. + +``` python +async def homepage(request): + # get parameters object with path, query, cookies and headers parameters + unmarshalled_params = request.scope["openapi"].parameters + # or specific location parameters + unmarshalled_path_params = request.scope["openapi"].parameters.path + + # get body + unmarshalled_body = request.scope["openapi"].body + + # get security data + unmarshalled_security = request.scope["openapi"].security +``` + +### Response validation + +You can skip the response validation process by setting `response_cls` to `None` + +``` python hl_lines="5" +app = FastAPI() +app.add_middleware( + FastAPIOpenAPIMiddleware, + openapi=openapi, + response_cls=None, +) +``` + +## Low level + +For low-level integration, see [Starlette](starlette.md) integration. diff --git a/docs/integrations/fastapi.rst b/docs/integrations/fastapi.rst deleted file mode 100644 index 830ce643..00000000 --- a/docs/integrations/fastapi.rst +++ /dev/null @@ -1,62 +0,0 @@ -FastAPI -======= - -This section describes integration with `FastAPI `__ ASGI framework. - -.. note:: - - FastAPI also provides OpenAPI support. The main difference is that, unlike FastAPI's code-first approach, OpenAPI-core allows you to laverage your existing specification that alligns with API-First approach. You can read more about API-first vs. code-first in the `Guide to API-first `__. - -Middleware ----------- - -FastAPI can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``FastAPIOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. - -.. code-block:: python - :emphasize-lines: 2,5 - - from fastapi import FastAPI - from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware - - app = FastAPI() - app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. - -.. code-block:: python - - async def homepage(request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.scope["openapi"].parameters - # or specific location parameters - unmarshalled_path_params = request.scope["openapi"].parameters.path - - # get body - unmarshalled_body = request.scope["openapi"].body - - # get security data - unmarshalled_security = request.scope["openapi"].security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - app = FastAPI() - app.add_middleware( - FastAPIOpenAPIMiddleware, - openapi=openapi, - response_cls=None, - ) - -Low level ---------- - -For low level integration see `Starlette `_ integration. diff --git a/docs/integrations/flask.md b/docs/integrations/flask.md new file mode 100644 index 00000000..45925dfa --- /dev/null +++ b/docs/integrations/flask.md @@ -0,0 +1,107 @@ +# Flask + +This section describes integration with the [Flask](https://flask.palletsprojects.com) web framework. The integration supports Flask versions 2 and 3. + +## View decorator + +Flask can be integrated using a [view decorator](https://flask.palletsprojects.com/en/latest/patterns/viewdecorators/) to apply OpenAPI validation to your application's specific views. + +Use `FlaskOpenAPIViewDecorator` with the OpenAPI object to create the decorator. + +``` python hl_lines="1 3 6" +from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator + +openapi_validated = FlaskOpenAPIViewDecorator(openapi) + +@app.route('/home') +@openapi_validated +def home(): + return "Welcome home" +``` + +You can skip the response validation process by setting `response_cls` to `None`. + +``` python hl_lines="5" +from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator + +openapi_validated = FlaskOpenAPIViewDecorator( + openapi, + response_cls=None, +) +``` + +If you want to decorate a class-based view, you can use the `decorators` attribute: + +``` python hl_lines="2" +class MyView(View): + decorators = [openapi_validated] + + def dispatch_request(self): + return "Welcome home" + +app.add_url_rule('/home', view_func=MyView.as_view('home')) +``` + +## View + +As an alternative to the decorator-based integration, Flask method-based views can be integrated by inheriting from the `FlaskOpenAPIView` class. + +``` python hl_lines="1 3 8" +from openapi_core.contrib.flask.views import FlaskOpenAPIView + +class MyView(FlaskOpenAPIView): + def get(self): + return "Welcome home" + +app.add_url_rule( + '/home', + view_func=MyView.as_view('home', spec), +) +``` + +Additional customization parameters can be passed to the view. + +``` python hl_lines="10" +from openapi_core.contrib.flask.views import FlaskOpenAPIView + +class MyView(FlaskOpenAPIView): + def get(self): + return "Welcome home" + +app.add_url_rule( + '/home', + view_func=MyView.as_view( + 'home', spec, + extra_format_validators=extra_format_validators, + ), +) +``` + +## Request parameters + +In Flask, all unmarshalled request data are provided as the Flask request object's `openapi.parameters` attribute. + +``` python hl_lines="6 7" +from flask.globals import request + +@app.route('/browse//') +@openapi +def browse(id): + browse_id = request.openapi.parameters.path['id'] + page = request.openapi.parameters.query.get('page', 1) + + return f"Browse {browse_id}, page {page}" +``` + +## Low level + +You can use `FlaskOpenAPIRequest` as a Flask request factory: + +```python +from openapi_core.contrib.flask import FlaskOpenAPIRequest + +openapi_request = FlaskOpenAPIRequest(flask_request) +result = openapi.unmarshal_request(openapi_request) +``` + +For the response factory, see the [Werkzeug](werkzeug.md) integration. diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst deleted file mode 100644 index 91e5c6d7..00000000 --- a/docs/integrations/flask.rst +++ /dev/null @@ -1,118 +0,0 @@ -Flask -====== - -This section describes integration with `Flask `__ web framework. - -View decorator --------------- - -Flask can be integrated by `view decorator `__ to apply OpenAPI validation to your application's specific views. - -Use ``FlaskOpenAPIViewDecorator`` with OpenAPI object to create the decorator. - -.. code-block:: python - :emphasize-lines: 1,3,6 - - from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator - - openapi_validated = FlaskOpenAPIViewDecorator(openapi) - - @app.route('/home') - @openapi_validated - def home(): - return "Welcome home" - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator - - openapi_validated = FlaskOpenAPIViewDecorator( - openapi, - response_cls=None, - ) - -If you want to decorate class based view you can use the decorators attribute: - -.. code-block:: python - :emphasize-lines: 2 - - class MyView(View): - decorators = [openapi_validated] - - def dispatch_request(self): - return "Welcome home" - - app.add_url_rule('/home', view_func=MyView.as_view('home')) - -View ----- - -As an alternative to the decorator-based integration, a Flask method based views can be integrated by inheritance from ``FlaskOpenAPIView`` class. - -.. code-block:: python - :emphasize-lines: 1,3,8 - - from openapi_core.contrib.flask.views import FlaskOpenAPIView - - class MyView(FlaskOpenAPIView): - def get(self): - return "Welcome home" - - app.add_url_rule( - '/home', - view_func=MyView.as_view('home', spec), - ) - -Additional customization parameters can be passed to the view. - -.. code-block:: python - :emphasize-lines: 10 - - from openapi_core.contrib.flask.views import FlaskOpenAPIView - - class MyView(FlaskOpenAPIView): - def get(self): - return "Welcome home" - - app.add_url_rule( - '/home', - view_func=MyView.as_view( - 'home', spec, - extra_format_validators=extra_format_validators, - ), - ) - -Request parameters ------------------- - -In Flask, all unmarshalled request data are provided as Flask request object's ``openapi.parameters`` attribute - -.. code-block:: python - :emphasize-lines: 6,7 - - from flask.globals import request - - @app.route('/browse//') - @openapi - def browse(id): - browse_id = request.openapi.parameters.path['id'] - page = request.openapi.parameters.query.get('page', 1) - - return f"Browse {browse_id}, page {page}" - -Low level ---------- - -You can use ``FlaskOpenAPIRequest`` as a Flask request factory: - -.. code-block:: python - - from openapi_core.contrib.flask import FlaskOpenAPIRequest - - openapi_request = FlaskOpenAPIRequest(flask_request) - result = openapi.unmarshal_request(openapi_request) - -For response factory see `Werkzeug `_ integration. diff --git a/docs/integrations/index.md b/docs/integrations/index.md new file mode 100644 index 00000000..e54bcfeb --- /dev/null +++ b/docs/integrations/index.md @@ -0,0 +1,3 @@ +# Integrations + +Openapi-core integrates with popular libraries and frameworks. Each integration offers different levels of support to help validate and unmarshal your request and response data. diff --git a/docs/integrations/index.rst b/docs/integrations/index.rst deleted file mode 100644 index f48c8cc9..00000000 --- a/docs/integrations/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -Integrations -============ - -Openapi-core integrates with your popular libraries and frameworks. Each integration offers different levels of integration that help validate and unmarshal your request and response data. - -.. toctree:: - :maxdepth: 1 - - aiohttp - bottle - django - falcon - fastapi - flask - pyramid - requests - starlette - tornado - werkzeug diff --git a/docs/integrations/pyramid.md b/docs/integrations/pyramid.md new file mode 100644 index 00000000..06501f92 --- /dev/null +++ b/docs/integrations/pyramid.md @@ -0,0 +1,3 @@ +# Pyramid + +For more information, see the [pyramid_openapi3](https://github.com/niteoweb/pyramid_openapi3) project. diff --git a/docs/integrations/pyramid.rst b/docs/integrations/pyramid.rst deleted file mode 100644 index 6989c5ce..00000000 --- a/docs/integrations/pyramid.rst +++ /dev/null @@ -1,4 +0,0 @@ -Pyramid -======= - -See `pyramid_openapi3 `_ project. diff --git a/docs/integrations/requests.md b/docs/integrations/requests.md new file mode 100644 index 00000000..2d229740 --- /dev/null +++ b/docs/integrations/requests.md @@ -0,0 +1,50 @@ +# Requests + +This section describes the integration with the [Requests](https://requests.readthedocs.io) library. + +## Low level + +The integration defines classes useful for low-level integration. + +### Request + +Use `RequestsOpenAPIRequest` to create an OpenAPI request from a Requests request: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIRequest + +request = Request('POST', url, data=data, headers=headers) +openapi_request = RequestsOpenAPIRequest(request) +openapi.validate_request(openapi_request) +``` + +### Webhook request + +Use `RequestsOpenAPIWebhookRequest` to create an OpenAPI webhook request from a Requests request: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest + +request = Request('POST', url, data=data, headers=headers) +openapi_webhook_request = RequestsOpenAPIWebhookRequest(request, "my_webhook") +openapi.validate_request(openapi_webhook_request) +``` + +### Response + +Use `RequestsOpenAPIResponse` to create an OpenAPI response from a Requests response: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIResponse + +session = Session() +request = Request('POST', url, data=data, headers=headers) +prepped = session.prepare_request(request) +response = session.send(prepped) +openapi_request = RequestsOpenAPIRequest(request) +openapi_response = RequestsOpenAPIResponse(response) +openapi.validate_response(openapi_request, openapi_response) +``` diff --git a/docs/integrations/requests.rst b/docs/integrations/requests.rst deleted file mode 100644 index bcd25b8f..00000000 --- a/docs/integrations/requests.rst +++ /dev/null @@ -1,52 +0,0 @@ -Requests -======== - -This section describes integration with `Requests `__ library. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``RequestsOpenAPIRequest`` to create OpenAPI request from Requests request: - -.. code-block:: python - - from openapi_core.contrib.requests import RequestsOpenAPIRequest - - request = Request('POST', url, data=data, headers=headers) - openapi_request = RequestsOpenAPIRequest(request) - openapi.validate_request(openapi_request) - -Webhook request -^^^^^^^^^^^^^^^ - -Use ``RequestsOpenAPIWebhookRequest`` to create OpenAPI webhook request from Requests request: - -.. code-block:: python - - from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest - - request = Request('POST', url, data=data, headers=headers) - openapi_webhook_request = RequestsOpenAPIWebhookRequest(request, "my_webhook") - openapi.validate_request(openapi_webhook_request) - -Response -^^^^^^^^ - -Use ``RequestsOpenAPIResponse`` to create OpenAPI response from Requests response: - -.. code-block:: python - - from openapi_core.contrib.requests import RequestsOpenAPIResponse - - session = Session() - request = Request('POST', url, data=data, headers=headers) - prepped = session.prepare_request(req) - response = session,send(prepped) - openapi_request = RequestsOpenAPIRequest(request) - openapi_response = RequestsOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) diff --git a/docs/integrations/starlette.md b/docs/integrations/starlette.md new file mode 100644 index 00000000..aed9d4b1 --- /dev/null +++ b/docs/integrations/starlette.md @@ -0,0 +1,89 @@ +# Starlette + +This section describes integration with the [Starlette](https://www.starlette.io) ASGI framework. The integration supports Starlette versions 0.4x, 0.5x, and 1.x. + +## Middleware + +Starlette can be integrated using [middleware](https://www.starlette.io/middleware/) to apply OpenAPI validation to your entire application. + +Add `StarletteOpenAPIMiddleware` with the OpenAPI object to your `middleware` list. + +``` python hl_lines="1 6" +from openapi_core.contrib.starlette.middlewares import StarletteOpenAPIMiddleware +from starlette.applications import Starlette +from starlette.middleware import Middleware + +middleware = [ + Middleware(StarletteOpenAPIMiddleware, openapi=openapi), +] + +app = Starlette( + # ... + middleware=middleware, +) +``` + +After that, all your requests and responses will be validated. + +You also have access to the unmarshalled result object with all unmarshalled request data through the `openapi` scope of the request object. + +``` python +async def homepage(request): + # get parameters object with path, query, cookies, and headers parameters + unmarshalled_params = request.scope["openapi"].parameters + # or specific location parameters + unmarshalled_path_params = request.scope["openapi"].parameters.path + + # get body + unmarshalled_body = request.scope["openapi"].body + + # get security data + unmarshalled_security = request.scope["openapi"].security +``` + +### Response validation + +You can skip the response validation process by setting `response_cls` to `None`. + +``` python hl_lines="2" +middleware = [ + Middleware(StarletteOpenAPIMiddleware, openapi=openapi, response_cls=None), +] + +app = Starlette( + # ... + middleware=middleware, +) +``` + +## Low level + +The integration defines classes useful for low-level integration. + +### Request + +Use `StarletteOpenAPIRequest` to create an OpenAPI request from a Starlette request: + +``` python +from openapi_core.contrib.starlette import StarletteOpenAPIRequest + +async def homepage(request): + openapi_request = StarletteOpenAPIRequest(request) + result = openapi.unmarshal_request(openapi_request) + return JSONResponse({'hello': 'world'}) +``` + +### Response + +Use `StarletteOpenAPIResponse` to create an OpenAPI response from a Starlette response: + +``` python +from openapi_core.contrib.starlette import StarletteOpenAPIResponse + +async def homepage(request): + response = JSONResponse({'hello': 'world'}) + openapi_request = StarletteOpenAPIRequest(request) + openapi_response = StarletteOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/starlette.rst b/docs/integrations/starlette.rst deleted file mode 100644 index 42911879..00000000 --- a/docs/integrations/starlette.rst +++ /dev/null @@ -1,97 +0,0 @@ -Starlette -========= - -This section describes integration with `Starlette `__ ASGI framework. - -Middleware ----------- - -Starlette can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``StarletteOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. - -.. code-block:: python - :emphasize-lines: 1,6 - - from openapi_core.contrib.starlette.middlewares import StarletteOpenAPIMiddleware - from starlette.applications import Starlette - from starlette.middleware import Middleware - - middleware = [ - Middleware(StarletteOpenAPIMiddleware, openapi=openapi), - ] - - app = Starlette( - # ... - middleware=middleware, - ) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. - -.. code-block:: python - - async def homepage(request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.scope["openapi"].parameters - # or specific location parameters - unmarshalled_path_params = request.scope["openapi"].parameters.path - - # get body - unmarshalled_body = request.scope["openapi"].body - - # get security data - unmarshalled_security = request.scope["openapi"].security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 2 - - middleware = [ - Middleware(StarletteOpenAPIMiddleware, openapi=openapi, response_cls=None), - ] - - app = Starlette( - # ... - middleware=middleware, - ) - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``StarletteOpenAPIRequest`` to create OpenAPI request from Starlette request: - -.. code-block:: python - - from openapi_core.contrib.starlette import StarletteOpenAPIRequest - - async def homepage(request): - openapi_request = StarletteOpenAPIRequest(request) - result = openapi.unmarshal_request(openapi_request) - return JSONResponse({'hello': 'world'}) - -Response -^^^^^^^^ - -Use ``StarletteOpenAPIResponse`` to create OpenAPI response from Starlette response: - -.. code-block:: python - - from openapi_core.contrib.starlette import StarletteOpenAPIResponse - - async def homepage(request): - response = JSONResponse({'hello': 'world'}) - openapi_request = StarletteOpenAPIRequest(request) - openapi_response = StarletteOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/tornado.md b/docs/integrations/tornado.md new file mode 100644 index 00000000..cecbbf2d --- /dev/null +++ b/docs/integrations/tornado.md @@ -0,0 +1,3 @@ +# Tornado + +For more information, see the [tornado-openapi3](https://github.com/correl/tornado-openapi3) project. diff --git a/docs/integrations/tornado.rst b/docs/integrations/tornado.rst deleted file mode 100644 index 59ace988..00000000 --- a/docs/integrations/tornado.rst +++ /dev/null @@ -1,4 +0,0 @@ -Tornado -======= - -See `tornado-openapi3 `_ project. diff --git a/docs/integrations/werkzeug.md b/docs/integrations/werkzeug.md new file mode 100644 index 00000000..ca49bc05 --- /dev/null +++ b/docs/integrations/werkzeug.md @@ -0,0 +1,38 @@ +# Werkzeug + +This section describes the integration with [Werkzeug](https://werkzeug.palletsprojects.com), a WSGI web application library. + +## Low level + +The integration defines classes useful for low-level integration. + +### Request + +Use `WerkzeugOpenAPIRequest` to create an OpenAPI request from a Werkzeug request: + +``` python +from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest + +def application(environ, start_response): + request = Request(environ) + openapi_request = WerkzeugOpenAPIRequest(request) + openapi.validate_request(openapi_request) + response = Response("Hello world", mimetype='text/plain') + return response(environ, start_response) +``` + +### Response + +Use `WerkzeugOpenAPIResponse` to create an OpenAPI response from a Werkzeug response: + +``` python +from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse + +def application(environ, start_response): + request = Request(environ) + response = Response("Hello world", mimetype='text/plain') + openapi_request = WerkzeugOpenAPIRequest(request) + openapi_response = WerkzeugOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response(environ, start_response) +``` diff --git a/docs/integrations/werkzeug.rst b/docs/integrations/werkzeug.rst deleted file mode 100644 index 5061d9a6..00000000 --- a/docs/integrations/werkzeug.rst +++ /dev/null @@ -1,42 +0,0 @@ -Werkzeug -======== - -This section describes integration with `Werkzeug `__ a WSGI web application library. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``WerkzeugOpenAPIRequest`` to create OpenAPI request from Werkzeug request: - -.. code-block:: python - - from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest - - def application(environ, start_response): - request = Request(environ) - openapi_request = WerkzeugOpenAPIRequest(request) - openapi.validate_request(openapi_request) - response = Response("Hello world", mimetype='text/plain') - return response(environ, start_response) - -Response -^^^^^^^^ - -Use ``WerkzeugOpenAPIResponse`` to create OpenAPI response from Werkzeug response: - -.. code-block:: python - - from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse - - def application(environ, start_response): - request = Request(environ) - response = Response("Hello world", mimetype='text/plain') - openapi_request = WerkzeugOpenAPIRequest(request) - openapi_response = WerkzeugOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response(environ, start_response) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2119f510..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/reference/configurations.md b/docs/reference/configurations.md new file mode 100644 index 00000000..91d2e908 --- /dev/null +++ b/docs/reference/configurations.md @@ -0,0 +1,3 @@ +# `Config` class + +::: openapi_core.Config diff --git a/docs/reference/datatypes.md b/docs/reference/datatypes.md new file mode 100644 index 00000000..1ab3f8b5 --- /dev/null +++ b/docs/reference/datatypes.md @@ -0,0 +1,5 @@ +# Datatypes + +::: openapi_core.unmarshalling.request.datatypes.RequestUnmarshalResult + +::: openapi_core.unmarshalling.response.datatypes.ResponseUnmarshalResult diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 00000000..d3c81f27 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,3 @@ +# Reference + +Documentation with information on functions, classes, methods, and all other parts of the OpenAPI-core public API. diff --git a/docs/reference/openapi.md b/docs/reference/openapi.md new file mode 100644 index 00000000..6fa1e7d5 --- /dev/null +++ b/docs/reference/openapi.md @@ -0,0 +1,14 @@ +# `OpenAPI` class + +::: openapi_core.OpenAPI + options: + members: + - __init__ + - from_dict + - from_path + - from_file_path + - from_file + - unmarshal_request + - unmarshal_response + - validate_request + - validate_response diff --git a/docs/reference/protocols.md b/docs/reference/protocols.md new file mode 100644 index 00000000..849ec67d --- /dev/null +++ b/docs/reference/protocols.md @@ -0,0 +1,3 @@ +# `Request`, `WebhookRequest` and `Response` protocols + +::: openapi_core.protocols diff --git a/docs/reference/types.md b/docs/reference/types.md new file mode 100644 index 00000000..d5b2a85c --- /dev/null +++ b/docs/reference/types.md @@ -0,0 +1,3 @@ +# Types + +::: openapi_core.types diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 82133027..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinx -sphinx_rtd_theme diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 00000000..f9315c3a --- /dev/null +++ b/docs/security.md @@ -0,0 +1,40 @@ +--- +hide: + - navigation +--- + +# Security + +Openapi-core provides easy access to security data for authentication and authorization processes. + +Supported security schemes: + +- http – for Basic and Bearer HTTP authentication schemes +- apiKey – for API keys and cookie authentication + +Here's an example with `BasicAuth` and `ApiKeyAuth` security schemes: + +```yaml +security: + - BasicAuth: [] + - ApiKeyAuth: [] +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key +``` + +Security scheme data is accessible from the `security` attribute of the `RequestUnmarshalResult` object. + +```python +# Get basic auth decoded credentials +result.security['BasicAuth'] + +# Get API key +result.security['ApiKeyAuth'] +``` diff --git a/docs/security.rst b/docs/security.rst deleted file mode 100644 index fc0e9a90..00000000 --- a/docs/security.rst +++ /dev/null @@ -1,36 +0,0 @@ -Security -======== - -Openapi-core provides you easy access to security data for authentication and authorization process. - -Supported security schemas: - -* http – for Basic and Bearer HTTP authentications schemes -* apiKey – for API keys and cookie authentication - -Here's an example with scheme ``BasicAuth`` and ``ApiKeyAuth`` security schemes: - -.. code-block:: yaml - - security: - - BasicAuth: [] - - ApiKeyAuth: [] - components: - securitySchemes: - BasicAuth: - type: http - scheme: basic - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - -Security schemes data are accessible from `security` attribute of `RequestUnmarshalResult` object. - -.. code-block:: python - - # get basic auth decoded credentials - result.security['BasicAuth'] - - # get api key - result.security['ApiKeyAuth'] diff --git a/docs/unmarshalling.md b/docs/unmarshalling.md new file mode 100644 index 00000000..724ba925 --- /dev/null +++ b/docs/unmarshalling.md @@ -0,0 +1,97 @@ +--- +hide: + - navigation +--- + +# Unmarshalling + +Unmarshalling is the process of converting a primitive schema type value into a higher-level object based on a `format` keyword. All request/response data that can be described by a schema in the OpenAPI specification can be unmarshalled. + +Unmarshallers first validate data against the provided schema (See [Validation](validation.md)). + +Openapi-core comes with a set of built-in format unmarshallers: + +- `date` - converts a string into a date object, +- `date-time` - converts a string into a datetime object, +- `binary` - converts a string into a byte object, +- `uuid` - converts a string into a UUID object, +- `byte` - decodes a Base64-encoded string. + +!!! note + + For backward compatibility, OpenAPI 3.1 unmarshalling in openapi-core currently supports `byte` and `binary` format handling. + +You can also define your own format unmarshallers (See [Extra Format Unmarshallers](configuration.md#extra-format-unmarshallers)). + +## Request unmarshalling + +Use the `unmarshal_request` method to validate and unmarshal request data against a given spec. By default, the OpenAPI spec version is detected: + +```python +# raises an error if the request is invalid +result = openapi.unmarshal_request(request) +``` + +The request object should implement the OpenAPI Request protocol (See [Integrations](integrations/index.md)). + +!!! note + + The Webhooks feature is part of OpenAPI v3.1+. + +Use the same method to validate and unmarshal webhook request data against a given spec. + +```python +# raises an error if the request is invalid +result = openapi.unmarshal_request(webhook_request) +``` + +The webhook request object should implement the OpenAPI WebhookRequest protocol (See [Integrations](integrations/index.md)). + +Retrieve validated and unmarshalled request data: + +```python +# get parameters +path_params = result.parameters.path +query_params = result.parameters.query +cookies_params = result.parameters.cookies +headers_params = result.parameters.headers +# get body +body = result.body +# get security data +security = result.security +``` + +You can also define your own request unmarshaller (See [Request Unmarshaller](configuration.md#request-unmarshaller)). + +## Response unmarshalling + +Use the `unmarshal_response` method to validate and unmarshal response data against a given spec. By default, the OpenAPI spec version is detected: + +```python +# raises an error if the response is invalid +result = openapi.unmarshal_response(request, response) +``` + +The response object should implement the OpenAPI Response protocol (See [Integrations](integrations/index.md)). + +!!! note + + The Webhooks feature is part of OpenAPI v3.1+. + +Use the same method to validate and unmarshal response data from a webhook request against a given spec. + +```python +# raises an error if the request is invalid +result = openapi.unmarshal_response(webhook_request, response) +``` + +Retrieve validated and unmarshalled response data: + +```python +# get headers +headers = result.headers +# get data +data = result.data +``` + +You can also define your own response unmarshaller (See [Response Unmarshaller](configuration.md#response-unmarshaller)). diff --git a/docs/unmarshalling.rst b/docs/unmarshalling.rst deleted file mode 100644 index 82c1302b..00000000 --- a/docs/unmarshalling.rst +++ /dev/null @@ -1,91 +0,0 @@ -Unmarshalling -============= - -Unmarshalling is the process of converting a primitive schema type of value into a higher-level object based on a ``format`` keyword. All request/response data, that can be described by a schema in OpenAPI specification, can be unmarshalled. - -Unmarshallers firstly validate data against the provided schema (See :doc:`validation`). - -Openapi-core comes with a set of built-in format unmarshallers: - -* ``date`` - converts string into a date object, -* ``date-time`` - converts string into a datetime object, -* ``binary`` - converts string into a byte object, -* ``uuid`` - converts string into an UUID object, -* ``byte`` - decodes Base64-encoded string. - -You can also define your own format unmarshallers (See :doc:`customizations/extra_format_unmarshallers`). - -Request unmarshalling ---------------------- - -Use ``unmarshal_request`` method to validate and unmarshal request data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(request) - -Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate and unmarshal webhook request data against a given spec. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(webhook_request) - -Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). - -Retrieve validated and unmarshalled request data - -.. code-block:: python - - # get parameters - path_params = result.parameters.path - query_params = result.parameters.query - cookies_params = result.parameters.cookies - headers_params = result.parameters.headers - # get body - body = result.body - # get security data - security = result.security - -You can also define your own request unmarshaller (See :doc:`customizations/request_unmarshaller_cls`). - -Response unmarshalling ----------------------- - -Use ``unmarshal_response`` method to validate and unmarshal response data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if response is invalid - result = openapi.unmarshal_response(request, response) - -Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate and unmarshal response data from webhook request against a given spec. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_response(webhook_request, response) - -Retrieve validated and unmarshalled response data - -.. code-block:: python - - # get headers - headers = result.headers - # get data - data = result.data - -You can also define your own response unmarshaller (See :doc:`customizations/response_unmarshaller_cls`). diff --git a/docs/validation.md b/docs/validation.md new file mode 100644 index 00000000..dc76e830 --- /dev/null +++ b/docs/validation.md @@ -0,0 +1,118 @@ +--- +hide: + - navigation +--- + +# Validation + +Validation is a process to validate request/response data under a given schema defined in the OpenAPI specification. + +Additionally, openapi-core uses the `format` keyword to check if primitive types conform to defined formats. + +Such valid formats can be further unmarshalled (See [Unmarshalling](unmarshalling.md)). + +Depending on the OpenAPI version, openapi-core comes with a set of built-in format validators such as: `date`, `date-time`, `binary`, `uuid`, or `byte`. + +!!! note + + For backward compatibility, OpenAPI 3.1 validation in openapi-core currently accepts OpenAPI 3.0-style format checker behavior, including `byte` and `binary`. + +You can also define your own format validators (See [Extra Format Validators](configuration.md#extra-format-validators)). + +## Request validation + +Use the `validate_request` method to validate request data against a given spec. By default, the OpenAPI spec version is detected: + +```python +# raises error if request is invalid +openapi.validate_request(request) +``` + +The request object should implement the OpenAPI Request protocol (See [Integrations](integrations/index.md)). + +!!! note + + The Webhooks feature is part of OpenAPI v3.1+ + +Use the same method to validate webhook request data against a given spec. + +```python +# raises error if request is invalid +openapi.validate_request(webhook_request) +``` + +The webhook request object should implement the OpenAPI WebhookRequest protocol (See [Integrations](integrations/index.md)). + +You can also define your own request validator (See [Request Validator](configuration.md#request-validator)). + +### Iterating request errors + +If you want to collect errors instead of raising on the first one, use iterator-based APIs: + +```python +errors = list(openapi.iter_request_errors(request)) +if errors: + for error in errors: + print(type(error), str(error)) +``` + +You can also call `iter_errors` directly on a validator class: + +```python +from openapi_core import V31RequestValidator + +errors = list(V31RequestValidator(spec).iter_errors(request)) +``` + +Validation errors expose structured details directly: + +```python +for error in openapi.iter_request_errors(request): + details = getattr(error, "details", {}) + print(details.get("message")) + for schema_error in details.get("schema_errors", []): + print(schema_error["message"], schema_error["path"]) +``` + +Some high-level errors wrap detailed schema errors in `__cause__`. You can still access those low-level objects directly: + +```python +for error in openapi.iter_request_errors(request): + cause = getattr(error, "__cause__", None) + schema_errors = getattr(cause, "schema_errors", None) + if schema_errors: + for schema_error in schema_errors: + print(schema_error.message) +``` + +## Response validation + +Use the `validate_response` function to validate response data against a given spec. By default, the OpenAPI spec version is detected: + +```python +# raises error if response is invalid +openapi.validate_response(request, response) +``` + +The response object should implement the OpenAPI Response protocol (See [Integrations](integrations/index.md)). + +!!! note + + The Webhooks feature is part of OpenAPI v3.1+ + +Use the same function to validate response data from a webhook request against a given spec. + +```python +# raises error if request is invalid +openapi.validate_response(webhook_request, response) +``` + +You can also define your own response validator (See [Response Validator](configuration.md#response-validator)). + +### Iterating response errors + +Use `iter_response_errors` to collect validation errors for a response: + +```python +errors = list(openapi.iter_response_errors(request, response)) +``` diff --git a/docs/validation.rst b/docs/validation.rst deleted file mode 100644 index 0cd9ac22..00000000 --- a/docs/validation.rst +++ /dev/null @@ -1,66 +0,0 @@ -Validation -========== - -Validation is a process to validate request/response data under a given schema defined in OpenAPI specification. - -Additionally, openapi-core uses the ``format`` keyword to check if primitive types conform to defined formats. - -Such valid formats can be forther unmarshalled (See :doc:`unmarshalling`). - -Depends on the OpenAPI version, openapi-core comes with a set of built-in format validators such as: ``date``, ``date-time``, ``binary``, ``uuid`` or ``byte``. - -You can also define your own format validators (See :doc:`customizations/extra_format_validators`). - -Request validation ------------------- - -Use ``validate_request`` method to validate request data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_request(request) - -Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate webhook request data against a given spec. - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_request(webhook_request) - -Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). - -You can also define your own request validator (See :doc:`customizations/request_validator_cls`). - -Response validation -------------------- - -Use ``validate_response`` function to validate response data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - from openapi_core import validate_response - - # raises error if response is invalid - openapi.validate_response(request, response) - -Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same function to validate response data from webhook request against a given spec. - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_response(webhook_request, response) - -You can also define your own response validator (See :doc:`customizations/response_validator_cls`). diff --git a/index.rst b/index.rst deleted file mode 100644 index 366a735e..00000000 --- a/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. openapi-core documentation master file, created by - sphinx-quickstart on Thu Nov 23 10:05:33 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to openapi-core's documentation! -======================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..56ddcd8e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,109 @@ +site_name: OpenAPI-core +site_description: OpenAPI for Python +site_url: https://openapi-core.readthedocs.io/ +theme: + name: material + icon: + repo: fontawesome/brands/github-alt + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/toggle-switch + name: Switch to light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: lime + accent: amber + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: lime + accent: amber + toggle: + icon: material/toggle-switch-off + name: Switch to system preference + features: + - content.code.annotate + - content.code.copy + - content.footnote.tooltips + - content.tabs.link + - content.tooltips + - navigation.footer + - navigation.indexes + - navigation.instant + - navigation.instant.prefetch + - navigation.instant.progress + - navigation.path + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow +repo_name: python-openapi/openapi-core +repo_url: https://github.com/python-openapi/openapi-core +plugins: + - mkdocstrings: + handlers: + python: + options: + extensions: + - griffe_typingdoc + show_root_heading: true + show_if_no_docstring: true + inherited_members: true + members_order: source + unwrap_annotated: true + docstring_section_style: spacy + separate_signature: true + signature_crossrefs: true + show_category_heading: true + show_signature_annotations: true + show_symbol_type_heading: true + show_symbol_type_toc: true +nav: + - OpenAPI-core: index.md + - unmarshalling.md + - validation.md + - Integrations: + - integrations/index.md + - integrations/aiohttp.md + - integrations/bottle.md + - integrations/django.md + - integrations/falcon.md + - integrations/fastapi.md + - integrations/flask.md + - integrations/pyramid.md + - integrations/requests.md + - integrations/starlette.md + - integrations/tornado.md + - integrations/werkzeug.md + - configuration.md + - security.md + - extensions.md + - Reference: + - reference/index.md + - reference/openapi.md + - reference/configurations.md + - reference/datatypes.md + - reference/protocols.md + - reference/types.md + - contributing.md +markdown_extensions: + - admonition + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + line_spans: __span + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true +extra: + analytics: + provider: google + property: G-J6T05Z51NY diff --git a/openapi_core/__init__.py b/openapi_core/__init__.py index 7ef64786..a36753c0 100644 --- a/openapi_core/__init__.py +++ b/openapi_core/__init__.py @@ -2,6 +2,12 @@ from openapi_core.app import OpenAPI from openapi_core.configurations import Config +from openapi_core.shortcuts import iter_apicall_request_errors +from openapi_core.shortcuts import iter_apicall_response_errors +from openapi_core.shortcuts import iter_request_errors +from openapi_core.shortcuts import iter_response_errors +from openapi_core.shortcuts import iter_webhook_request_errors +from openapi_core.shortcuts import iter_webhook_response_errors from openapi_core.shortcuts import unmarshal_apicall_request from openapi_core.shortcuts import unmarshal_apicall_response from openapi_core.shortcuts import unmarshal_request @@ -14,38 +20,44 @@ from openapi_core.shortcuts import validate_response from openapi_core.shortcuts import validate_webhook_request from openapi_core.shortcuts import validate_webhook_response -from openapi_core.spec.paths import Spec from openapi_core.unmarshalling.request import V3RequestUnmarshaller from openapi_core.unmarshalling.request import V3WebhookRequestUnmarshaller from openapi_core.unmarshalling.request import V30RequestUnmarshaller from openapi_core.unmarshalling.request import V31RequestUnmarshaller from openapi_core.unmarshalling.request import V31WebhookRequestUnmarshaller +from openapi_core.unmarshalling.request import V32RequestUnmarshaller +from openapi_core.unmarshalling.request import V32WebhookRequestUnmarshaller from openapi_core.unmarshalling.response import V3ResponseUnmarshaller from openapi_core.unmarshalling.response import V3WebhookResponseUnmarshaller from openapi_core.unmarshalling.response import V30ResponseUnmarshaller from openapi_core.unmarshalling.response import V31ResponseUnmarshaller from openapi_core.unmarshalling.response import V31WebhookResponseUnmarshaller +from openapi_core.unmarshalling.response import V32ResponseUnmarshaller +from openapi_core.unmarshalling.response import V32WebhookResponseUnmarshaller from openapi_core.validation.request import V3RequestValidator from openapi_core.validation.request import V3WebhookRequestValidator from openapi_core.validation.request import V30RequestValidator from openapi_core.validation.request import V31RequestValidator from openapi_core.validation.request import V31WebhookRequestValidator +from openapi_core.validation.request import V32RequestValidator +from openapi_core.validation.request import V32WebhookRequestValidator from openapi_core.validation.response import V3ResponseValidator from openapi_core.validation.response import V3WebhookResponseValidator from openapi_core.validation.response import V30ResponseValidator from openapi_core.validation.response import V31ResponseValidator from openapi_core.validation.response import V31WebhookResponseValidator +from openapi_core.validation.response import V32ResponseValidator +from openapi_core.validation.response import V32WebhookResponseValidator __author__ = "Artur Maciag" __email__ = "maciag.artur@gmail.com" -__version__ = "0.19.2" +__version__ = "0.23.1" __url__ = "https://github.com/python-openapi/openapi-core" __license__ = "BSD 3-Clause License" __all__ = [ "OpenAPI", "Config", - "Spec", "unmarshal_request", "unmarshal_response", "unmarshal_apicall_request", @@ -58,12 +70,22 @@ "validate_webhook_response", "validate_request", "validate_response", + "iter_apicall_request_errors", + "iter_webhook_request_errors", + "iter_apicall_response_errors", + "iter_webhook_response_errors", + "iter_request_errors", + "iter_response_errors", "V30RequestUnmarshaller", "V30ResponseUnmarshaller", "V31RequestUnmarshaller", "V31ResponseUnmarshaller", "V31WebhookRequestUnmarshaller", "V31WebhookResponseUnmarshaller", + "V32RequestUnmarshaller", + "V32ResponseUnmarshaller", + "V32WebhookRequestUnmarshaller", + "V32WebhookResponseUnmarshaller", "V3RequestUnmarshaller", "V3ResponseUnmarshaller", "V3WebhookRequestUnmarshaller", @@ -74,6 +96,10 @@ "V31ResponseValidator", "V31WebhookRequestValidator", "V31WebhookResponseValidator", + "V32RequestValidator", + "V32ResponseValidator", + "V32WebhookRequestValidator", + "V32WebhookResponseValidator", "V3RequestValidator", "V3ResponseValidator", "V3WebhookRequestValidator", diff --git a/openapi_core/app.py b/openapi_core/app.py index 50c73904..0a7f7960 100644 --- a/openapi_core/app.py +++ b/openapi_core/app.py @@ -2,6 +2,8 @@ from functools import cached_property from pathlib import Path +from typing import Any +from typing import Iterator from typing import Optional from jsonschema._utils import Unset @@ -14,6 +16,8 @@ from openapi_spec_validator.versions.datatypes import SpecVersion from openapi_spec_validator.versions.exceptions import OpenAPIVersionNotFound from openapi_spec_validator.versions.shortcuts import get_spec_version +from typing_extensions import Annotated +from typing_extensions import Doc from openapi_core.configurations import Config from openapi_core.exceptions import SpecError @@ -72,12 +76,65 @@ class OpenAPI: - """OpenAPI class.""" + """`OpenAPI` application class, the main entrypoint class for OpenAPI-core. + + OpenAPI can be created in multiple ways: from existing memory data or from storage such as local disk via ``from_*()`` APIs + + Read more information, in the + [OpenAPI-core docs for First Steps](https://openapi-core.readthedocs.io/#first-steps). + + Examples: + You can import the OpenAPI class directly from openapi_core: + + Create an OpenAPI from a dictionary: + + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_dict(spec) + ``` + + Create an OpenAPI from a path object: + + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_path(path) + ``` + + Create an OpenAPI from a file path: + + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_file_path('spec.yaml') + ``` + + Create an OpenAPI from a file object: + + ```python + from openapi_core import OpenAPI + + with open('spec.yaml') as f: + app = OpenAPI.from_file(f) + ``` + + """ def __init__( self, - spec: SchemaPath, - config: Optional[Config] = None, + spec: Annotated[ + SchemaPath, + Doc(""" + OpenAPI specification schema path object. + """), + ], + config: Annotated[ + Optional[Config], + Doc(""" + Configuration object for the OpenAPI application. + """), + ] = None, ): if not isinstance(spec, SchemaPath): raise TypeError("'spec' argument is not type of SchemaPath") @@ -89,35 +146,189 @@ def __init__( @classmethod def from_dict( - cls, data: Schema, config: Optional[Config] = None + cls, + data: Annotated[ + Schema, + Doc(""" + Dictionary representing the OpenAPI specification. + """), + ], + config: Annotated[ + Optional[Config], + Doc(""" + Configuration object for the OpenAPI application. + """), + ] = None, + base_uri: Annotated[ + str, + Doc(""" + Base URI for the OpenAPI specification. + """), + ] = "", ) -> "OpenAPI": - sp = SchemaPath.from_dict(data) + """Creates an `OpenAPI` from a dictionary. + + Example: + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_dict(spec) + ``` + + Returns: + OpenAPI: An instance of the OpenAPI class. + """ + sp = SchemaPath.from_dict(data, base_uri=base_uri) return cls(sp, config=config) @classmethod def from_path( - cls, path: Path, config: Optional[Config] = None + cls, + path: Annotated[ + Path, + Doc(""" + Path object representing the OpenAPI specification file. + """), + ], + config: Annotated[ + Optional[Config], + Doc(""" + Configuration object for the OpenAPI application. + """), + ] = None, ) -> "OpenAPI": + """Creates an `OpenAPI` from a [Path object](https://docs.python.org/3/library/pathlib.html#pathlib.Path). + + Example: + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_path(path) + ``` + + Returns: + OpenAPI: An instance of the OpenAPI class. + """ sp = SchemaPath.from_path(path) return cls(sp, config=config) @classmethod def from_file_path( - cls, file_path: str, config: Optional[Config] = None + cls, + file_path: Annotated[ + str, + Doc(""" + File path string representing the OpenAPI specification file. + """), + ], + config: Annotated[ + Optional[Config], + Doc(""" + Configuration object for the OpenAPI application. + """), + ] = None, ) -> "OpenAPI": + """Creates an `OpenAPI` from a file path string. + + Example: + ```python + from openapi_core import OpenAPI + + app = OpenAPI.from_file_path('spec.yaml') + ``` + + Returns: + OpenAPI: An instance of the OpenAPI class. + """ sp = SchemaPath.from_file_path(file_path) return cls(sp, config=config) @classmethod def from_file( - cls, fileobj: SupportsRead, config: Optional[Config] = None + cls, + fileobj: Annotated[ + SupportsRead, + Doc(""" + File object representing the OpenAPI specification file. + """), + ], + config: Annotated[ + Optional[Config], + Doc(""" + Configuration object for the OpenAPI application. + """), + ] = None, + base_uri: Annotated[ + str, + Doc(""" + Base URI for the OpenAPI specification. + """), + ] = "", ) -> "OpenAPI": - sp = SchemaPath.from_file(fileobj) + """Creates an `OpenAPI` from a [file object](https://docs.python.org/3/glossary.html#term-file-object). + + Example: + ```python + from openapi_core import OpenAPI + + with open('spec.yaml') as f: + app = OpenAPI.from_file(f) + ``` + + Returns: + OpenAPI: An instance of the OpenAPI class. + """ + sp = SchemaPath.from_file(fileobj, base_uri=base_uri) return cls(sp, config=config) + @classmethod + def build( + cls, + spec: Annotated[ + SchemaPath, + Doc(""" + OpenAPI specification schema path object. + """), + ], + request_unmarshaller_cls: Annotated[ + Optional[RequestUnmarshallerType], + Doc(""" + Custom request unmarshaller class. + """), + ] = None, + response_unmarshaller_cls: Annotated[ + Optional[ResponseUnmarshallerType], + Doc(""" + Custom response unmarshaller class. + """), + ] = None, + ) -> "OpenAPI": + """Builds an `OpenAPI` from a `SchemaPath` object with optional configuration parameters. + + Example: + ```python + from openapi_core import OpenAPI + app = OpenAPI.build(spec, request_unmarshaller_cls=CustomRequestUnmarshaller) + ``` + + Returns: + OpenAPI: An instance of the OpenAPI class. + """ + config_kwargs: dict[str, Any] = {} + if request_unmarshaller_cls is not None: + config_kwargs["request_unmarshaller_cls"] = ( + request_unmarshaller_cls + ) + if response_unmarshaller_cls is not None: + config_kwargs["response_unmarshaller_cls"] = ( + response_unmarshaller_cls + ) + config = Config(**config_kwargs) + return cls(spec, config=config) + def _get_version(self) -> SpecVersion: try: - return get_spec_version(self.spec.contents()) + return get_spec_version(self.spec.read_value()) # backward compatibility except OpenAPIVersionNotFound: raise SpecError("Spec schema version not detected") @@ -132,8 +343,8 @@ def check_spec(self) -> None: try: validate( - self.spec.contents(), - base_uri=self.config.spec_base_uri, + self.spec.read_value(), + base_uri=self.config.spec_base_uri or self.spec.base_uri, cls=cls, ) except ValidatorDetectError: @@ -216,6 +427,8 @@ def request_validator(self) -> RequestValidator: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", security_provider_factory=self.config.security_provider_factory, ) @@ -234,6 +447,10 @@ def response_validator(self) -> ResponseValidator: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", + enforce_properties_required=self.config.response_properties_default_policy + == "required", ) @cached_property @@ -251,6 +468,8 @@ def webhook_request_validator(self) -> WebhookRequestValidator: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", security_provider_factory=self.config.security_provider_factory, ) @@ -269,6 +488,10 @@ def webhook_response_validator(self) -> WebhookResponseValidator: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", + enforce_properties_required=self.config.response_properties_default_policy + == "required", ) @cached_property @@ -286,6 +509,8 @@ def request_unmarshaller(self) -> RequestUnmarshaller: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", security_provider_factory=self.config.security_provider_factory, schema_unmarshallers_factory=self.config.schema_unmarshallers_factory, extra_format_unmarshallers=self.config.extra_format_unmarshallers, @@ -306,6 +531,10 @@ def response_unmarshaller(self) -> ResponseUnmarshaller: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", + enforce_properties_required=self.config.response_properties_default_policy + == "required", schema_unmarshallers_factory=self.config.schema_unmarshallers_factory, extra_format_unmarshallers=self.config.extra_format_unmarshallers, ) @@ -325,6 +554,8 @@ def webhook_request_unmarshaller(self) -> WebhookRequestUnmarshaller: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", security_provider_factory=self.config.security_provider_factory, schema_unmarshallers_factory=self.config.schema_unmarshallers_factory, extra_format_unmarshallers=self.config.extra_format_unmarshallers, @@ -345,31 +576,166 @@ def webhook_response_unmarshaller(self) -> WebhookResponseUnmarshaller: spec_validator_cls=self.config.spec_validator_cls, extra_format_validators=self.config.extra_format_validators, extra_media_type_deserializers=self.config.extra_media_type_deserializers, + forbid_unspecified_additional_properties=self.config.additional_properties_default_policy + == "forbid", + enforce_properties_required=self.config.response_properties_default_policy + == "required", schema_unmarshallers_factory=self.config.schema_unmarshallers_factory, extra_format_unmarshallers=self.config.extra_format_unmarshallers, ) - def validate_request(self, request: AnyRequest) -> None: + def validate_request( + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object to be validated. + """), + ], + ) -> None: + """Validates the given request object. + + Args: + request (AnyRequest): Request object to be validated. + + Raises: + TypeError: If the request object is not of the expected type. + SpecError: If the validator class is not found. + """ if isinstance(request, WebhookRequest): self.validate_webhook_request(request) else: self.validate_apicall_request(request) + def iter_request_errors( + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object to be validated. + """), + ], + ) -> Iterator[Exception]: + """Iterates over request validation errors. + + Args: + request (AnyRequest): Request object to be validated. + + Returns: + Iterator[Exception]: Iterator over request validation errors. + + Raises: + TypeError: If the request object is not of the expected type. + SpecError: If the validator class is not found. + """ + if isinstance(request, WebhookRequest): + return self.iter_webhook_request_errors(request) + else: + return self.iter_apicall_request_errors(request) + def validate_response( - self, request: AnyRequest, response: Response + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Response object to be validated. + """), + ], ) -> None: + """Validates the given response object associated with the request. + + Args: + request (AnyRequest): Request object associated with the response. + response (Response): Response object to be validated. + + Raises: + TypeError: If the request or response object is not of the expected type. + SpecError: If the validator class is not found. + """ if isinstance(request, WebhookRequest): self.validate_webhook_response(request, response) else: self.validate_apicall_response(request, response) - def validate_apicall_request(self, request: Request) -> None: + def iter_response_errors( + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Response object to be validated. + """), + ], + ) -> Iterator[Exception]: + """Iterates over response validation errors. + + Args: + request (AnyRequest): Request object associated with the response. + response (Response): Response object to be validated. + + Returns: + Iterator[Exception]: Iterator over response validation errors. + + Raises: + TypeError: If the request or response object is not of the expected type. + SpecError: If the validator class is not found. + """ + if isinstance(request, WebhookRequest): + return self.iter_webhook_response_errors(request, response) + else: + return self.iter_apicall_response_errors(request, response) + + def validate_apicall_request( + self, + request: Annotated[ + Request, + Doc(""" + API call request object to be validated. + """), + ], + ) -> None: if not isinstance(request, Request): raise TypeError("'request' argument is not type of Request") self.request_validator.validate(request) + def iter_apicall_request_errors( + self, + request: Annotated[ + Request, + Doc(""" + API call request object to be validated. + """), + ], + ) -> Iterator[Exception]: + if not isinstance(request, Request): + raise TypeError("'request' argument is not type of Request") + return self.request_validator.iter_errors(request) + def validate_apicall_response( - self, request: Request, response: Response + self, + request: Annotated[ + Request, + Doc(""" + API call request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + API call response object to be validated. + """), + ], ) -> None: if not isinstance(request, Request): raise TypeError("'request' argument is not type of Request") @@ -377,13 +743,67 @@ def validate_apicall_response( raise TypeError("'response' argument is not type of Response") self.response_validator.validate(request, response) - def validate_webhook_request(self, request: WebhookRequest) -> None: + def iter_apicall_response_errors( + self, + request: Annotated[ + Request, + Doc(""" + API call request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + API call response object to be validated. + """), + ], + ) -> Iterator[Exception]: + if not isinstance(request, Request): + raise TypeError("'request' argument is not type of Request") + if not isinstance(response, Response): + raise TypeError("'response' argument is not type of Response") + return self.response_validator.iter_errors(request, response) + + def validate_webhook_request( + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object to be validated. + """), + ], + ) -> None: if not isinstance(request, WebhookRequest): raise TypeError("'request' argument is not type of WebhookRequest") self.webhook_request_validator.validate(request) + def iter_webhook_request_errors( + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object to be validated. + """), + ], + ) -> Iterator[Exception]: + if not isinstance(request, WebhookRequest): + raise TypeError("'request' argument is not type of WebhookRequest") + return self.webhook_request_validator.iter_errors(request) + def validate_webhook_response( - self, request: WebhookRequest, response: Response + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Webhook response object to be validated. + """), + ], ) -> None: if not isinstance(request, WebhookRequest): raise TypeError("'request' argument is not type of WebhookRequest") @@ -391,29 +811,113 @@ def validate_webhook_response( raise TypeError("'response' argument is not type of Response") self.webhook_response_validator.validate(request, response) - def unmarshal_request(self, request: AnyRequest) -> RequestUnmarshalResult: + def iter_webhook_response_errors( + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Webhook response object to be validated. + """), + ], + ) -> Iterator[Exception]: + if not isinstance(request, WebhookRequest): + raise TypeError("'request' argument is not type of WebhookRequest") + if not isinstance(response, Response): + raise TypeError("'response' argument is not type of Response") + return self.webhook_response_validator.iter_errors(request, response) + + def unmarshal_request( + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object to be unmarshalled. + """), + ], + ) -> RequestUnmarshalResult: + """Unmarshals the given request object. + + Args: + request (AnyRequest): Request object to be unmarshalled. + + Returns: + RequestUnmarshalResult: The result of the unmarshalling process. + + Raises: + TypeError: If the request object is not of the expected type. + SpecError: If the unmarshaller class is not found. + """ if isinstance(request, WebhookRequest): return self.unmarshal_webhook_request(request) else: return self.unmarshal_apicall_request(request) def unmarshal_response( - self, request: AnyRequest, response: Response + self, + request: Annotated[ + AnyRequest, + Doc(""" + Request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Response object to be unmarshalled. + """), + ], ) -> ResponseUnmarshalResult: + """Unmarshals the given response object associated with the request. + + Args: + request (AnyRequest): Request object associated with the response. + response (Response): Response object to be unmarshalled. + + Returns: + ResponseUnmarshalResult: The result of the unmarshalling process. + + Raises: + TypeError: If the request or response object is not of the expected type. + SpecError: If the unmarshaller class is not found. + """ if isinstance(request, WebhookRequest): return self.unmarshal_webhook_response(request, response) else: return self.unmarshal_apicall_response(request, response) def unmarshal_apicall_request( - self, request: Request + self, + request: Annotated[ + Request, + Doc(""" + API call request object to be unmarshalled. + """), + ], ) -> RequestUnmarshalResult: if not isinstance(request, Request): raise TypeError("'request' argument is not type of Request") return self.request_unmarshaller.unmarshal(request) def unmarshal_apicall_response( - self, request: Request, response: Response + self, + request: Annotated[ + Request, + Doc(""" + API call request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + API call response object to be unmarshalled. + """), + ], ) -> ResponseUnmarshalResult: if not isinstance(request, Request): raise TypeError("'request' argument is not type of Request") @@ -422,14 +926,32 @@ def unmarshal_apicall_response( return self.response_unmarshaller.unmarshal(request, response) def unmarshal_webhook_request( - self, request: WebhookRequest + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object to be unmarshalled. + """), + ], ) -> RequestUnmarshalResult: if not isinstance(request, WebhookRequest): raise TypeError("'request' argument is not type of WebhookRequest") return self.webhook_request_unmarshaller.unmarshal(request) def unmarshal_webhook_response( - self, request: WebhookRequest, response: Response + self, + request: Annotated[ + WebhookRequest, + Doc(""" + Webhook request object associated with the response. + """), + ], + response: Annotated[ + Response, + Doc(""" + Webhook response object to be unmarshalled. + """), + ], ) -> ResponseUnmarshalResult: if not isinstance(request, WebhookRequest): raise TypeError("'request' argument is not type of WebhookRequest") diff --git a/openapi_core/casting/schemas/__init__.py b/openapi_core/casting/schemas/__init__.py index 18b1a9e3..1becd642 100644 --- a/openapi_core/casting/schemas/__init__.py +++ b/openapi_core/casting/schemas/__init__.py @@ -1,5 +1,6 @@ from collections import OrderedDict +from openapi_core.casting.schemas.casters import AnyCaster from openapi_core.casting.schemas.casters import ArrayCaster from openapi_core.casting.schemas.casters import BooleanCaster from openapi_core.casting.schemas.casters import IntegerCaster @@ -15,11 +16,13 @@ oas30_write_schema_validators_factory, ) from openapi_core.validation.schemas import oas31_schema_validators_factory +from openapi_core.validation.schemas import oas32_schema_validators_factory __all__ = [ "oas30_write_schema_casters_factory", "oas30_read_schema_casters_factory", "oas31_schema_casters_factory", + "oas32_schema_casters_factory", ] oas30_casters_dict = OrderedDict( @@ -41,13 +44,14 @@ oas30_types_caster = TypesCaster( oas30_casters_dict, - PrimitiveCaster, + AnyCaster, ) oas31_types_caster = TypesCaster( oas31_casters_dict, - PrimitiveCaster, + AnyCaster, multi=PrimitiveCaster, ) +oas32_types_caster = oas31_types_caster oas30_write_schema_casters_factory = SchemaCastersFactory( oas30_write_schema_validators_factory, @@ -63,3 +67,7 @@ oas31_schema_validators_factory, oas31_types_caster, ) +oas32_schema_casters_factory = SchemaCastersFactory( + oas32_schema_validators_factory, + oas32_types_caster, +) diff --git a/openapi_core/casting/schemas/casters.py b/openapi_core/casting/schemas/casters.py index 94df492b..2a0fd8e8 100644 --- a/openapi_core/casting/schemas/casters.py +++ b/openapi_core/casting/schemas/casters.py @@ -1,7 +1,6 @@ from typing import Any from typing import Generic from typing import Iterable -from typing import List from typing import Mapping from typing import Optional from typing import Type @@ -12,6 +11,8 @@ from openapi_core.casting.schemas.exceptions import CastError from openapi_core.schema.schemas import get_properties +from openapi_core.util import BOOLEAN_FALSE_VALUES +from openapi_core.util import BOOLEAN_TRUE_VALUES from openapi_core.util import forcebool from openapi_core.validation.schemas.validators import SchemaValidator @@ -28,6 +29,47 @@ def __init__( self.schema_caster = schema_caster def __call__(self, value: Any) -> Any: + self.validate(value) + + return self.cast(value) + + def validate(self, value: Any) -> None: + pass + + def cast(self, value: Any) -> Any: + return value + + +class AnyCaster(PrimitiveCaster): + def cast(self, value: Any) -> Any: + if "allOf" in self.schema: + for subschema in self.schema / "allOf": + try: + # Note: Mutates `value` iteratively. This sequentially + # resolves standard overlapping types but can cause edge cases + # if a string is casted to an int and passed to a string schema. + value = self.schema_caster.evolve(subschema).cast(value) + except (ValueError, TypeError, CastError): + pass + + if "oneOf" in self.schema: + for subschema in self.schema / "oneOf": + try: + # Note: Greedy resolution. Will return the first successful + # cast based on the order of the oneOf array. + return self.schema_caster.evolve(subschema).cast(value) + except (ValueError, TypeError, CastError): + pass + + if "anyOf" in self.schema: + for subschema in self.schema / "anyOf": + try: + # Note: Greedy resolution. Will return the first successful + # cast based on the order of the anyOf array. + return self.schema_caster.evolve(subschema).cast(value) + except (ValueError, TypeError, CastError): + pass + return value @@ -37,18 +79,9 @@ def __call__(self, value: Any) -> Any: class PrimitiveTypeCaster(Generic[PrimitiveType], PrimitiveCaster): primitive_type: Type[PrimitiveType] = NotImplemented - def __call__(self, value: Union[str, bytes]) -> Any: - self.validate(value) - + def cast(self, value: Union[str, bytes]) -> PrimitiveType: return self.primitive_type(value) # type: ignore [call-arg] - def validate(self, value: Any) -> None: - # FIXME: don't cast data from media type deserializer - # See https://github.com/python-openapi/openapi-core/issues/706 - # if not isinstance(value, (str, bytes)): - # raise ValueError("should cast only from string or bytes") - pass - class IntegerCaster(PrimitiveTypeCaster[int]): primitive_type = int @@ -61,22 +94,18 @@ class NumberCaster(PrimitiveTypeCaster[float]): class BooleanCaster(PrimitiveTypeCaster[bool]): primitive_type = bool - def __call__(self, value: Union[str, bytes]) -> Any: - self.validate(value) - - return self.primitive_type(forcebool(value)) - def validate(self, value: Any) -> None: super().validate(value) - # FIXME: don't cast data from media type deserializer - # See https://github.com/python-openapi/openapi-core/issues/706 if isinstance(value, bool): return - if value.lower() not in ["false", "true"]: + if value.lower() not in BOOLEAN_TRUE_VALUES + BOOLEAN_FALSE_VALUES: raise ValueError("not a boolean format") + def cast(self, value: Union[str, bytes]) -> bool: + return self.primitive_type(forcebool(value)) + class ArrayCaster(PrimitiveCaster): @property @@ -85,19 +114,21 @@ def items_caster(self) -> "SchemaCaster": items_schema = self.schema.get("items", SchemaPath.from_dict({})) return self.schema_caster.evolve(items_schema) - def __call__(self, value: Any) -> List[Any]: + def validate(self, value: Any) -> None: # str and bytes are not arrays according to the OpenAPI spec if isinstance(value, (str, bytes)) or not isinstance(value, Iterable): - raise CastError(value, self.schema["type"]) + raise ValueError("not an array format") - try: - return list(map(self.items_caster.cast, value)) - except (ValueError, TypeError): - raise CastError(value, self.schema["type"]) + def cast(self, value: list[Any]) -> list[Any]: + return list(map(self.items_caster.cast, value)) class ObjectCaster(PrimitiveCaster): - def __call__(self, value: Any) -> Any: + def validate(self, value: Any) -> None: + if not isinstance(value, dict): + raise ValueError("not an object format") + + def cast(self, value: dict[str, Any]) -> dict[str, Any]: return self._cast_proparties(value) def evolve(self, schema: SchemaPath) -> "ObjectCaster": @@ -109,9 +140,11 @@ def evolve(self, schema: SchemaPath) -> "ObjectCaster": self.schema_caster.evolve(schema), ) - def _cast_proparties(self, value: Any, schema_only: bool = False) -> Any: + def _cast_proparties( + self, value: dict[str, Any], schema_only: bool = False + ) -> dict[str, Any]: if not isinstance(value, dict): - raise CastError(value, self.schema["type"]) + raise ValueError("not an object format") all_of_schemas = self.schema_validator.iter_all_of_schemas(value) for all_of_schema in all_of_schemas: @@ -132,9 +165,7 @@ def _cast_proparties(self, value: Any, schema_only: bool = False) -> Any: if schema_only: return value - additional_properties = self.schema.getkey( - "additionalProperties", True - ) + additional_properties = self.schema.get("additionalProperties", True) if additional_properties is not False: # free-form object if additional_properties is True: @@ -199,11 +230,12 @@ def __init__( def cast(self, value: Any) -> Any: # skip casting for nullable in OpenAPI 3.0 - if value is None and self.schema.getkey("nullable", False): + if value is None and (self.schema / "nullable").read_bool( + default=False + ): return value - schema_type = self.schema.getkey("type") - + schema_type = (self.schema / "type").read_str(None) type_caster = self.get_type_caster(schema_type) if value is None: @@ -211,8 +243,8 @@ def cast(self, value: Any) -> Any: try: return type_caster(value) - except (ValueError, TypeError): - raise CastError(value, schema_type) + except (ValueError, TypeError) as exc: + raise CastError(value, schema_type) from exc def get_type_caster( self, diff --git a/openapi_core/casting/schemas/datatypes.py b/openapi_core/casting/schemas/datatypes.py deleted file mode 100644 index 1014bf63..00000000 --- a/openapi_core/casting/schemas/datatypes.py +++ /dev/null @@ -1,4 +0,0 @@ -from typing import Any -from typing import Callable - -CasterCallable = Callable[[Any], Any] diff --git a/openapi_core/casting/schemas/exceptions.py b/openapi_core/casting/schemas/exceptions.py index 242288d2..de0f800c 100644 --- a/openapi_core/casting/schemas/exceptions.py +++ b/openapi_core/casting/schemas/exceptions.py @@ -1,15 +1,15 @@ from dataclasses import dataclass from typing import Any -from openapi_core.exceptions import OpenAPIError +from openapi_core.deserializing.exceptions import DeserializeError @dataclass -class CastError(OpenAPIError): +class CastError(DeserializeError): """Schema cast operation error""" value: Any - type: str + type: str | None def __str__(self) -> str: return f"Failed to cast value to {self.type} type: {self.value}" diff --git a/openapi_core/casting/schemas/factories.py b/openapi_core/casting/schemas/factories.py index 39c7832b..6e95925e 100644 --- a/openapi_core/casting/schemas/factories.py +++ b/openapi_core/casting/schemas/factories.py @@ -19,11 +19,13 @@ def __init__( def create( self, + spec: SchemaPath, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, ) -> SchemaCaster: schema_validator = self.schema_validators_factory.create( + spec, schema, format_validators=format_validators, extra_format_validators=extra_format_validators, diff --git a/openapi_core/configurations.py b/openapi_core/configurations.py index 8e8625a2..f3318f8f 100644 --- a/openapi_core/configurations.py +++ b/openapi_core/configurations.py @@ -24,27 +24,23 @@ class Config(UnmarshallerConfig): """OpenAPI configuration dataclass. + Read more information, in the + [OpenAPI-core docs for Configuration](https://openapi-core.readthedocs.io/configuration/). + Attributes: - spec_validator_cls - Specifincation validator class. - spec_base_uri - Specification base uri. - request_validator_cls - Request validator class. - response_validator_cls - Response validator class. - webhook_request_validator_cls - Webhook request validator class. - webhook_response_validator_cls - Webhook response validator class. - request_unmarshaller_cls - Request unmarshaller class. - response_unmarshaller_cls - Response unmarshaller class. - webhook_request_unmarshaller_cls - Webhook request unmarshaller class. - webhook_response_unmarshaller_cls - Webhook response unmarshaller class. + spec_validator_cls: Specification validator class. + spec_base_uri: Specification base URI. Deprecated, use base_uri parameter in OpenAPI.from_dict and OpenAPI.from_file if you want to define it. + request_validator_cls: Request validator class. + response_validator_cls: Response validator class. + webhook_request_validator_cls: Webhook request validator class. + webhook_response_validator_cls: Webhook response validator class. + request_unmarshaller_cls: Request unmarshaller class. + response_unmarshaller_cls: Response unmarshaller class. + webhook_request_unmarshaller_cls: Webhook request unmarshaller class. + webhook_response_unmarshaller_cls: Webhook response unmarshaller class. + response_properties_default_policy: If true, require documented response + properties (except writeOnly properties) in response validation and + unmarshalling. """ spec_validator_cls: Union[SpecValidatorType, Unset] = _UNSET diff --git a/openapi_core/contrib/aiohttp/requests.py b/openapi_core/contrib/aiohttp/requests.py index eac7965e..27c3d327 100644 --- a/openapi_core/contrib/aiohttp/requests.py +++ b/openapi_core/contrib/aiohttp/requests.py @@ -1,7 +1,5 @@ """OpenAPI core contrib aiohttp requests module""" -from __future__ import annotations - from aiohttp import web from openapi_core.datatypes import RequestParameters diff --git a/openapi_core/contrib/django/decorators.py b/openapi_core/contrib/django/decorators.py new file mode 100644 index 00000000..f6be3cbf --- /dev/null +++ b/openapi_core/contrib/django/decorators.py @@ -0,0 +1,102 @@ +"""OpenAPI core contrib django decorators module""" + +from typing import Any +from typing import Callable +from typing import Optional +from typing import Type + +from django.conf import settings +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from jsonschema_path import SchemaPath + +from openapi_core import OpenAPI +from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler +from openapi_core.contrib.django.handlers import ( + DjangoOpenAPIValidRequestHandler, +) +from openapi_core.contrib.django.integrations import DjangoIntegration +from openapi_core.contrib.django.providers import get_default_openapi_instance +from openapi_core.contrib.django.requests import DjangoOpenAPIRequest +from openapi_core.contrib.django.responses import DjangoOpenAPIResponse + + +class DjangoOpenAPIViewDecorator(DjangoIntegration): + valid_request_handler_cls = DjangoOpenAPIValidRequestHandler + errors_handler_cls: Type[DjangoOpenAPIErrorsHandler] = ( + DjangoOpenAPIErrorsHandler + ) + + def __init__( + self, + openapi: Optional[OpenAPI] = None, + request_cls: Type[DjangoOpenAPIRequest] = DjangoOpenAPIRequest, + response_cls: Type[DjangoOpenAPIResponse] = DjangoOpenAPIResponse, + errors_handler_cls: Type[ + DjangoOpenAPIErrorsHandler + ] = DjangoOpenAPIErrorsHandler, + ): + if openapi is None: + openapi = get_default_openapi_instance() + + super().__init__(openapi) + + # If OPENAPI_RESPONSE_CLS is defined in settings.py (for custom response classes), + # set the response_cls accordingly. + if hasattr(settings, "OPENAPI_RESPONSE_CLS"): + response_cls = settings.OPENAPI_RESPONSE_CLS + + self.request_cls = request_cls + self.response_cls = response_cls + + def __call__(self, view_func: Callable[..., Any]) -> Callable[..., Any]: + """ + Thanks to this method, the class acts as a decorator. + Example usage: + + @DjangoOpenAPIViewDecorator() + def my_view(request): ... + + """ + + def _wrapped_view( + request: HttpRequest, *args: Any, **kwargs: Any + ) -> HttpResponse: + # get_response is the function that we treats + # as the "next step" in the chain (i.e., our original view). + def get_response(r: HttpRequest) -> HttpResponse: + return view_func(r, *args, **kwargs) + + # Create a handler that will validate the request. + valid_request_handler = self.valid_request_handler_cls( + request, get_response + ) + + # Validate the request (before running the view). + errors_handler = self.errors_handler_cls() + response = self.handle_request( + request, valid_request_handler, errors_handler + ) + + # Validate the response (after the view) if should_validate_response() returns True. + return self.handle_response(request, response, errors_handler) + + return _wrapped_view + + @classmethod + def from_spec( + cls, + spec: SchemaPath, + request_cls: Type[DjangoOpenAPIRequest] = DjangoOpenAPIRequest, + response_cls: Type[DjangoOpenAPIResponse] = DjangoOpenAPIResponse, + errors_handler_cls: Type[ + DjangoOpenAPIErrorsHandler + ] = DjangoOpenAPIErrorsHandler, + ) -> "DjangoOpenAPIViewDecorator": + openapi = OpenAPI(spec) + return cls( + openapi, + request_cls=request_cls, + response_cls=response_cls, + errors_handler_cls=errors_handler_cls, + ) diff --git a/openapi_core/contrib/django/middlewares.py b/openapi_core/contrib/django/middlewares.py index 35b865bd..34ffe273 100644 --- a/openapi_core/contrib/django/middlewares.py +++ b/openapi_core/contrib/django/middlewares.py @@ -1,19 +1,17 @@ """OpenAPI core contrib django middlewares module""" -import warnings from typing import Callable from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.http.request import HttpRequest from django.http.response import HttpResponse -from openapi_core import OpenAPI from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler from openapi_core.contrib.django.handlers import ( DjangoOpenAPIValidRequestHandler, ) from openapi_core.contrib.django.integrations import DjangoIntegration +from openapi_core.contrib.django.providers import get_default_openapi_instance class DjangoOpenAPIMiddleware(DjangoIntegration): @@ -26,19 +24,7 @@ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): if hasattr(settings, "OPENAPI_RESPONSE_CLS"): self.response_cls = settings.OPENAPI_RESPONSE_CLS - if not hasattr(settings, "OPENAPI"): - if not hasattr(settings, "OPENAPI_SPEC"): - raise ImproperlyConfigured( - "OPENAPI_SPEC not defined in settings" - ) - else: - warnings.warn( - "OPENAPI_SPEC is deprecated. Use OPENAPI instead.", - DeprecationWarning, - ) - openapi = OpenAPI(settings.OPENAPI_SPEC) - else: - openapi = settings.OPENAPI + openapi = get_default_openapi_instance() super().__init__(openapi) diff --git a/openapi_core/contrib/django/providers.py b/openapi_core/contrib/django/providers.py new file mode 100644 index 00000000..cb4f2a73 --- /dev/null +++ b/openapi_core/contrib/django/providers.py @@ -0,0 +1,31 @@ +"""OpenAPI core contrib django providers module""" + +import warnings +from typing import cast + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from openapi_core import OpenAPI + + +def get_default_openapi_instance() -> OpenAPI: + """ + Retrieves or initializes the OpenAPI instance based on Django settings + (either OPENAPI or OPENAPI_SPEC). + This function ensures the spec is only loaded once. + """ + if hasattr(settings, "OPENAPI"): + # Recommended (newer) approach + return cast(OpenAPI, settings.OPENAPI) + elif hasattr(settings, "OPENAPI_SPEC"): + # Backward compatibility + warnings.warn( + "OPENAPI_SPEC is deprecated. Use OPENAPI in your settings instead.", + DeprecationWarning, + ) + return OpenAPI(settings.OPENAPI_SPEC) + else: + raise ImproperlyConfigured( + "Neither OPENAPI nor OPENAPI_SPEC is defined in Django settings." + ) diff --git a/openapi_core/contrib/falcon/__init__.py b/openapi_core/contrib/falcon/__init__.py index 67c28a13..ef2a2411 100644 --- a/openapi_core/contrib/falcon/__init__.py +++ b/openapi_core/contrib/falcon/__init__.py @@ -1,7 +1,13 @@ +from openapi_core.contrib.falcon.middlewares import FalconASGIOpenAPIMiddleware +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware +from openapi_core.contrib.falcon.middlewares import FalconWSGIOpenAPIMiddleware from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse __all__ = [ + "FalconASGIOpenAPIMiddleware", + "FalconOpenAPIMiddleware", + "FalconWSGIOpenAPIMiddleware", "FalconOpenAPIRequest", "FalconOpenAPIResponse", ] diff --git a/openapi_core/contrib/falcon/integrations.py b/openapi_core/contrib/falcon/integrations.py index 8c3fa544..0b1ddc8f 100644 --- a/openapi_core/contrib/falcon/integrations.py +++ b/openapi_core/contrib/falcon/integrations.py @@ -1,8 +1,15 @@ +from typing import Optional +from typing import Type + from falcon.request import Request from falcon.response import Response +from openapi_core import OpenAPI +from openapi_core.contrib.falcon.requests import FalconAsgiOpenAPIRequest from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest +from openapi_core.contrib.falcon.responses import FalconAsgiOpenAPIResponse from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse +from openapi_core.unmarshalling.processors import AsyncUnmarshallingProcessor from openapi_core.unmarshalling.processors import UnmarshallingProcessor from openapi_core.unmarshalling.typing import ErrorsHandlerCallable @@ -32,3 +39,46 @@ def handle_response( if not self.should_validate_response(): return response return super().handle_response(request, response, errors_handler) + + +class AsyncFalconIntegration(AsyncUnmarshallingProcessor[Request, Response]): + request_cls: Type[FalconAsgiOpenAPIRequest] = FalconAsgiOpenAPIRequest + response_cls: Optional[Type[FalconAsgiOpenAPIResponse]] = ( + FalconAsgiOpenAPIResponse + ) + + def __init__( + self, + openapi: OpenAPI, + request_cls: Type[FalconAsgiOpenAPIRequest] = FalconAsgiOpenAPIRequest, + response_cls: Optional[Type[FalconAsgiOpenAPIResponse]] = ( + FalconAsgiOpenAPIResponse + ), + ): + super().__init__(openapi) + self.request_cls = request_cls or self.request_cls + self.response_cls = response_cls + + async def get_openapi_request( + self, request: Request + ) -> FalconAsgiOpenAPIRequest: + return await self.request_cls.from_request(request) + + async def get_openapi_response( + self, response: Response + ) -> FalconAsgiOpenAPIResponse: + assert self.response_cls is not None + return await self.response_cls.from_response(response) + + def should_validate_response(self) -> bool: + return self.response_cls is not None + + async def handle_response( + self, + request: Request, + response: Response, + errors_handler: ErrorsHandlerCallable[Response], + ) -> Response: + if not self.should_validate_response(): + return response + return await super().handle_response(request, response, errors_handler) diff --git a/openapi_core/contrib/falcon/middlewares.py b/openapi_core/contrib/falcon/middlewares.py index 13e4c5e8..89d9f81a 100644 --- a/openapi_core/contrib/falcon/middlewares.py +++ b/openapi_core/contrib/falcon/middlewares.py @@ -1,29 +1,39 @@ """OpenAPI core contrib falcon middlewares module""" from typing import Any +from typing import Optional from typing import Type -from typing import Union +from typing import cast from falcon.request import Request from falcon.response import Response -from jsonschema._utils import Unset -from jsonschema.validators import _UNSET from jsonschema_path import SchemaPath -from openapi_core import Config from openapi_core import OpenAPI from openapi_core.contrib.falcon.handlers import FalconOpenAPIErrorsHandler from openapi_core.contrib.falcon.handlers import ( FalconOpenAPIValidRequestHandler, ) +from openapi_core.contrib.falcon.integrations import AsyncFalconIntegration from openapi_core.contrib.falcon.integrations import FalconIntegration +from openapi_core.contrib.falcon.requests import FalconAsgiOpenAPIRequest from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest +from openapi_core.contrib.falcon.responses import FalconAsgiOpenAPIResponse from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse +from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult from openapi_core.unmarshalling.request.types import RequestUnmarshallerType from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType +_DEFAULT_ASYNC = object() + + +class FalconWSGIOpenAPIMiddleware(FalconIntegration): + """OpenAPI middleware for Falcon WSGI applications. + + This class wires Falcon's synchronous middleware hooks to the + synchronous OpenAPI integration. + """ -class FalconOpenAPIMiddleware(FalconIntegration): valid_request_handler_cls = FalconOpenAPIValidRequestHandler errors_handler_cls: Type[FalconOpenAPIErrorsHandler] = ( FalconOpenAPIErrorsHandler @@ -37,7 +47,7 @@ def __init__( errors_handler_cls: Type[ FalconOpenAPIErrorsHandler ] = FalconOpenAPIErrorsHandler, - **unmarshaller_kwargs: Any, + **kwargs: Any, ): super().__init__(openapi) self.request_cls = request_cls or self.request_cls @@ -48,32 +58,26 @@ def __init__( def from_spec( cls, spec: SchemaPath, - request_unmarshaller_cls: Union[ - RequestUnmarshallerType, Unset - ] = _UNSET, - response_unmarshaller_cls: Union[ - ResponseUnmarshallerType, Unset - ] = _UNSET, + request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None, + response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None, request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest, response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse, errors_handler_cls: Type[ FalconOpenAPIErrorsHandler ] = FalconOpenAPIErrorsHandler, - **unmarshaller_kwargs: Any, - ) -> "FalconOpenAPIMiddleware": - config = Config( + **kwargs: Any, + ) -> "FalconWSGIOpenAPIMiddleware": + openapi = OpenAPI.build( + spec, request_unmarshaller_cls=request_unmarshaller_cls, response_unmarshaller_cls=response_unmarshaller_cls, ) - openapi = OpenAPI(spec, config=config) return cls( openapi, - request_unmarshaller_cls=request_unmarshaller_cls, - response_unmarshaller_cls=response_unmarshaller_cls, request_cls=request_cls, response_cls=response_cls, errors_handler_cls=errors_handler_cls, - **unmarshaller_kwargs, + **kwargs, ) def process_request(self, req: Request, resp: Response) -> None: @@ -86,3 +90,200 @@ def process_response( ) -> None: errors_handler = self.errors_handler_cls(req, resp) self.handle_response(req, resp, errors_handler) + + +class FalconASGIOpenAPIMiddleware(AsyncFalconIntegration): + """OpenAPI middleware for Falcon ASGI applications. + + This class wires Falcon's asynchronous middleware hooks to the + asynchronous OpenAPI integration. + """ + + valid_request_handler_cls = FalconOpenAPIValidRequestHandler + errors_handler_cls: Type[FalconOpenAPIErrorsHandler] = ( + FalconOpenAPIErrorsHandler + ) + + def __init__( + self, + openapi: OpenAPI, + request_cls: Type[FalconAsgiOpenAPIRequest] = FalconAsgiOpenAPIRequest, + response_cls: Optional[Type[FalconAsgiOpenAPIResponse]] = ( + FalconAsgiOpenAPIResponse + ), + errors_handler_cls: Type[ + FalconOpenAPIErrorsHandler + ] = FalconOpenAPIErrorsHandler, + **kwargs: Any, + ): + super().__init__( + openapi, + request_cls=request_cls, + response_cls=response_cls, + ) + self.errors_handler_cls = errors_handler_cls or self.errors_handler_cls + + @classmethod + def from_spec( + cls, + spec: SchemaPath, + request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None, + response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None, + request_cls: Type[FalconAsgiOpenAPIRequest] = FalconAsgiOpenAPIRequest, + response_cls: Optional[Type[FalconAsgiOpenAPIResponse]] = ( + FalconAsgiOpenAPIResponse + ), + errors_handler_cls: Type[ + FalconOpenAPIErrorsHandler + ] = FalconOpenAPIErrorsHandler, + **kwargs: Any, + ) -> "FalconASGIOpenAPIMiddleware": + openapi = OpenAPI.build( + spec, + request_unmarshaller_cls=request_unmarshaller_cls, + response_unmarshaller_cls=response_unmarshaller_cls, + ) + return cls( + openapi, + request_cls=request_cls, + response_cls=response_cls, + errors_handler_cls=errors_handler_cls, + **kwargs, + ) + + async def process_request_async( + self, req: Request, resp: Response + ) -> None: + errors_handler = self.errors_handler_cls(req, resp) + valid_request_handler = self.valid_request_handler_cls(req, resp) + + async def async_valid_request_handler( + request_unmarshal_result: RequestUnmarshalResult, + ) -> Response: + return valid_request_handler(request_unmarshal_result) + + await self.handle_request( + req, + async_valid_request_handler, + errors_handler, + ) + + async def process_response_async( + self, + req: Request, + resp: Response, + resource: Any, + req_succeeded: bool, + ) -> None: + errors_handler = self.errors_handler_cls(req, resp) + await self.handle_response(req, resp, errors_handler) + + +class FalconOpenAPIMiddleware: + """OpenAPI middleware compatible with both WSGI and ASGI Falcon apps. + + This class delegates to transport-specific middleware implementations: + :class:`FalconWSGIOpenAPIMiddleware` for sync hooks and + :class:`FalconASGIOpenAPIMiddleware` for async hooks. + """ + + def __init__( + self, + openapi: OpenAPI, + request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest, + response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse, + request_async_cls: Any = _DEFAULT_ASYNC, + response_async_cls: Any = _DEFAULT_ASYNC, + errors_handler_cls: Type[ + FalconOpenAPIErrorsHandler + ] = FalconOpenAPIErrorsHandler, + **kwargs: Any, + ): + if request_async_cls is _DEFAULT_ASYNC: + request_async_cls = FalconAsgiOpenAPIRequest + if response_async_cls is _DEFAULT_ASYNC: + response_async_cls = ( + FalconAsgiOpenAPIResponse if response_cls is not None else None + ) + + self.wsgi_middleware = FalconWSGIOpenAPIMiddleware( + openapi, + request_cls=request_cls, + response_cls=response_cls, + errors_handler_cls=errors_handler_cls, + **kwargs, + ) + self.asgi_middleware = FalconASGIOpenAPIMiddleware( + openapi, + request_cls=cast( + Type[FalconAsgiOpenAPIRequest], request_async_cls + ), + response_cls=cast( + Optional[Type[FalconAsgiOpenAPIResponse]], + response_async_cls, + ), + errors_handler_cls=errors_handler_cls, + **kwargs, + ) + + @classmethod + def from_spec( + cls, + spec: SchemaPath, + request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None, + response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None, + request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest, + response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse, + request_async_cls: Any = _DEFAULT_ASYNC, + response_async_cls: Any = _DEFAULT_ASYNC, + errors_handler_cls: Type[ + FalconOpenAPIErrorsHandler + ] = FalconOpenAPIErrorsHandler, + **kwargs: Any, + ) -> "FalconOpenAPIMiddleware": + openapi = OpenAPI.build( + spec, + request_unmarshaller_cls=request_unmarshaller_cls, + response_unmarshaller_cls=response_unmarshaller_cls, + ) + return cls( + openapi, + request_cls=request_cls, + response_cls=response_cls, + request_async_cls=request_async_cls, + response_async_cls=response_async_cls, + errors_handler_cls=errors_handler_cls, + **kwargs, + ) + + def process_request(self, req: Request, resp: Response) -> None: + self.wsgi_middleware.process_request(req, resp) + + def process_response( + self, req: Request, resp: Response, resource: Any, req_succeeded: bool + ) -> None: + self.wsgi_middleware.process_response( + req, + resp, + resource, + req_succeeded, + ) + + async def process_request_async( + self, req: Request, resp: Response + ) -> None: + await self.asgi_middleware.process_request_async(req, resp) + + async def process_response_async( + self, + req: Request, + resp: Response, + resource: Any, + req_succeeded: bool, + ) -> None: + await self.asgi_middleware.process_response_async( + req, + resp, + resource, + req_succeeded, + ) diff --git a/openapi_core/contrib/falcon/requests.py b/openapi_core/contrib/falcon/requests.py index 7e1fe1cf..9c59af31 100644 --- a/openapi_core/contrib/falcon/requests.py +++ b/openapi_core/contrib/falcon/requests.py @@ -1,19 +1,22 @@ """OpenAPI core contrib falcon responses module""" -import warnings from json import dumps from typing import Any from typing import Dict from typing import Optional +from typing import cast from falcon.request import Request from falcon.request import RequestOptions from werkzeug.datastructures import Headers from werkzeug.datastructures import ImmutableMultiDict +from openapi_core.contrib.falcon.util import serialize_body from openapi_core.contrib.falcon.util import unpack_params from openapi_core.datatypes import RequestParameters +_BODY_NOT_SET = object() + class FalconOpenAPIRequest: def __init__( @@ -27,6 +30,7 @@ def __init__( if default_when_empty is None: default_when_empty = {} self.default_when_empty = default_when_empty + self._body: Any = _BODY_NOT_SET # Path gets deduced by path finder against spec self.parameters = RequestParameters( @@ -52,32 +56,23 @@ def method(self) -> str: @property def body(self) -> Optional[bytes]: + if self._body is not _BODY_NOT_SET: + return cast(Optional[bytes], self._body) + # Falcon doesn't store raw request stream. # That's why we need to revert deserialized data # Support falcon-jsonify. - if hasattr(self.request, "json"): - return dumps(self.request.json).encode("utf-8") + request_json = getattr(cast(Any, self.request), "json", None) + if request_json is not None: + self._body = dumps(request_json).encode("utf-8") + return cast(Optional[bytes], self._body) media = self.request.get_media( default_when_empty=self.default_when_empty, ) - handler, _, _ = self.request.options.media_handlers._resolve( - self.request.content_type, self.request.options.default_media_type - ) - try: - body = handler.serialize( - media, content_type=self.request.content_type - ) - # multipart form serialization is not supported - except NotImplementedError: - warnings.warn( - f"body serialization for {self.request.content_type} not supported" - ) - return None - else: - assert isinstance(body, bytes) - return body + self._body = serialize_body(self.request, media, self.content_type) + return cast(Optional[bytes], self._body) @property def content_type(self) -> str: @@ -88,3 +83,21 @@ def content_type(self) -> str: assert isinstance(self.request.options, RequestOptions) assert isinstance(self.request.options.default_media_type, str) return self.request.options.default_media_type + + +class FalconAsgiOpenAPIRequest(FalconOpenAPIRequest): + @classmethod + async def from_request( + cls, + request: Request, + default_when_empty: Optional[Dict[Any, Any]] = None, + ) -> "FalconAsgiOpenAPIRequest": + instance = cls( + request, + default_when_empty=default_when_empty, + ) + media = await request.get_media( + default_when_empty=instance.default_when_empty + ) + instance._body = serialize_body(request, media, instance.content_type) + return instance diff --git a/openapi_core/contrib/falcon/responses.py b/openapi_core/contrib/falcon/responses.py index 2a3e7470..a6a74484 100644 --- a/openapi_core/contrib/falcon/responses.py +++ b/openapi_core/contrib/falcon/responses.py @@ -1,6 +1,11 @@ """OpenAPI core contrib falcon responses module""" +import inspect +from io import BytesIO from itertools import tee +from typing import Any +from typing import Iterable +from typing import List from falcon.response import Response from werkzeug.datastructures import Headers @@ -17,16 +22,22 @@ def data(self) -> bytes: if self.response.text is None: if self.response.stream is None: return b"" - resp_iter1, resp_iter2 = tee(self.response.stream) - self.response.stream = resp_iter1 - content = b"".join(resp_iter2) - return content + if isinstance(self.response.stream, Iterable): + resp_iter1, resp_iter2 = tee(self.response.stream) + self.response.stream = resp_iter1 + content = b"".join(resp_iter2) + return content + # checks ReadableIO protocol + if hasattr(self.response.stream, "read"): + data = self.response.stream.read() + self.response.stream = BytesIO(data) + return data assert isinstance(self.response.text, str) return self.response.text.encode("utf-8") @property def status_code(self) -> int: - return int(self.response.status[:3]) + return self.response.status_code @property def content_type(self) -> str: @@ -40,3 +51,82 @@ def content_type(self) -> str: @property def headers(self) -> Headers: return Headers(self.response.headers) + + +class FalconAsgiOpenAPIResponse(FalconOpenAPIResponse): + def __init__(self, response: Response, data: bytes): + super().__init__(response) + self._data = data + + @classmethod + async def from_response( + cls, + response: Any, + ) -> "FalconAsgiOpenAPIResponse": + data = await cls._get_asgi_response_data(response) + return cls(response, data=data) + + @classmethod + async def _get_asgi_response_data(cls, response: Any) -> bytes: + response_any = response + stream = response_any.stream + if stream is None: + data = await response_any.render_body() + if data is None: + return b"" + assert isinstance(data, bytes) + return data + + charset = getattr(response_any, "charset", None) or "utf-8" + chunks: List[bytes] = [] + stream_any = stream + + if hasattr(stream_any, "__aiter__"): + async for chunk in stream_any: + if chunk is None: + break + if not isinstance(chunk, bytes): + chunk = chunk.encode(charset) + chunks.append(chunk) + elif hasattr(stream_any, "read"): + while True: + chunk = stream_any.read() + if inspect.isawaitable(chunk): + chunk = await chunk + if not chunk: + break + if not isinstance(chunk, bytes): + chunk = chunk.encode(charset) + chunks.append(chunk) + elif isinstance(stream_any, Iterable): + response_iter1, response_iter2 = tee(stream_any) + response_any.stream = response_iter1 + for chunk in response_iter2: + if not isinstance(chunk, bytes): + chunk = chunk.encode(charset) + chunks.append(chunk) + return b"".join(chunks) + + response_any.stream = _AsyncChunksIterator(chunks) + return b"".join(chunks) + + @property + def data(self) -> bytes: + return self._data + + +class _AsyncChunksIterator: + def __init__(self, chunks: List[bytes]): + self._chunks = chunks + self._index = 0 + + def __aiter__(self) -> "_AsyncChunksIterator": + return self + + async def __anext__(self) -> bytes: + if self._index >= len(self._chunks): + raise StopAsyncIteration + + chunk = self._chunks[self._index] + self._index += 1 + return chunk diff --git a/openapi_core/contrib/falcon/util.py b/openapi_core/contrib/falcon/util.py index 0f651e42..83fa43e5 100644 --- a/openapi_core/contrib/falcon/util.py +++ b/openapi_core/contrib/falcon/util.py @@ -1,11 +1,35 @@ +import warnings from typing import Any -from typing import Dict from typing import Generator +from typing import Mapping +from typing import Optional from typing import Tuple +from falcon.request import Request + + +def serialize_body( + request: Request, + media: Any, + content_type: str, +) -> Optional[bytes]: + """Serialize request body using media handlers.""" + handler, _, _ = request.options.media_handlers._resolve( + content_type, + request.options.default_media_type, + ) + try: + body = handler.serialize(media, content_type=content_type) + # multipart form serialization is not supported + except NotImplementedError: + warnings.warn(f"body serialization for {content_type} not supported") + return None + assert isinstance(body, bytes) + return body + def unpack_params( - params: Dict[str, Any] + params: Mapping[str, Any], ) -> Generator[Tuple[str, Any], None, None]: for k, v in params.items(): if isinstance(v, list): diff --git a/openapi_core/contrib/starlette/integrations.py b/openapi_core/contrib/starlette/integrations.py index 02bfc5f1..4667fe01 100644 --- a/openapi_core/contrib/starlette/integrations.py +++ b/openapi_core/contrib/starlette/integrations.py @@ -1,7 +1,6 @@ from aioitertools.itertools import tee as atee from starlette.requests import Request from starlette.responses import Response -from starlette.responses import StreamingResponse from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse @@ -24,7 +23,7 @@ async def get_openapi_response( ) -> StarletteOpenAPIResponse: assert self.response_cls is not None data = None - if isinstance(response, StreamingResponse): + if hasattr(response, "body_iterator"): body_iter1, body_iter2 = atee(response.body_iterator) response.body_iterator = body_iter2 data = b"".join( diff --git a/openapi_core/contrib/starlette/middlewares.py b/openapi_core/contrib/starlette/middlewares.py index 2b0b9368..7a19f45f 100644 --- a/openapi_core/contrib/starlette/middlewares.py +++ b/openapi_core/contrib/starlette/middlewares.py @@ -1,5 +1,7 @@ """OpenAPI core contrib starlette middlewares module""" +from typing import Type + from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import RequestResponseEndpoint from starlette.requests import Request @@ -14,14 +16,26 @@ StarletteOpenAPIValidRequestHandler, ) from openapi_core.contrib.starlette.integrations import StarletteIntegration +from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest +from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse class StarletteOpenAPIMiddleware(StarletteIntegration, BaseHTTPMiddleware): valid_request_handler_cls = StarletteOpenAPIValidRequestHandler errors_handler = StarletteOpenAPIErrorsHandler() - def __init__(self, app: ASGIApp, openapi: OpenAPI): + def __init__( + self, + app: ASGIApp, + openapi: OpenAPI, + request_cls: Type[StarletteOpenAPIRequest] = StarletteOpenAPIRequest, + response_cls: Type[ + StarletteOpenAPIResponse + ] = StarletteOpenAPIResponse, + ): super().__init__(openapi) + self.request_cls = request_cls + self.response_cls = response_cls BaseHTTPMiddleware.__init__(self, app) async def dispatch( diff --git a/openapi_core/datatypes.py b/openapi_core/datatypes.py index 38746aff..07d4225f 100644 --- a/openapi_core/datatypes.py +++ b/openapi_core/datatypes.py @@ -1,15 +1,17 @@ """OpenAPI core validation request datatypes module""" -from __future__ import annotations - from dataclasses import dataclass from dataclasses import field from typing import Any from typing import Mapping +from typing import Union from werkzeug.datastructures import Headers from werkzeug.datastructures import ImmutableMultiDict +# Type alias for headers that accepts both Mapping and werkzeug Headers +HeadersType = Union[Mapping[str, Any], Headers] + @dataclass class RequestParameters: @@ -27,7 +29,7 @@ class RequestParameters: """ query: Mapping[str, Any] = field(default_factory=ImmutableMultiDict) - header: Mapping[str, Any] = field(default_factory=Headers) + header: HeadersType = field(default_factory=Headers) cookie: Mapping[str, Any] = field(default_factory=ImmutableMultiDict) path: Mapping[str, Any] = field(default_factory=dict) diff --git a/openapi_core/deserializing/media_types/__init__.py b/openapi_core/deserializing/media_types/__init__.py index fd4a0ae1..dfcd4b9d 100644 --- a/openapi_core/deserializing/media_types/__init__.py +++ b/openapi_core/deserializing/media_types/__init__.py @@ -12,9 +12,8 @@ from openapi_core.deserializing.media_types.util import plain_loads from openapi_core.deserializing.media_types.util import urlencoded_form_loads from openapi_core.deserializing.media_types.util import xml_loads -from openapi_core.deserializing.styles import style_deserializers_factory -__all__ = ["media_type_deserializers_factory"] +__all__ = ["media_type_deserializers", "MediaTypeDeserializersFactory"] media_type_deserializers: MediaTypeDeserializersDict = defaultdict( lambda: binary_loads, @@ -30,8 +29,3 @@ "multipart/form-data": data_form_loads, } ) - -media_type_deserializers_factory = MediaTypeDeserializersFactory( - style_deserializers_factory, - media_type_deserializers=media_type_deserializers, -) diff --git a/openapi_core/deserializing/media_types/deserializers.py b/openapi_core/deserializing/media_types/deserializers.py index a03c7e0d..027058d6 100644 --- a/openapi_core/deserializing/media_types/deserializers.py +++ b/openapi_core/deserializing/media_types/deserializers.py @@ -1,3 +1,4 @@ +from typing import TYPE_CHECKING from typing import Any from typing import Mapping from typing import Optional @@ -22,6 +23,10 @@ from openapi_core.schema.protocols import SuportsGetAll from openapi_core.schema.protocols import SuportsGetList from openapi_core.schema.schemas import get_properties +from openapi_core.validation.schemas.validators import SchemaValidator + +if TYPE_CHECKING: + from openapi_core.casting.schemas.casters import SchemaCaster class MediaTypesDeserializer: @@ -61,17 +66,23 @@ def get_deserializer_callable( class MediaTypeDeserializer: def __init__( self, + spec: SchemaPath, style_deserializers_factory: StyleDeserializersFactory, media_types_deserializer: MediaTypesDeserializer, mimetype: str, schema: Optional[SchemaPath] = None, + schema_validator: Optional[SchemaValidator] = None, + schema_caster: Optional["SchemaCaster"] = None, encoding: Optional[SchemaPath] = None, **parameters: str, ): + self.spec = spec self.style_deserializers_factory = style_deserializers_factory self.media_types_deserializer = media_types_deserializer self.mimetype = mimetype self.schema = schema + self.schema_validator = schema_validator + self.schema_caster = schema_caster self.encoding = encoding self.parameters = parameters @@ -86,25 +97,73 @@ def deserialize(self, value: bytes) -> Any: ): return deserialized - # decode multipart request bodies - return self.decode(deserialized) + # decode multipart request bodies if schema provided + if self.schema is not None: + return self.decode(deserialized) + + return deserialized def evolve( - self, mimetype: str, schema: Optional[SchemaPath] + self, + schema: SchemaPath, + mimetype: Optional[str] = None, ) -> "MediaTypeDeserializer": cls = self.__class__ + schema_validator = None + if self.schema_validator is not None: + schema_validator = self.schema_validator.evolve(schema) + + schema_caster = None + if self.schema_caster is not None: + schema_caster = self.schema_caster.evolve(schema) + return cls( + self.spec, self.style_deserializers_factory, self.media_types_deserializer, - mimetype, + mimetype=mimetype or self.mimetype, schema=schema, + schema_validator=schema_validator, + schema_caster=schema_caster, ) - def decode(self, location: Mapping[str, Any]) -> Mapping[str, Any]: + def decode( + self, location: Mapping[str, Any], schema_only: bool = False + ) -> Mapping[str, Any]: # schema is required for multipart assert self.schema is not None - properties = {} + properties: dict[str, Any] = {} + + # For urlencoded/multipart, use caster for oneOf/anyOf detection if validator available + if self.schema_validator is not None: + one_of_schema = self.schema_validator.get_one_of_schema( + location, caster=self.schema_caster + ) + if one_of_schema is not None: + one_of_properties = self.evolve(one_of_schema).decode( + location, schema_only=True + ) + properties.update(one_of_properties) + + any_of_schemas = self.schema_validator.iter_any_of_schemas( + location, caster=self.schema_caster + ) + for any_of_schema in any_of_schemas: + any_of_properties = self.evolve(any_of_schema).decode( + location, schema_only=True + ) + properties.update(any_of_properties) + + all_of_schemas = self.schema_validator.iter_all_of_schemas( + location + ) + for all_of_schema in all_of_schemas: + all_of_properties = self.evolve(all_of_schema).decode( + location, schema_only=True + ) + properties.update(all_of_properties) + for prop_name, prop_schema in get_properties(self.schema).items(): try: properties[prop_name] = self.decode_property( @@ -113,7 +172,10 @@ def decode(self, location: Mapping[str, Any]) -> Mapping[str, Any]: except KeyError: if "default" not in prop_schema: continue - properties[prop_name] = prop_schema["default"] + properties[prop_name] = (prop_schema / "default").read_value() + + if schema_only: + return properties return properties @@ -162,7 +224,7 @@ def decode_property_style( prep_encoding, default_location="query" ) prop_deserializer = self.style_deserializers_factory.create( - prop_style, prop_explode, prop_schema, name=prop_name + self.spec, prop_schema, prop_style, prop_explode, name=prop_name ) return prop_deserializer.deserialize(location) @@ -175,10 +237,10 @@ def decode_property_content_type( ) -> Any: prop_content_type = get_content_type(prop_schema, prop_encoding) prop_deserializer = self.evolve( - prop_content_type, prop_schema, + mimetype=prop_content_type, ) - prop_schema_type = prop_schema.getkey("type", "") + prop_schema_type = (prop_schema / "type").read_str("") if ( self.mimetype.startswith("multipart") and prop_schema_type == "array" diff --git a/openapi_core/deserializing/media_types/factories.py b/openapi_core/deserializing/media_types/factories.py index 45bc5075..33fb7369 100644 --- a/openapi_core/deserializing/media_types/factories.py +++ b/openapi_core/deserializing/media_types/factories.py @@ -3,6 +3,7 @@ from jsonschema_path import SchemaPath +from openapi_core.casting.schemas.factories import SchemaCastersFactory from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) @@ -12,9 +13,11 @@ from openapi_core.deserializing.media_types.deserializers import ( MediaTypesDeserializer, ) +from openapi_core.deserializing.styles.datatypes import StyleDeserializersDict from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) +from openapi_core.validation.schemas.validators import SchemaValidator class MediaTypeDeserializersFactory: @@ -28,10 +31,37 @@ def __init__( media_type_deserializers = {} self.media_type_deserializers = media_type_deserializers + @classmethod + def from_schema_casters_factory( + cls, + schema_casters_factory: SchemaCastersFactory, + style_deserializers: Optional[StyleDeserializersDict] = None, + media_type_deserializers: Optional[MediaTypeDeserializersDict] = None, + ) -> "MediaTypeDeserializersFactory": + from openapi_core.deserializing.media_types import ( + media_type_deserializers as default_media_type_deserializers, + ) + from openapi_core.deserializing.styles import ( + style_deserializers as default_style_deserializers, + ) + + style_deserializers_factory = StyleDeserializersFactory( + schema_casters_factory, + style_deserializers=style_deserializers + or default_style_deserializers, + ) + return cls( + style_deserializers_factory, + media_type_deserializers=media_type_deserializers + or default_media_type_deserializers, + ) + def create( self, + spec: SchemaPath, mimetype: str, schema: Optional[SchemaPath] = None, + schema_validator: Optional[SchemaValidator] = None, parameters: Optional[Mapping[str, str]] = None, encoding: Optional[SchemaPath] = None, extra_media_type_deserializers: Optional[ @@ -47,11 +77,31 @@ def create( extra_media_type_deserializers, ) + # Create schema caster for urlencoded/multipart content types + # Only create if both schema and schema_validator are provided + schema_caster = None + if ( + schema is not None + and schema_validator is not None + and ( + mimetype == "application/x-www-form-urlencoded" + or mimetype.startswith("multipart") + ) + ): + schema_caster = ( + self.style_deserializers_factory.schema_casters_factory.create( + spec, schema + ) + ) + return MediaTypeDeserializer( + spec, self.style_deserializers_factory, media_types_deserializer, mimetype, schema=schema, + schema_validator=schema_validator, + schema_caster=schema_caster, encoding=encoding, **parameters, ) diff --git a/openapi_core/deserializing/media_types/util.py b/openapi_core/deserializing/media_types/util.py index 16254382..9c63ce69 100644 --- a/openapi_core/deserializing/media_types/util.py +++ b/openapi_core/deserializing/media_types/util.py @@ -1,7 +1,10 @@ +from email.message import Message from email.parser import Parser from json import loads from typing import Any +from typing import Iterator from typing import Mapping +from typing import Tuple from urllib.parse import parse_qsl from xml.etree.ElementTree import Element from xml.etree.ElementTree import fromstring @@ -41,7 +44,9 @@ def urlencoded_form_loads( value: bytes, **parameters: str ) -> Mapping[str, Any]: # only UTF-8 is conforming - return ImmutableMultiDict(parse_qsl(value.decode("utf-8"))) + return ImmutableMultiDict( + parse_qsl(value.decode("utf-8"), keep_blank_values=True) + ) def data_form_loads(value: bytes, **parameters: str) -> Mapping[str, Any]: @@ -57,12 +62,14 @@ def data_form_loads(value: bytes, **parameters: str) -> Mapping[str, Any]: header = f'Content-Type: {mimetype}; boundary="{boundary}"' text = "\n\n".join([header, decoded]) parts = parser.parsestr(text, headersonly=False) - return ImmutableMultiDict( - [ - ( - part.get_param("name", header="content-disposition"), - part.get_payload(decode=True), - ) - for part in parts.get_payload() - ] - ) + return ImmutableMultiDict(list(iter_payloads(parts))) + + +def iter_payloads(parts: Message) -> Iterator[Tuple[str, bytes]]: + for part in parts.get_payload(): + assert isinstance(part, Message) + name = part.get_param("name", header="content-disposition") + assert isinstance(name, str) + payload = part.get_payload(decode=True) + assert isinstance(payload, bytes) + yield name, payload diff --git a/openapi_core/deserializing/styles/__init__.py b/openapi_core/deserializing/styles/__init__.py index f9ecef06..b7819950 100644 --- a/openapi_core/deserializing/styles/__init__.py +++ b/openapi_core/deserializing/styles/__init__.py @@ -10,7 +10,7 @@ from openapi_core.deserializing.styles.util import simple_loads from openapi_core.deserializing.styles.util import space_delimited_loads -__all__ = ["style_deserializers_factory"] +__all__ = ["style_deserializers", "StyleDeserializersFactory"] style_deserializers: StyleDeserializersDict = { "matrix": matrix_loads, @@ -21,7 +21,3 @@ "pipeDelimited": pipe_delimited_loads, "deepObject": deep_object_loads, } - -style_deserializers_factory = StyleDeserializersFactory( - style_deserializers=style_deserializers, -) diff --git a/openapi_core/deserializing/styles/casters.py b/openapi_core/deserializing/styles/casters.py new file mode 100644 index 00000000..698101df --- /dev/null +++ b/openapi_core/deserializing/styles/casters.py @@ -0,0 +1,55 @@ +from typing import Any + +from jsonschema_path import SchemaPath + +from openapi_core.util import forcebool + + +def cast_primitive(value: Any, schema: SchemaPath) -> Any: + """Cast a primitive value based on schema type.""" + schema_type = (schema / "type").read_str("") + + if schema_type == "integer": + return int(value) + elif schema_type == "number": + return float(value) + elif schema_type == "boolean": + return forcebool(value) + + return value + + +def cast_value(value: Any, schema: SchemaPath, cast: bool) -> Any: + """Recursively cast a value based on schema.""" + if not cast: + return value + + schema_type = (schema / "type").read_str("") + + # Handle arrays + if schema_type == "array": + if not isinstance(value, list): + raise ValueError( + f"Expected list for array type, got {type(value)}" + ) + items_schema = schema.get("items", SchemaPath.from_dict({})) + return [cast_value(item, items_schema, cast) for item in value] + + # Handle objects + if schema_type == "object": + if not isinstance(value, dict): + raise ValueError( + f"Expected dict for object type, got {type(value)}" + ) + properties = schema.get("properties", SchemaPath.from_dict({})) + result = {} + for key, val in value.items(): + if key in properties: + prop_schema = schema / "properties" / key + result[key] = cast_value(val, prop_schema, cast) + else: + result[key] = val + return result + + # Handle primitives + return cast_primitive(value, schema) diff --git a/openapi_core/deserializing/styles/deserializers.py b/openapi_core/deserializing/styles/deserializers.py index 2303f7a3..59565603 100644 --- a/openapi_core/deserializing/styles/deserializers.py +++ b/openapi_core/deserializing/styles/deserializers.py @@ -3,6 +3,8 @@ from typing import Mapping from typing import Optional +from openapi_core.casting.schemas.casters import SchemaCaster +from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.styles.datatypes import DeserializerCallable @@ -14,12 +16,14 @@ def __init__( explode: bool, name: str, schema_type: str, + caster: SchemaCaster, deserializer_callable: Optional[DeserializerCallable] = None, ): self.style = style self.explode = explode self.name = name self.schema_type = schema_type + self.caster = caster self.deserializer_callable = deserializer_callable def deserialize(self, location: Mapping[str, Any]) -> Any: @@ -28,8 +32,13 @@ def deserialize(self, location: Mapping[str, Any]) -> Any: return location[self.name] try: - return self.deserializer_callable( + value = self.deserializer_callable( self.explode, self.name, self.schema_type, location ) - except (ValueError, TypeError, AttributeError): - raise DeserializeError(self.style, self.name) + except (ValueError, TypeError, AttributeError) as exc: + raise DeserializeError(self.style, self.name) from exc + + try: + return self.caster.cast(value) + except (ValueError, TypeError, AttributeError) as exc: + raise CastError(value, self.schema_type) from exc diff --git a/openapi_core/deserializing/styles/factories.py b/openapi_core/deserializing/styles/factories.py index 5758d97d..2d4504c5 100644 --- a/openapi_core/deserializing/styles/factories.py +++ b/openapi_core/deserializing/styles/factories.py @@ -2,6 +2,7 @@ from jsonschema_path import SchemaPath +from openapi_core.casting.schemas.factories import SchemaCastersFactory from openapi_core.deserializing.styles.datatypes import StyleDeserializersDict from openapi_core.deserializing.styles.deserializers import StyleDeserializer @@ -9,22 +10,25 @@ class StyleDeserializersFactory: def __init__( self, + schema_casters_factory: SchemaCastersFactory, style_deserializers: Optional[StyleDeserializersDict] = None, ): + self.schema_casters_factory = schema_casters_factory if style_deserializers is None: style_deserializers = {} self.style_deserializers = style_deserializers def create( self, + spec: SchemaPath, + schema: SchemaPath, style: str, explode: bool, - schema: SchemaPath, name: str, ) -> StyleDeserializer: - schema_type = schema.getkey("type", "") - deserialize_callable = self.style_deserializers.get(style) + caster = self.schema_casters_factory.create(spec, schema) + schema_type = (schema / "type").read_str("") return StyleDeserializer( - style, explode, name, schema_type, deserialize_callable + style, explode, name, schema_type, caster, deserialize_callable ) diff --git a/openapi_core/extensions/models/factories.py b/openapi_core/extensions/models/factories.py index 0f33b3cf..9f25f04d 100644 --- a/openapi_core/extensions/models/factories.py +++ b/openapi_core/extensions/models/factories.py @@ -27,7 +27,7 @@ def create( schema: SchemaPath, fields: Iterable[Field], ) -> Type[Any]: - name = schema.getkey("x-model") + name = (schema / "x-model").read_str(None) if name is None: return super().create(schema, fields) @@ -40,7 +40,7 @@ def create( schema: SchemaPath, fields: Iterable[Field], ) -> Any: - model_class_path = schema.getkey("x-model-path") + model_class_path = (schema / "x-model-path").read_str(None) if model_class_path is None: return super().create(schema, fields) diff --git a/openapi_core/protocols.py b/openapi_core/protocols.py index 5c3145c6..0397956d 100644 --- a/openapi_core/protocols.py +++ b/openapi_core/protocols.py @@ -1,101 +1,98 @@ -"""OpenAPI core protocols module""" +"""OpenAPI core protocols""" from typing import Any from typing import Mapping from typing import Optional from typing import Protocol +from typing import Union from typing import runtime_checkable +from werkzeug.datastructures import Headers + from openapi_core.datatypes import RequestParameters +# Type alias for headers that accepts both Mapping and werkzeug Headers +HeadersType = Union[Mapping[str, Any], Headers] + @runtime_checkable class BaseRequest(Protocol): parameters: RequestParameters @property - def method(self) -> str: ... + def method(self) -> str: + """The request method, as lowercase string.""" @property - def body(self) -> Optional[bytes]: ... + def body(self) -> Optional[bytes]: + """The request body, as bytes (None if not provided).""" @property - def content_type(self) -> str: ... + def content_type(self) -> str: + """The content type with parameters (e.g., charset, boundary, etc.) and always lowercase.""" @runtime_checkable class Request(BaseRequest, Protocol): - """Request attributes protocol. + """Request protocol. Attributes: - host_url - Url with scheme and host - For example: - https://localhost:8000 - path - Request path - full_url_pattern - The matched url with scheme, host and path pattern. - For example: - https://localhost:8000/api/v1/pets - https://localhost:8000/api/v1/pets/{pet_id} - method - The request method, as lowercase string. - parameters - A RequestParameters object. Needs to supports path attribute setter + host_url: Url with scheme and host. + For example: https://localhost:8000 + path: Request path. + full_url_pattern: The matched url with scheme, host and path pattern. + For example: https://localhost:8000/api/v1/pets + https://localhost:8000/api/v1/pets/{pet_id} + method: The request method, as lowercase string. + parameters: A RequestParameters object. Needs to support path attribute setter to write resolved path parameters. - content_type - The content type with parameters (eg, charset, boundary etc.) + content_type: The content type with parameters (e.g., charset, boundary, etc.) and always lowercase. - body - The request body, as bytes (None if not provided). + body: The request body, as bytes (None if not provided). """ @property - def host_url(self) -> str: ... + def host_url(self) -> str: + """Url with scheme and host. For example: https://localhost:8000""" @property - def path(self) -> str: ... + def path(self) -> str: + """Request path.""" @runtime_checkable class WebhookRequest(BaseRequest, Protocol): - """Webhook request attributes protocol. + """Webhook request protocol. Attributes: - name - Webhook name - method - The request method, as lowercase string. - parameters - A RequestParameters object. Needs to supports path attribute setter + name: Webhook name. + method: The request method, as lowercase string. + parameters: A RequestParameters object. Needs to support path attribute setter to write resolved path parameters. - content_type - The content type with parameters (eg, charset, boundary etc.) + content_type: The content type with parameters (e.g., charset, boundary, etc.) and always lowercase. - body - The request body, as bytes (None if not provided). + body: The request body, as bytes (None if not provided). """ @property - def name(self) -> str: ... + def name(self) -> str: + """Webhook name.""" @runtime_checkable class SupportsPathPattern(Protocol): - """Supports path_pattern attribute protocol. + """Supports path_pattern protocol. You also need to provide path variables in RequestParameters. Attributes: - path_pattern - The matched path pattern. - For example: - /api/v1/pets/{pet_id} + path_pattern: The matched path pattern. + For example: /api/v1/pets/{pet_id} """ @property - def path_pattern(self) -> str: ... + def path_pattern(self) -> str: + """The matched path pattern. For example: /api/v1/pets/{pet_id}""" @runtime_checkable @@ -103,24 +100,24 @@ class Response(Protocol): """Response protocol. Attributes: - status_code - The status code as integer. - headers - Response headers as Headers. - content_type - The content type with parameters and always lowercase. - data - The response body, as bytes (None if not provided). + status_code: The status code as integer. + headers: Response headers as Headers. + content_type: The content type with parameters and always lowercase. + data: The response body, as bytes (None if not provided). """ @property - def status_code(self) -> int: ... + def status_code(self) -> int: + """The status code as integer.""" @property - def content_type(self) -> str: ... + def content_type(self) -> str: + """The content type with parameters and always lowercase.""" @property - def headers(self) -> Mapping[str, Any]: ... + def headers(self) -> HeadersType: + """Response headers as Headers.""" @property - def data(self) -> Optional[bytes]: ... + def data(self) -> Optional[bytes]: + """The response body, as bytes (None if not provided).""" diff --git a/openapi_core/schema/encodings.py b/openapi_core/schema/encodings.py index 2dd3d9fa..b78c1a6a 100644 --- a/openapi_core/schema/encodings.py +++ b/openapi_core/schema/encodings.py @@ -22,11 +22,11 @@ def get_default_content_type( if prop_schema is None: return "text/plain" - prop_type = prop_schema.getkey("type") + prop_type = (prop_schema / "type").read_str(None) if prop_type is None: return "text/plain" if encoding else "application/octet-stream" - prop_format = prop_schema.getkey("format") + prop_format = (prop_schema / "format").read_str(None) if prop_type == "string" and prop_format in ["binary", "base64"]: return "application/octet-stream" diff --git a/openapi_core/schema/parameters.py b/openapi_core/schema/parameters.py index 967e53f3..e9f1debd 100644 --- a/openapi_core/schema/parameters.py +++ b/openapi_core/schema/parameters.py @@ -11,7 +11,7 @@ def get_style( assert isinstance(param_or_header["style"], str) return param_or_header["style"] - location = param_or_header.getkey("in", default_location) + location = (param_or_header / "in").read_str(default=default_location) # determine default return "simple" if location in ["path", "header"] else "form" diff --git a/openapi_core/schema/schemas.py b/openapi_core/schema/schemas.py index 7ddb6b17..cab9d5b8 100644 --- a/openapi_core/schema/schemas.py +++ b/openapi_core/schema/schemas.py @@ -5,6 +5,6 @@ def get_properties(schema: SchemaPath) -> Dict[str, Any]: - properties = schema.get("properties", {}) + properties = schema.get("properties", SchemaPath.from_dict({})) properties_dict = dict(list(properties.items())) return properties_dict diff --git a/openapi_core/schema/servers.py b/openapi_core/schema/servers.py index 249c30bc..b29eefaf 100644 --- a/openapi_core/schema/servers.py +++ b/openapi_core/schema/servers.py @@ -14,13 +14,14 @@ def get_server_default_variables(server: SchemaPath) -> Dict[str, Any]: defaults = {} variables = server / "variables" - for name, variable in list(variables.items()): - defaults[name] = variable["default"] + for name, variable in list(variables.str_items()): + defaults[name] = (variable / "default").read_value() return defaults def get_server_url(server: SchemaPath, **variables: Any) -> str: if not variables: variables = get_server_default_variables(server) - assert isinstance(server["url"], str) - return server["url"].format(**variables) + url = (server / "url").read_value() + assert isinstance(url, str) + return url.format(**variables) diff --git a/openapi_core/security/factories.py b/openapi_core/security/factories.py index 3ab9b79b..a85a004d 100644 --- a/openapi_core/security/factories.py +++ b/openapi_core/security/factories.py @@ -19,6 +19,6 @@ class SecurityProviderFactory: } def create(self, scheme: SchemaPath) -> Any: - scheme_type = scheme["type"] + scheme_type = (scheme / "type").read_str() provider_class = self.PROVIDERS[scheme_type] return provider_class(scheme) diff --git a/openapi_core/security/providers.py b/openapi_core/security/providers.py index 531feec3..f75ba663 100644 --- a/openapi_core/security/providers.py +++ b/openapi_core/security/providers.py @@ -22,8 +22,8 @@ def __call__(self, parameters: RequestParameters) -> Any: class ApiKeyProvider(BaseProvider): def __call__(self, parameters: RequestParameters) -> Any: - name = self.scheme["name"] - location = self.scheme["in"] + name = (self.scheme / "name").read_str() + location = (self.scheme / "in").read_str() source = getattr(parameters, location) if name not in source: raise SecurityProviderError("Missing api key parameter.") diff --git a/openapi_core/shortcuts.py b/openapi_core/shortcuts.py index be5c69f9..39bc2b4f 100644 --- a/openapi_core/shortcuts.py +++ b/openapi_core/shortcuts.py @@ -1,6 +1,7 @@ """OpenAPI core shortcuts module""" from typing import Any +from typing import Iterator from typing import Optional from typing import Union @@ -164,6 +165,22 @@ def validate_request( return OpenAPI(spec, config=config).validate_request(request) +def iter_request_errors( + request: AnyRequest, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[AnyRequestValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + request_validator_cls=cls or _UNSET, + webhook_request_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_request_errors(request) + + def validate_response( request: Union[Request, WebhookRequest], response: Response, @@ -181,6 +198,23 @@ def validate_response( return OpenAPI(spec, config=config).validate_response(request, response) +def iter_response_errors( + request: Union[Request, WebhookRequest], + response: Response, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[AnyResponseValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + response_validator_cls=cls or _UNSET, + webhook_response_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_response_errors(request, response) + + def validate_apicall_request( request: Request, spec: SchemaPath, @@ -196,6 +230,21 @@ def validate_apicall_request( return OpenAPI(spec, config=config).validate_apicall_request(request) +def iter_apicall_request_errors( + request: Request, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[RequestValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + request_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_apicall_request_errors(request) + + def validate_webhook_request( request: WebhookRequest, spec: SchemaPath, @@ -211,6 +260,21 @@ def validate_webhook_request( return OpenAPI(spec, config=config).validate_webhook_request(request) +def iter_webhook_request_errors( + request: WebhookRequest, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[WebhookRequestValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + webhook_request_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_webhook_request_errors(request) + + def validate_apicall_response( request: Request, response: Response, @@ -229,6 +293,24 @@ def validate_apicall_response( ) +def iter_apicall_response_errors( + request: Request, + response: Response, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[ResponseValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + response_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_apicall_response_errors( + request, response + ) + + def validate_webhook_response( request: WebhookRequest, response: Response, @@ -245,3 +327,21 @@ def validate_webhook_response( return OpenAPI(spec, config=config).validate_webhook_response( request, response ) + + +def iter_webhook_response_errors( + request: WebhookRequest, + response: Response, + spec: SchemaPath, + base_url: Optional[str] = None, + cls: Optional[WebhookResponseValidatorType] = None, + **validator_kwargs: Any, +) -> Iterator[Exception]: + config = Config( + server_base_url=base_url, + webhook_response_validator_cls=cls or _UNSET, + **validator_kwargs, + ) + return OpenAPI(spec, config=config).iter_webhook_response_errors( + request, response + ) diff --git a/openapi_core/spec/paths.py b/openapi_core/spec/paths.py deleted file mode 100644 index a1846ee0..00000000 --- a/openapi_core/spec/paths.py +++ /dev/null @@ -1,13 +0,0 @@ -import warnings -from typing import Any - -from jsonschema_path import SchemaPath - - -class Spec(SchemaPath): - def __init__(self, *args: Any, **kwargs: Any): - warnings.warn( - "Spec is deprecated. Use SchemaPath from jsonschema-path package.", - DeprecationWarning, - ) - super().__init__(*args, **kwargs) diff --git a/openapi_core/templating/media_types/finders.py b/openapi_core/templating/media_types/finders.py index 1be2a022..6fd0fd03 100644 --- a/openapi_core/templating/media_types/finders.py +++ b/openapi_core/templating/media_types/finders.py @@ -32,11 +32,11 @@ def find(self, mimetype: str) -> MediaType: # range mime type if mime_type: - for key, value in self.content.items(): + for key, value in self.content.str_items(): if fnmatch.fnmatch(mime_type, key): return MediaType(key, parameters, value) - raise MediaTypeNotFound(mimetype, list(self.content.keys())) + raise MediaTypeNotFound(mimetype, list(self.content.str_keys())) def _parse_mimetype(self, mimetype: str) -> Tuple[str, Mapping[str, str]]: mimetype_parts = mimetype.split(";") diff --git a/openapi_core/templating/paths/iterators.py b/openapi_core/templating/paths/iterators.py index f78d3342..5c1dd2fb 100644 --- a/openapi_core/templating/paths/iterators.py +++ b/openapi_core/templating/paths/iterators.py @@ -1,3 +1,4 @@ +from functools import lru_cache from typing import Iterator from typing import List from typing import Optional @@ -12,9 +13,8 @@ from openapi_core.templating.paths.datatypes import PathOperation from openapi_core.templating.paths.datatypes import PathOperationServer from openapi_core.templating.paths.exceptions import PathsNotFound +from openapi_core.templating.paths.parsers import PathParser from openapi_core.templating.paths.util import template_path_len -from openapi_core.templating.util import parse -from openapi_core.templating.util import search class SimplePathsIterator: @@ -27,7 +27,7 @@ def __call__( paths = spec / self.paths_part if not paths.exists(): raise PathsNotFound(paths.as_uri()) - for path_name, path in list(paths.items()): + for path_name, path in list(paths.str_items()): if name == path_name: path_result = TemplateResult(path_name, {}) yield Path(path, path_result) @@ -44,7 +44,7 @@ def __call__( if not paths.exists(): raise PathsNotFound(paths.as_uri()) template_paths: List[Path] = [] - for path_pattern, path in list(paths.items()): + for path_pattern, path in list(paths.str_items()): # simple path. # Return right away since it is always the most concrete if name.endswith(path_pattern): @@ -52,7 +52,8 @@ def __call__( yield Path(path, path_result) # template path else: - result = search(path_pattern, name) + path_parser = self._get_path_parser(path_pattern) + result = path_parser.search(name) if result: path_result = TemplateResult(path_pattern, result.named) template_paths.append(Path(path, path_result)) @@ -60,6 +61,10 @@ def __call__( # Fewer variables -> more concrete path yield from sorted(template_paths, key=template_path_len) + @lru_cache(maxsize=4096) + def _get_path_parser(self, path_pattern: str) -> PathParser: + return PathParser(path_pattern, post_expression="$") + class SimpleOperationsIterator: def __call__( @@ -156,7 +161,10 @@ def __call__( ) # template path else: - result = parse(server["url"], server_url_pattern) + server_url_parser = self._get_server_url_parser( + server["url"] + ) + result = server_url_parser.parse(server_url_pattern) if result: server_result = TemplateResult( server["url"], result.named @@ -171,7 +179,7 @@ def __call__( # servers should'n end with tailing slash # but let's search for this too server_url_pattern += "/" - result = parse(server["url"], server_url_pattern) + result = server_url_parser.parse(server_url_pattern) if result: server_result = TemplateResult( server["url"], result.named @@ -183,3 +191,7 @@ def __call__( path_result, server_result, ) + + @lru_cache(maxsize=1024) + def _get_server_url_parser(self, server_url: str) -> PathParser: + return PathParser(server_url, pre_expression="^") diff --git a/openapi_core/templating/paths/parsers.py b/openapi_core/templating/paths/parsers.py new file mode 100644 index 00000000..27049ec6 --- /dev/null +++ b/openapi_core/templating/paths/parsers.py @@ -0,0 +1,70 @@ +import re +from dataclasses import dataclass + + +@dataclass(frozen=True) +class PathMatchResult: + """Result of path parsing.""" + + named: dict[str, str] + + +class PathParser: + """Parses path patterns with parameters into regex and matches against URLs.""" + + _PARAM_PATTERN = r"[^/]*" + + def __init__( + self, pattern: str, pre_expression: str = "", post_expression: str = "" + ) -> None: + self.pattern = pattern + self._group_to_name: dict[str, str] = {} + + regex_body = self._compile_template_to_regex(pattern) + self._expression = f"{pre_expression}{regex_body}{post_expression}" + self._compiled = re.compile(self._expression) + + def search(self, text: str) -> PathMatchResult | None: + """Searches for a match in the given text.""" + match = self._compiled.search(text) + return self._to_result(match) + + def parse(self, text: str) -> PathMatchResult | None: + """Parses the entire text for a match.""" + match = self._compiled.fullmatch(text) + return self._to_result(match) + + def _compile_template_to_regex(self, template: str) -> str: + parts: list[str] = [] + i = 0 + group_index = 0 + while i < len(template): + start = template.find("{", i) + if start == -1: + parts.append(re.escape(template[i:])) + break + end = template.find("}", start + 1) + if end == -1: + raise ValueError(f"Unmatched '{{' in template: {template!r}") + + parts.append(re.escape(template[i:start])) + param_name = template[start + 1 : end] + group_name = f"g{group_index}" + group_index += 1 + self._group_to_name[group_name] = param_name + parts.append(f"(?P<{group_name}>{self._PARAM_PATTERN})") + i = end + 1 + + return "".join(parts) + + def _to_result( + self, match: re.Match[str] | None + ) -> PathMatchResult | None: + if match is None: + return None + return PathMatchResult( + named={ + param_name: match.group(group_name) + for group_name, param_name in self._group_to_name.items() + }, + ) diff --git a/openapi_core/templating/responses/finders.py b/openapi_core/templating/responses/finders.py index b05cda91..a49d37fa 100644 --- a/openapi_core/templating/responses/finders.py +++ b/openapi_core/templating/responses/finders.py @@ -17,6 +17,8 @@ def find(self, http_status: str = "default") -> SchemaPath: return self.responses / http_status_range if "default" not in self.responses: - raise ResponseNotFound(http_status, list(self.responses.keys())) + raise ResponseNotFound( + http_status, list(self.responses.str_keys()) + ) return self.responses / "default" diff --git a/openapi_core/templating/util.py b/openapi_core/templating/util.py deleted file mode 100644 index ef5dfa71..00000000 --- a/openapi_core/templating/util.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Any -from typing import Optional - -from parse import Match -from parse import Parser - - -class ExtendedParser(Parser): # type: ignore - def _handle_field(self, field: str) -> Any: - # handle as path parameter field - field = field[1:-1] - path_parameter_field = "{%s:PathParameter}" % field - return super()._handle_field(path_parameter_field) - - -class PathParameter: - name = "PathParameter" - pattern = r"[^\/]*" - - def __call__(self, text: str) -> str: - return text - - -parse_path_parameter = PathParameter() - - -def search(path_pattern: str, full_url_pattern: str) -> Optional[Match]: - extra_types = {parse_path_parameter.name: parse_path_parameter} - p = ExtendedParser(path_pattern, extra_types) - p._expression = p._expression + "$" - return p.search(full_url_pattern) - - -def parse(server_url: str, server_url_pattern: str) -> Match: - extra_types = {parse_path_parameter.name: parse_path_parameter} - p = ExtendedParser(server_url, extra_types) - p._expression = "^" + p._expression - return p.parse(server_url_pattern) diff --git a/openapi_core/types.py b/openapi_core/types.py index 2a1934ad..ab47f7a5 100644 --- a/openapi_core/types.py +++ b/openapi_core/types.py @@ -1,3 +1,5 @@ +"""OpenAPI core types""" + from typing import Union from openapi_core.protocols import Request diff --git a/openapi_core/unmarshalling/request/__init__.py b/openapi_core/unmarshalling/request/__init__.py index 1b41432e..b6fe98ae 100644 --- a/openapi_core/unmarshalling/request/__init__.py +++ b/openapi_core/unmarshalling/request/__init__.py @@ -18,6 +18,12 @@ from openapi_core.unmarshalling.request.unmarshallers import ( V31WebhookRequestUnmarshaller, ) +from openapi_core.unmarshalling.request.unmarshallers import ( + V32RequestUnmarshaller, +) +from openapi_core.unmarshalling.request.unmarshallers import ( + V32WebhookRequestUnmarshaller, +) __all__ = [ "UNMARSHALLERS", @@ -27,17 +33,21 @@ "V30RequestUnmarshaller", "V31RequestUnmarshaller", "V31WebhookRequestUnmarshaller", + "V32RequestUnmarshaller", + "V32WebhookRequestUnmarshaller", ] # versions mapping UNMARSHALLERS: Mapping[SpecVersion, RequestUnmarshallerType] = { versions.OPENAPIV30: V30RequestUnmarshaller, versions.OPENAPIV31: V31RequestUnmarshaller, + versions.OPENAPIV32: V32RequestUnmarshaller, } WEBHOOK_UNMARSHALLERS: Mapping[SpecVersion, WebhookRequestUnmarshallerType] = { versions.OPENAPIV31: V31WebhookRequestUnmarshaller, + versions.OPENAPIV32: V32WebhookRequestUnmarshaller, } # alias to the latest v3 version -V3RequestUnmarshaller = V31RequestUnmarshaller -V3WebhookRequestUnmarshaller = V31WebhookRequestUnmarshaller +V3RequestUnmarshaller = V32RequestUnmarshaller +V3WebhookRequestUnmarshaller = V32WebhookRequestUnmarshaller diff --git a/openapi_core/unmarshalling/request/datatypes.py b/openapi_core/unmarshalling/request/datatypes.py index 9e82ccab..02f70636 100644 --- a/openapi_core/unmarshalling/request/datatypes.py +++ b/openapi_core/unmarshalling/request/datatypes.py @@ -1,9 +1,8 @@ """OpenAPI core unmarshalling request datatypes module""" -from __future__ import annotations - from dataclasses import dataclass from dataclasses import field +from typing import Any from openapi_core.datatypes import Parameters from openapi_core.unmarshalling.datatypes import BaseUnmarshalResult @@ -11,6 +10,6 @@ @dataclass class RequestUnmarshalResult(BaseUnmarshalResult): - body: str | None = None + body: Any | None = None parameters: Parameters = field(default_factory=Parameters) security: dict[str, str] | None = None diff --git a/openapi_core/unmarshalling/request/protocols.py b/openapi_core/unmarshalling/request/protocols.py index 43a18cbe..9289def5 100644 --- a/openapi_core/unmarshalling/request/protocols.py +++ b/openapi_core/unmarshalling/request/protocols.py @@ -8,16 +8,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -43,8 +39,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -55,6 +55,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, @@ -74,8 +75,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -86,6 +91,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, diff --git a/openapi_core/unmarshalling/request/unmarshallers.py b/openapi_core/unmarshalling/request/unmarshallers.py index efd45930..3be652d2 100644 --- a/openapi_core/unmarshalling/request/unmarshallers.py +++ b/openapi_core/unmarshalling/request/unmarshallers.py @@ -4,16 +4,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -31,6 +27,9 @@ from openapi_core.unmarshalling.schemas import ( oas31_schema_unmarshallers_factory, ) +from openapi_core.unmarshalling.schemas import ( + oas32_schema_unmarshallers_factory, +) from openapi_core.unmarshalling.schemas.datatypes import ( FormatUnmarshallersDict, ) @@ -75,6 +74,26 @@ from openapi_core.validation.request.validators import ( V31WebhookRequestValidator, ) +from openapi_core.validation.request.validators import V32RequestBodyValidator +from openapi_core.validation.request.validators import ( + V32RequestParametersValidator, +) +from openapi_core.validation.request.validators import ( + V32RequestSecurityValidator, +) +from openapi_core.validation.request.validators import V32RequestValidator +from openapi_core.validation.request.validators import ( + V32WebhookRequestBodyValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestParametersValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestSecurityValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestValidator, +) from openapi_core.validation.request.validators import WebhookRequestValidator from openapi_core.validation.schemas.datatypes import FormatValidatorsDict from openapi_core.validation.schemas.factories import SchemaValidatorsFactory @@ -85,8 +104,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -97,6 +120,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, @@ -116,6 +140,7 @@ def __init__( format_validators=format_validators, extra_format_validators=extra_format_validators, extra_media_type_deserializers=extra_media_type_deserializers, + forbid_unspecified_additional_properties=forbid_unspecified_additional_properties, schema_unmarshallers_factory=schema_unmarshallers_factory, format_unmarshallers=format_unmarshallers, extra_format_unmarshallers=extra_format_unmarshallers, @@ -134,6 +159,7 @@ def __init__( extra_format_validators=extra_format_validators, extra_media_type_deserializers=extra_media_type_deserializers, security_provider_factory=security_provider_factory, + forbid_unspecified_additional_properties=forbid_unspecified_additional_properties, ) def _unmarshal( @@ -198,7 +224,7 @@ def _unmarshal_parameters( self, request: BaseRequest, operation: SchemaPath, path: SchemaPath ) -> RequestUnmarshalResult: try: - params = self._get_parameters(request.parameters, path, operation) + params = self._get_parameters(request.parameters, operation, path) except ParametersError as exc: params = exc.parameters params_errors = exc.errors @@ -434,3 +460,49 @@ class V31WebhookRequestUnmarshaller( V31WebhookRequestValidator, WebhookRequestUnmarshaller ): schema_unmarshallers_factory = oas31_schema_unmarshallers_factory + + +class V32RequestBodyUnmarshaller( + V32RequestBodyValidator, APICallRequestBodyUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32RequestParametersUnmarshaller( + V32RequestParametersValidator, APICallRequestParametersUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32RequestSecurityUnmarshaller( + V32RequestSecurityValidator, APICallRequestSecurityUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32RequestUnmarshaller(V32RequestValidator, APICallRequestUnmarshaller): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookRequestBodyUnmarshaller( + V32WebhookRequestBodyValidator, WebhookRequestBodyUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookRequestParametersUnmarshaller( + V32WebhookRequestParametersValidator, WebhookRequestParametersUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookRequestSecurityUnmarshaller( + V32WebhookRequestSecurityValidator, WebhookRequestSecuritysUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookRequestUnmarshaller( + V32WebhookRequestValidator, WebhookRequestUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory diff --git a/openapi_core/unmarshalling/response/__init__.py b/openapi_core/unmarshalling/response/__init__.py index e1ebf5d5..24f9e9c0 100644 --- a/openapi_core/unmarshalling/response/__init__.py +++ b/openapi_core/unmarshalling/response/__init__.py @@ -18,6 +18,12 @@ from openapi_core.unmarshalling.response.unmarshallers import ( V31WebhookResponseUnmarshaller, ) +from openapi_core.unmarshalling.response.unmarshallers import ( + V32ResponseUnmarshaller, +) +from openapi_core.unmarshalling.response.unmarshallers import ( + V32WebhookResponseUnmarshaller, +) __all__ = [ "UNMARSHALLERS", @@ -27,19 +33,23 @@ "V30ResponseUnmarshaller", "V31ResponseUnmarshaller", "V31WebhookResponseUnmarshaller", + "V32ResponseUnmarshaller", + "V32WebhookResponseUnmarshaller", ] # versions mapping UNMARSHALLERS: Mapping[SpecVersion, ResponseUnmarshallerType] = { versions.OPENAPIV30: V30ResponseUnmarshaller, versions.OPENAPIV31: V31ResponseUnmarshaller, + versions.OPENAPIV32: V32ResponseUnmarshaller, } WEBHOOK_UNMARSHALLERS: Mapping[ SpecVersion, WebhookResponseUnmarshallerType ] = { versions.OPENAPIV31: V31WebhookResponseUnmarshaller, + versions.OPENAPIV32: V32WebhookResponseUnmarshaller, } # alias to the latest v3 version -V3ResponseUnmarshaller = V31ResponseUnmarshaller -V3WebhookResponseUnmarshaller = V31WebhookResponseUnmarshaller +V3ResponseUnmarshaller = V32ResponseUnmarshaller +V3WebhookResponseUnmarshaller = V32WebhookResponseUnmarshaller diff --git a/openapi_core/unmarshalling/response/protocols.py b/openapi_core/unmarshalling/response/protocols.py index de90c58d..fb3c494c 100644 --- a/openapi_core/unmarshalling/response/protocols.py +++ b/openapi_core/unmarshalling/response/protocols.py @@ -8,16 +8,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -44,8 +40,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -55,6 +55,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, @@ -75,8 +77,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -86,6 +92,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, diff --git a/openapi_core/unmarshalling/response/unmarshallers.py b/openapi_core/unmarshalling/response/unmarshallers.py index 4f02f5c7..36c2bed1 100644 --- a/openapi_core/unmarshalling/response/unmarshallers.py +++ b/openapi_core/unmarshalling/response/unmarshallers.py @@ -14,6 +14,9 @@ from openapi_core.unmarshalling.schemas import ( oas31_schema_unmarshallers_factory, ) +from openapi_core.unmarshalling.schemas import ( + oas32_schema_unmarshallers_factory, +) from openapi_core.unmarshalling.unmarshallers import BaseUnmarshaller from openapi_core.util import chainiters from openapi_core.validation.response.exceptions import DataValidationError @@ -45,6 +48,22 @@ from openapi_core.validation.response.validators import ( V31WebhookResponseValidator, ) +from openapi_core.validation.response.validators import ( + V32ResponseDataValidator, +) +from openapi_core.validation.response.validators import ( + V32ResponseHeadersValidator, +) +from openapi_core.validation.response.validators import V32ResponseValidator +from openapi_core.validation.response.validators import ( + V32WebhookResponseDataValidator, +) +from openapi_core.validation.response.validators import ( + V32WebhookResponseHeadersValidator, +) +from openapi_core.validation.response.validators import ( + V32WebhookResponseValidator, +) from openapi_core.validation.response.validators import ( WebhookResponseValidator, ) @@ -300,3 +319,39 @@ class V31WebhookResponseUnmarshaller( V31WebhookResponseValidator, WebhookResponseUnmarshaller ): schema_unmarshallers_factory = oas31_schema_unmarshallers_factory + + +class V32ResponseDataUnmarshaller( + V32ResponseDataValidator, APICallResponseDataUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32ResponseHeadersUnmarshaller( + V32ResponseHeadersValidator, APICallResponseHeadersUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32ResponseUnmarshaller( + V32ResponseValidator, APICallResponseUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookResponseDataUnmarshaller( + V32WebhookResponseDataValidator, WebhookResponseDataUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookResponseHeadersUnmarshaller( + V32WebhookResponseHeadersValidator, WebhookResponseHeadersUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory + + +class V32WebhookResponseUnmarshaller( + V32WebhookResponseValidator, WebhookResponseUnmarshaller +): + schema_unmarshallers_factory = oas32_schema_unmarshallers_factory diff --git a/openapi_core/unmarshalling/schemas/__init__.py b/openapi_core/unmarshalling/schemas/__init__.py index bb0aa65f..300d52ec 100644 --- a/openapi_core/unmarshalling/schemas/__init__.py +++ b/openapi_core/unmarshalling/schemas/__init__.py @@ -25,13 +25,16 @@ oas30_write_schema_validators_factory, ) from openapi_core.validation.schemas import oas31_schema_validators_factory +from openapi_core.validation.schemas import oas32_schema_validators_factory __all__ = [ "oas30_format_unmarshallers", "oas31_format_unmarshallers", + "oas32_format_unmarshallers", "oas30_write_schema_unmarshallers_factory", "oas30_read_schema_unmarshallers_factory", "oas31_schema_unmarshallers_factory", + "oas32_schema_unmarshallers_factory", ] oas30_unmarshallers_dict = OrderedDict( @@ -44,22 +47,28 @@ ("string", PrimitiveUnmarshaller), ] ) -oas31_unmarshallers_dict = oas30_unmarshallers_dict.copy() -oas31_unmarshallers_dict.update( - { - "null": PrimitiveUnmarshaller, - } +oas31_unmarshallers_dict = OrderedDict( + [ + ("object", ObjectUnmarshaller), + ("array", ArrayUnmarshaller), + ("boolean", PrimitiveUnmarshaller), + ("integer", PrimitiveUnmarshaller), + ("number", PrimitiveUnmarshaller), + ("string", PrimitiveUnmarshaller), + ("null", PrimitiveUnmarshaller), + ] ) oas30_types_unmarshaller = TypesUnmarshaller( - oas30_unmarshallers_dict, + dict(oas30_unmarshallers_dict), AnyUnmarshaller, ) oas31_types_unmarshaller = TypesUnmarshaller( - oas31_unmarshallers_dict, + dict(oas31_unmarshallers_dict), AnyUnmarshaller, multi=MultiTypeUnmarshaller, ) +oas32_types_unmarshaller = oas31_types_unmarshaller oas30_format_unmarshallers = { # string compatible @@ -69,7 +78,11 @@ "uuid": format_uuid, "byte": format_byte, } +# NOTE: Intentionally reuse OAS 3.0 format unmarshallers for OAS 3.1/3.2 +# to preserve backward compatibility for `byte`/`binary` formats. +# See https://github.com/python-openapi/openapi-core/issues/506 oas31_format_unmarshallers = oas30_format_unmarshallers +oas32_format_unmarshallers = oas31_format_unmarshallers oas30_write_schema_unmarshallers_factory = SchemaUnmarshallersFactory( oas30_write_schema_validators_factory, @@ -89,8 +102,20 @@ format_unmarshallers=oas31_format_unmarshallers, ) +oas32_schema_unmarshallers_factory = SchemaUnmarshallersFactory( + oas32_schema_validators_factory, + oas32_types_unmarshaller, + format_unmarshallers=oas32_format_unmarshallers, +) + # alias to v31 version (request/response are the same bcs no context needed) oas31_request_schema_unmarshallers_factory = oas31_schema_unmarshallers_factory oas31_response_schema_unmarshallers_factory = ( oas31_schema_unmarshallers_factory ) + +# alias to v32 version (request/response are the same bcs no context needed) +oas32_request_schema_unmarshallers_factory = oas32_schema_unmarshallers_factory +oas32_response_schema_unmarshallers_factory = ( + oas32_schema_unmarshallers_factory +) diff --git a/openapi_core/unmarshalling/schemas/factories.py b/openapi_core/unmarshalling/schemas/factories.py index 6472cab5..8bd3a061 100644 --- a/openapi_core/unmarshalling/schemas/factories.py +++ b/openapi_core/unmarshalling/schemas/factories.py @@ -33,28 +33,34 @@ def __init__( def create( self, + spec: SchemaPath, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, format_unmarshallers: Optional[FormatUnmarshallersDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, ) -> SchemaUnmarshaller: """Create unmarshaller from the schema.""" if schema is None: raise TypeError("Invalid schema") - if schema.getkey("deprecated", False): + if (schema / "deprecated").read_bool(default=False): warnings.warn("The schema is deprecated", DeprecationWarning) if extra_format_validators is None: extra_format_validators = {} schema_validator = self.schema_validators_factory.create( + spec, schema, format_validators=format_validators, extra_format_validators=extra_format_validators, + forbid_unspecified_additional_properties=forbid_unspecified_additional_properties, + enforce_properties_required=enforce_properties_required, ) - schema_format = schema.getkey("format") + schema_format = (schema / "format").read_str(None) formats_unmarshaller = FormatsUnmarshaller( format_unmarshallers or self.format_unmarshallers, diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 53ddcc34..0b8bd822 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -100,7 +100,7 @@ def _unmarshal_properties( except KeyError: if "default" not in prop_schema: continue - prop_value = prop_schema["default"] + prop_value = (prop_schema / "default").read_value() properties[prop_name] = self.schema_unmarshaller.evolve( prop_schema @@ -109,9 +109,7 @@ def _unmarshal_properties( if schema_only: return properties - additional_properties = self.schema.getkey( - "additionalProperties", True - ) + additional_properties = self.schema.get("additionalProperties", True) if additional_properties is not False: # free-form object if additional_properties is True: @@ -137,6 +135,9 @@ def _unmarshal_properties( class MultiTypeUnmarshaller(PrimitiveUnmarshaller): def __call__(self, value: Any) -> Any: primitive_type = self.schema_validator.get_primitive_type(value) + # OpenAPI 3.0: handle no type for None + if primitive_type is None: + return None unmarshaller = self.schema_unmarshaller.get_type_unmarshaller( primitive_type ) @@ -241,12 +242,17 @@ def unmarshal(self, value: Any) -> Any: self.schema_validator.validate(value) # skip unmarshalling for nullable in OpenAPI 3.0 - if value is None and self.schema.getkey("nullable", False): + if value is None and (self.schema / "nullable").read_bool( + default=False + ): return value - schema_type = self.schema.getkey("type") + schema_type = (self.schema / "type").read_str_or_list(None) type_unmarshaller = self.get_type_unmarshaller(schema_type) typed = type_unmarshaller(value) + # skip finding format for None + if typed is None: + return None schema_format = self.find_format(value) if schema_format is None: return typed @@ -301,5 +307,5 @@ def find_format(self, value: Any) -> Optional[str]: if primitive_type != "string": continue if "format" in schema: - return str(schema.getkey("format")) + return (schema / "format").read_str() return None diff --git a/openapi_core/unmarshalling/unmarshallers.py b/openapi_core/unmarshalling/unmarshallers.py index 984b9ea1..4fc7d501 100644 --- a/openapi_core/unmarshalling/unmarshallers.py +++ b/openapi_core/unmarshalling/unmarshallers.py @@ -7,16 +7,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -39,8 +35,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -50,6 +50,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, schema_unmarshallers_factory: Optional[ SchemaUnmarshallersFactory ] = None, @@ -73,6 +75,8 @@ def __init__( format_validators=format_validators, extra_format_validators=extra_format_validators, extra_media_type_deserializers=extra_media_type_deserializers, + forbid_unspecified_additional_properties=forbid_unspecified_additional_properties, + enforce_properties_required=enforce_properties_required, ) self.schema_unmarshallers_factory = ( schema_unmarshallers_factory or self.schema_unmarshallers_factory @@ -86,9 +90,12 @@ def __init__( def _unmarshal_schema(self, schema: SchemaPath, value: Any) -> Any: unmarshaller = self.schema_unmarshallers_factory.create( + self.spec, schema, format_validators=self.format_validators, extra_format_validators=self.extra_format_validators, + forbid_unspecified_additional_properties=self.forbid_unspecified_additional_properties, + enforce_properties_required=self.enforce_properties_required, format_unmarshallers=self.format_unmarshallers, extra_format_unmarshallers=self.extra_format_unmarshallers, ) diff --git a/openapi_core/util.py b/openapi_core/util.py index d8c5da16..029b3602 100644 --- a/openapi_core/util.py +++ b/openapi_core/util.py @@ -4,13 +4,16 @@ from typing import Any from typing import Iterable +BOOLEAN_TRUE_VALUES = ("y", "yes", "t", "true", "on", "1") +BOOLEAN_FALSE_VALUES = ("n", "no", "f", "false", "off", "0") + def forcebool(val: Any) -> bool: if isinstance(val, str): val = val.lower() - if val in ("y", "yes", "t", "true", "on", "1"): + if val in BOOLEAN_TRUE_VALUES: return True - elif val in ("n", "no", "f", "false", "off", "0"): + elif val in BOOLEAN_FALSE_VALUES: return False else: raise ValueError(f"invalid truth value {val!r}") diff --git a/openapi_core/validation/configurations.py b/openapi_core/validation/configurations.py index ebc32fc4..afdfbb1a 100644 --- a/openapi_core/validation/configurations.py +++ b/openapi_core/validation/configurations.py @@ -1,17 +1,14 @@ from dataclasses import dataclass +from typing import Literal from typing import Optional from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -47,18 +44,23 @@ class ValidatorConfig: Extra media type deserializers. security_provider_factory Security providers factory. + additional_properties_default_policy + If forbid, treat schemas that omit additionalProperties as if + additionalProperties: false. + response_properties_default_policy + If true, response schema properties are treated as required during + response validation/unmarshalling, except properties marked as + writeOnly. """ server_base_url: Optional[str] = None path_finder_cls: Optional[PathFinderType] = None webhook_path_finder_cls: Optional[PathFinderType] = None - style_deserializers_factory: StyleDeserializersFactory = ( - style_deserializers_factory - ) - media_type_deserializers_factory: MediaTypeDeserializersFactory = ( - media_type_deserializers_factory - ) + style_deserializers_factory: Optional[StyleDeserializersFactory] = None + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None schema_casters_factory: Optional[SchemaCastersFactory] = None schema_validators_factory: Optional[SchemaValidatorsFactory] = None @@ -68,3 +70,7 @@ class ValidatorConfig: security_provider_factory: SecurityProviderFactory = ( security_provider_factory ) + additional_properties_default_policy: Literal["allow", "forbid"] = "allow" + response_properties_default_policy: Literal["optional", "required"] = ( + "optional" + ) diff --git a/openapi_core/validation/exceptions.py b/openapi_core/validation/exceptions.py index 95b87cda..36cea031 100644 --- a/openapi_core/validation/exceptions.py +++ b/openapi_core/validation/exceptions.py @@ -1,11 +1,46 @@ """OpenAPI core validation exceptions module""" from dataclasses import dataclass +from typing import Any from openapi_core.exceptions import OpenAPIError +def _schema_error_to_dict(schema_error: Exception) -> dict[str, Any]: + message = getattr(schema_error, "message", str(schema_error)) + raw_path = getattr(schema_error, "path", ()) + try: + path = list(raw_path) + except TypeError: + path = [] + return { + "message": message, + "path": path, + } + + @dataclass class ValidationError(OpenAPIError): + @property + def details(self) -> dict[str, Any]: + cause = self.__cause__ + schema_errors: list[dict[str, Any]] = [] + if cause is not None: + cause_schema_errors = getattr(cause, "schema_errors", None) + if cause_schema_errors is not None: + schema_errors = [ + _schema_error_to_dict(schema_error) + for schema_error in cause_schema_errors + ] + + return { + "message": str(self), + "error_type": self.__class__.__name__, + "cause_type": ( + cause.__class__.__name__ if cause is not None else None + ), + "schema_errors": schema_errors, + } + def __str__(self) -> str: return f"{self.__class__.__name__}: {self.__cause__}" diff --git a/openapi_core/validation/request/__init__.py b/openapi_core/validation/request/__init__.py index fdde7767..0ede17eb 100644 --- a/openapi_core/validation/request/__init__.py +++ b/openapi_core/validation/request/__init__.py @@ -35,6 +35,26 @@ from openapi_core.validation.request.validators import ( V31WebhookRequestValidator, ) +from openapi_core.validation.request.validators import V32RequestBodyValidator +from openapi_core.validation.request.validators import ( + V32RequestParametersValidator, +) +from openapi_core.validation.request.validators import ( + V32RequestSecurityValidator, +) +from openapi_core.validation.request.validators import V32RequestValidator +from openapi_core.validation.request.validators import ( + V32WebhookRequestBodyValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestParametersValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestSecurityValidator, +) +from openapi_core.validation.request.validators import ( + V32WebhookRequestValidator, +) __all__ = [ "VALIDATORS", @@ -51,6 +71,14 @@ "V31WebhookRequestParametersValidator", "V31WebhookRequestSecurityValidator", "V31WebhookRequestValidator", + "V32RequestBodyValidator", + "V32RequestParametersValidator", + "V32RequestSecurityValidator", + "V32RequestValidator", + "V32WebhookRequestBodyValidator", + "V32WebhookRequestParametersValidator", + "V32WebhookRequestSecurityValidator", + "V32WebhookRequestValidator", "V3RequestValidator", "V3WebhookRequestValidator", ] @@ -59,11 +87,13 @@ VALIDATORS: Mapping[SpecVersion, RequestValidatorType] = { versions.OPENAPIV30: V30RequestValidator, versions.OPENAPIV31: V31RequestValidator, + versions.OPENAPIV32: V32RequestValidator, } WEBHOOK_VALIDATORS: Mapping[SpecVersion, WebhookRequestValidatorType] = { versions.OPENAPIV31: V31WebhookRequestValidator, + versions.OPENAPIV32: V32WebhookRequestValidator, } # alias to the latest v3 version -V3RequestValidator = V31RequestValidator -V3WebhookRequestValidator = V31WebhookRequestValidator +V3RequestValidator = V32RequestValidator +V3WebhookRequestValidator = V32WebhookRequestValidator diff --git a/openapi_core/validation/request/exceptions.py b/openapi_core/validation/request/exceptions.py index eb27a5c3..ae562125 100644 --- a/openapi_core/validation/request/exceptions.py +++ b/openapi_core/validation/request/exceptions.py @@ -21,6 +21,8 @@ class RequestValidationError(ValidationError): class RequestBodyValidationError(RequestValidationError): def __str__(self) -> str: + if self.__cause__ is not None: + return f"Request body validation error: {self.__cause__}" return "Request body validation error" @@ -49,7 +51,9 @@ class ParameterValidationError(RequestValidationError): @classmethod def from_spec(cls, spec: SchemaPath) -> "ParameterValidationError": - return cls(spec["name"], spec["in"]) + name = (spec / "name").read_str() + location = (spec / "in").read_str() + return cls(name, location) def __str__(self) -> str: return f"{self.location.title()} parameter error: {self.name}" diff --git a/openapi_core/validation/request/protocols.py b/openapi_core/validation/request/protocols.py index 983864e2..d57f68e4 100644 --- a/openapi_core/validation/request/protocols.py +++ b/openapi_core/validation/request/protocols.py @@ -9,16 +9,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -37,8 +33,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -49,6 +49,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, ): ... def iter_errors( @@ -68,8 +69,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -80,6 +85,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, ): ... def iter_errors( diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index f2e1ae95..ede517f1 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -9,23 +9,21 @@ from jsonschema_path import SchemaPath from openapi_spec_validator import OpenAPIV30SpecValidator from openapi_spec_validator import OpenAPIV31SpecValidator +from openapi_spec_validator import OpenAPIV32SpecValidator from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas import oas30_write_schema_casters_factory from openapi_core.casting.schemas import oas31_schema_casters_factory +from openapi_core.casting.schemas import oas32_schema_casters_factory from openapi_core.casting.schemas.factories import SchemaCastersFactory from openapi_core.datatypes import Parameters from openapi_core.datatypes import RequestParameters -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -59,6 +57,7 @@ oas30_write_schema_validators_factory, ) from openapi_core.validation.schemas import oas31_schema_validators_factory +from openapi_core.validation.schemas import oas32_schema_validators_factory from openapi_core.validation.schemas.datatypes import FormatValidatorsDict from openapi_core.validation.schemas.factories import SchemaValidatorsFactory from openapi_core.validation.validators import BaseAPICallValidator @@ -71,8 +70,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -83,6 +86,7 @@ def __init__( MediaTypeDeserializersDict ] = None, security_provider_factory: SecurityProviderFactory = security_provider_factory, + forbid_unspecified_additional_properties: bool = False, ): BaseValidator.__init__( @@ -98,6 +102,7 @@ def __init__( format_validators=format_validators, extra_format_validators=extra_format_validators, extra_media_type_deserializers=extra_media_type_deserializers, + forbid_unspecified_additional_properties=forbid_unspecified_additional_properties, ) self.security_provider_factory = security_provider_factory @@ -135,7 +140,7 @@ def _iter_parameters_errors( self, request: BaseRequest, operation: SchemaPath, path: SchemaPath ) -> Iterator[Exception]: try: - self._get_parameters(request.parameters, path, operation) + self._get_parameters(request.parameters, operation, path) except ParametersError as exc: yield from exc.errors @@ -153,16 +158,20 @@ def _get_parameters( operation: SchemaPath, path: SchemaPath, ) -> Parameters: - operation_params = operation.get("parameters", []) - path_params = path.get("parameters", []) + operation_params: SchemaPath = operation.get( + "parameters", SchemaPath.from_dict({}) + ) + path_params: SchemaPath = path.get( + "parameters", SchemaPath.from_dict({}) + ) errors = [] seen = set() validated = Parameters() params_iter = chainiters(operation_params, path_params) for param in params_iter: - param_name = param["name"] - param_location = param["in"] + param_name = (param / "name").read_str() + param_location = (param / "in").read_str() if (param_name, param_location) in seen: # skip parameter already seen # e.g. overriden path item paremeter on operation @@ -193,22 +202,20 @@ def _get_parameters( def _get_parameter( self, parameters: RequestParameters, param: SchemaPath ) -> Any: - name = param["name"] - deprecated = param.getkey("deprecated", False) - if deprecated: + name = (param / "name").read_str() + param_location = (param / "in").read_str() + location = parameters[param_location] + deprecated = (param / "deprecated").read_bool(default=False) + if deprecated and name in location: warnings.warn( f"{name} parameter is deprecated", DeprecationWarning, ) - param_location = param["in"] - location = parameters[param_location] - try: value, _ = self._get_param_or_header_and_schema(param, location) except KeyError: - required = param.getkey("required", False) - if required: + if (param / "required").read_bool(default=False): raise MissingRequiredParameter(name, param_location) raise MissingParameter(name, param_location) else: @@ -230,7 +237,7 @@ def _get_security( schemes = [] for security_requirement in security: try: - scheme_names = list(security_requirement.keys()) + scheme_names = list(security_requirement.str_keys()) schemes.append(scheme_names) return { scheme_name: self._get_security_value( @@ -272,7 +279,7 @@ def _get_body_value( self, body: Optional[bytes], request_body: SchemaPath ) -> bytes: if not body: - if request_body.getkey("required", False): + if (request_body / "required").read_bool(default=False): raise MissingRequiredRequestBody raise MissingRequestBody return body @@ -472,3 +479,51 @@ class V31WebhookRequestValidator(WebhookRequestValidator): spec_validator_cls = OpenAPIV31SpecValidator schema_casters_factory = oas31_schema_casters_factory schema_validators_factory = oas31_schema_validators_factory + + +class V32RequestBodyValidator(APICallRequestBodyValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32RequestParametersValidator(APICallRequestParametersValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32RequestSecurityValidator(APICallRequestSecurityValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32RequestValidator(APICallRequestValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookRequestBodyValidator(WebhookRequestBodyValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookRequestParametersValidator(WebhookRequestParametersValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookRequestSecurityValidator(WebhookRequestSecurityValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookRequestValidator(WebhookRequestValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory diff --git a/openapi_core/validation/response/__init__.py b/openapi_core/validation/response/__init__.py index 94694360..986fbe1d 100644 --- a/openapi_core/validation/response/__init__.py +++ b/openapi_core/validation/response/__init__.py @@ -30,6 +30,22 @@ from openapi_core.validation.response.validators import ( V31WebhookResponseValidator, ) +from openapi_core.validation.response.validators import ( + V32ResponseDataValidator, +) +from openapi_core.validation.response.validators import ( + V32ResponseHeadersValidator, +) +from openapi_core.validation.response.validators import V32ResponseValidator +from openapi_core.validation.response.validators import ( + V32WebhookResponseDataValidator, +) +from openapi_core.validation.response.validators import ( + V32WebhookResponseHeadersValidator, +) +from openapi_core.validation.response.validators import ( + V32WebhookResponseValidator, +) __all__ = [ "VALIDATORS", @@ -43,6 +59,12 @@ "V31WebhookResponseDataValidator", "V31WebhookResponseHeadersValidator", "V31WebhookResponseValidator", + "V32ResponseDataValidator", + "V32ResponseHeadersValidator", + "V32ResponseValidator", + "V32WebhookResponseDataValidator", + "V32WebhookResponseHeadersValidator", + "V32WebhookResponseValidator", "V3ResponseValidator", "V3WebhookResponseValidator", ] @@ -51,11 +73,13 @@ VALIDATORS: Mapping[SpecVersion, ResponseValidatorType] = { versions.OPENAPIV30: V30ResponseValidator, versions.OPENAPIV31: V31ResponseValidator, + versions.OPENAPIV32: V32ResponseValidator, } WEBHOOK_VALIDATORS: Mapping[SpecVersion, WebhookResponseValidatorType] = { versions.OPENAPIV31: V31WebhookResponseValidator, + versions.OPENAPIV32: V32WebhookResponseValidator, } # alias to the latest v3 version -V3ResponseValidator = V31ResponseValidator -V3WebhookResponseValidator = V31WebhookResponseValidator +V3ResponseValidator = V32ResponseValidator +V3WebhookResponseValidator = V32WebhookResponseValidator diff --git a/openapi_core/validation/response/protocols.py b/openapi_core/validation/response/protocols.py index f0f33dc6..fb3ea64c 100644 --- a/openapi_core/validation/response/protocols.py +++ b/openapi_core/validation/response/protocols.py @@ -9,16 +9,12 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) @@ -36,8 +32,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -47,6 +47,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, ): ... def iter_errors( @@ -68,8 +70,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -79,6 +85,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, ): ... def iter_errors( diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index cd4d4350..8ffefc48 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -11,10 +11,13 @@ from jsonschema_path import SchemaPath from openapi_spec_validator import OpenAPIV30SpecValidator from openapi_spec_validator import OpenAPIV31SpecValidator +from openapi_spec_validator import OpenAPIV32SpecValidator from openapi_core.casting.schemas import oas30_read_schema_casters_factory from openapi_core.casting.schemas import oas31_schema_casters_factory +from openapi_core.casting.schemas import oas32_schema_casters_factory from openapi_core.exceptions import OpenAPIError +from openapi_core.protocols import HeadersType from openapi_core.protocols import Request from openapi_core.protocols import Response from openapi_core.protocols import WebhookRequest @@ -34,6 +37,7 @@ oas30_read_schema_validators_factory, ) from openapi_core.validation.schemas import oas31_schema_validators_factory +from openapi_core.validation.schemas import oas32_schema_validators_factory from openapi_core.validation.validators import BaseAPICallValidator from openapi_core.validation.validators import BaseValidator from openapi_core.validation.validators import BaseWebhookValidator @@ -44,7 +48,7 @@ def _iter_errors( self, status_code: int, data: Optional[bytes], - headers: Mapping[str, Any], + headers: HeadersType, mimetype: str, operation: SchemaPath, ) -> Iterator[Exception]: @@ -91,7 +95,7 @@ def _iter_data_errors( def _iter_headers_errors( self, status_code: int, - headers: Mapping[str, Any], + headers: HeadersType, operation: SchemaPath, ) -> Iterator[Exception]: try: @@ -141,16 +145,14 @@ def _get_data_value(self, data: Optional[bytes]) -> bytes: return data def _get_headers( - self, headers: Mapping[str, Any], operation_response: SchemaPath + self, headers: HeadersType, operation_response: SchemaPath ) -> Dict[str, Any]: if "headers" not in operation_response: return {} - response_headers = operation_response / "headers" - errors: List[OpenAPIError] = [] validated: Dict[str, Any] = {} - for name, header in list(response_headers.items()): + for name, header in (operation_response / "headers").str_items(): # ignore Content-Type header if name.lower() == "content-type": continue @@ -173,8 +175,8 @@ def _get_headers( def _get_header( self, headers: Mapping[str, Any], name: str, header: SchemaPath ) -> Any: - deprecated = header.getkey("deprecated", False) - if deprecated: + deprecated = (header / "deprecated").read_bool(default=False) + if deprecated and name in headers: warnings.warn( f"{name} header is deprecated", DeprecationWarning, @@ -185,7 +187,7 @@ def _get_header( header, headers, name=name ) except KeyError: - required = header.getkey("required", False) + required = (header / "required").read_bool(default=False) if required: raise MissingRequiredHeader(name) raise MissingHeader(name) @@ -405,3 +407,39 @@ class V31WebhookResponseValidator(WebhookResponseValidator): spec_validator_cls = OpenAPIV31SpecValidator schema_casters_factory = oas31_schema_casters_factory schema_validators_factory = oas31_schema_validators_factory + + +class V32ResponseDataValidator(APICallResponseDataValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32ResponseHeadersValidator(APICallResponseHeadersValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32ResponseValidator(APICallResponseValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookResponseDataValidator(WebhookResponseDataValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookResponseHeadersValidator(WebhookResponseHeadersValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory + + +class V32WebhookResponseValidator(WebhookResponseValidator): + spec_validator_cls = OpenAPIV32SpecValidator + schema_casters_factory = oas32_schema_casters_factory + schema_validators_factory = oas32_schema_validators_factory diff --git a/openapi_core/validation/schemas/__init__.py b/openapi_core/validation/schemas/__init__.py index 4fdf466b..2d57bd97 100644 --- a/openapi_core/validation/schemas/__init__.py +++ b/openapi_core/validation/schemas/__init__.py @@ -1,13 +1,20 @@ +from openapi_schema_validator import OAS31_BASE_DIALECT_ID +from openapi_schema_validator import OAS32_BASE_DIALECT_ID from openapi_schema_validator import OAS30ReadValidator from openapi_schema_validator import OAS30WriteValidator from openapi_schema_validator import OAS31Validator +from openapi_schema_validator import OAS32Validator +from openapi_core.validation.schemas.factories import ( + DialectSchemaValidatorsFactory, +) from openapi_core.validation.schemas.factories import SchemaValidatorsFactory __all__ = [ "oas30_write_schema_validators_factory", "oas30_read_schema_validators_factory", "oas31_schema_validators_factory", + "oas32_schema_validators_factory", ] oas30_write_schema_validators_factory = SchemaValidatorsFactory( @@ -18,9 +25,16 @@ OAS30ReadValidator, ) -oas31_schema_validators_factory = SchemaValidatorsFactory( +oas31_schema_validators_factory = DialectSchemaValidatorsFactory( OAS31Validator, - # FIXME: OpenAPI 3.1 schema validator uses OpenAPI 3.0 format checker. + OAS31_BASE_DIALECT_ID, + # NOTE: Intentionally use OAS 3.0 format checker for OAS 3.1 to preserve + # backward compatibility for `byte`/`binary` formats. # See https://github.com/python-openapi/openapi-core/issues/506 format_checker=OAS30ReadValidator.FORMAT_CHECKER, ) + +oas32_schema_validators_factory = DialectSchemaValidatorsFactory( + OAS32Validator, + OAS32_BASE_DIALECT_ID, +) diff --git a/openapi_core/validation/schemas/_validators.py b/openapi_core/validation/schemas/_validators.py new file mode 100644 index 00000000..f1fe8fc0 --- /dev/null +++ b/openapi_core/validation/schemas/_validators.py @@ -0,0 +1,122 @@ +from typing import Any +from typing import Iterator +from typing import Mapping +from typing import cast + +from jsonschema._utils import extras_msg +from jsonschema._utils import find_additional_properties +from jsonschema.exceptions import ValidationError +from jsonschema.protocols import Validator +from jsonschema.validators import extend + + +def build_forbid_unspecified_additional_properties_validator( + validator_class: type[Validator], +) -> type[Validator]: + properties_validator = validator_class.VALIDATORS.get("properties") + type_validator = validator_class.VALIDATORS.get("type") + + def strict_properties( + validator: Any, + properties: Any, + instance: Any, + schema: Mapping[str, Any], + ) -> Iterator[Any]: + if properties_validator is not None: + yield from properties_validator( + validator, properties, instance, schema + ) + yield from iter_missing_additional_properties_errors( + validator, instance, schema + ) + + def strict_type( + validator: Any, + data_type: Any, + instance: Any, + schema: Mapping[str, Any], + ) -> Iterator[Any]: + if type_validator is not None: + yield from type_validator(validator, data_type, instance, schema) + + schema_types = data_type + if isinstance(schema_types, str): + schema_types = [schema_types] + if not isinstance(schema_types, list): + return + if "object" not in schema_types: + return + if "additionalProperties" in schema or "properties" in schema: + return + + yield from iter_missing_additional_properties_errors( + validator, instance, schema + ) + + return cast( + type[Validator], + extend( + validator_class, + validators={ + "properties": strict_properties, + "type": strict_type, + }, + ), + ) + + +def iter_missing_additional_properties_errors( + validator: Any, + instance: Any, + schema: Mapping[str, Any], +) -> Iterator[ValidationError]: + if not validator.is_type(instance, "object"): + return + + if "additionalProperties" in schema: + return + + extras = sorted(set(find_additional_properties(instance, schema))) + + if extras: + error = "Additional properties are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(extras)) + + +def build_enforce_properties_required_validator( + validator_class: type[Validator], +) -> type[Validator]: + properties_validator = validator_class.VALIDATORS.get("properties") + + def enforce_properties( + validator: Any, + properties: Any, + instance: Any, + schema: Mapping[str, Any], + ) -> Iterator[Any]: + if properties_validator is not None: + yield from properties_validator( + validator, properties, instance, schema + ) + + if not validator.is_type(instance, "object"): + return + + for prop_name, prop_schema in properties.items(): + if prop_name not in instance: + if ( + isinstance(prop_schema, dict) + and prop_schema.get("writeOnly") is True + ): + continue + yield ValidationError(f"'{prop_name}' is a required property") + + return cast( + type[Validator], + extend( + validator_class, + validators={ + "properties": enforce_properties, + }, + ), + ) diff --git a/openapi_core/validation/schemas/exceptions.py b/openapi_core/validation/schemas/exceptions.py index 437a273c..b3324169 100644 --- a/openapi_core/validation/schemas/exceptions.py +++ b/openapi_core/validation/schemas/exceptions.py @@ -14,7 +14,7 @@ class InvalidSchemaValue(ValidateError): """Value not valid for schema""" value: str - type: str + type: str | list[str] schema_errors: Iterable[Exception] = field(default_factory=list) def __str__(self) -> str: diff --git a/openapi_core/validation/schemas/factories.py b/openapi_core/validation/schemas/factories.py index a71d5139..49020bdc 100644 --- a/openapi_core/validation/schemas/factories.py +++ b/openapi_core/validation/schemas/factories.py @@ -1,11 +1,20 @@ from copy import deepcopy +from functools import lru_cache +from typing import Any from typing import Optional -from typing import Type +from typing import cast from jsonschema._format import FormatChecker from jsonschema.protocols import Validator +from jsonschema.validators import validator_for from jsonschema_path import SchemaPath +from openapi_core.validation.schemas._validators import ( + build_enforce_properties_required_validator, +) +from openapi_core.validation.schemas._validators import ( + build_forbid_unspecified_additional_properties_validator, +) from openapi_core.validation.schemas.datatypes import FormatValidatorsDict from openapi_core.validation.schemas.validators import SchemaValidator @@ -13,28 +22,35 @@ class SchemaValidatorsFactory: def __init__( self, - schema_validator_class: Type[Validator], + schema_validator_cls: type[Validator], format_checker: Optional[FormatChecker] = None, ): - self.schema_validator_class = schema_validator_class + self.schema_validator_cls = schema_validator_cls if format_checker is None: - format_checker = self.schema_validator_class.FORMAT_CHECKER + format_checker = self.schema_validator_cls.FORMAT_CHECKER + assert format_checker is not None self.format_checker = format_checker + def get_validator_cls( + self, spec: SchemaPath, schema: SchemaPath + ) -> type[Validator]: + return self.schema_validator_cls + def get_format_checker( self, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, ) -> FormatChecker: + format_checker: FormatChecker if format_validators is None: - format_checker = deepcopy(self.format_checker) + format_checker = deepcopy(cast(FormatChecker, self.format_checker)) else: format_checker = FormatChecker([]) format_checker = self._add_validators( - format_checker, format_validators + cast(FormatChecker, format_checker), format_validators ) format_checker = self._add_validators( - format_checker, extra_format_validators + cast(FormatChecker, format_checker), extra_format_validators ) return format_checker @@ -50,19 +66,82 @@ def _add_validators( def create( self, + spec: SchemaPath, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, - ) -> Validator: + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, + ) -> SchemaValidator: + validator_cls: type[Validator] = self.get_validator_cls(spec, schema) + if enforce_properties_required: + validator_cls = build_enforce_properties_required_validator( + validator_cls + ) + if forbid_unspecified_additional_properties: + validator_cls = ( + build_forbid_unspecified_additional_properties_validator( + validator_cls + ) + ) + format_checker = self.get_format_checker( format_validators, extra_format_validators ) - resolver = schema.accessor.resolver # type: ignore - with schema.open() as schema_dict: - jsonschema_validator = self.schema_validator_class( - schema_dict, - _resolver=resolver, + with schema.resolve() as resolved: + jsonschema_validator = validator_cls( + resolved.contents, + _resolver=resolved.resolver, format_checker=format_checker, ) return SchemaValidator(schema, jsonschema_validator) + + +class DialectSchemaValidatorsFactory(SchemaValidatorsFactory): + def __init__( + self, + schema_validator_cls: type[Validator], + default_jsonschema_dialect_id: str, + format_checker: Optional[FormatChecker] = None, + ): + super().__init__(schema_validator_cls, format_checker) + self.default_jsonschema_dialect_id = default_jsonschema_dialect_id + + def get_validator_cls( + self, spec: SchemaPath, schema: SchemaPath + ) -> type[Validator]: + dialect_id = self._get_dialect_id(spec, schema) + + validator_cls = self._get_validator_class_for_dialect(dialect_id) + if validator_cls is None: + raise ValueError(f"Unknown JSON Schema dialect: {dialect_id!r}") + + return validator_cls + + def _get_dialect_id( + self, + spec: SchemaPath, + schema: SchemaPath, + ) -> str: + try: + return (schema / "$schema").read_str() + except KeyError: + return self._get_default_jsonschema_dialect_id(spec) + + def _get_default_jsonschema_dialect_id(self, spec: SchemaPath) -> str: + return (spec / "jsonSchemaDialect").read_str( + default=self.default_jsonschema_dialect_id + ) + + @lru_cache + def _get_validator_class_for_dialect( + self, dialect_id: str + ) -> type[Validator] | None: + return cast( + type[Validator] | None, + validator_for( + {"$schema": dialect_id}, + default=cast(Any, None), + ), + ) diff --git a/openapi_core/validation/schemas/validators.py b/openapi_core/validation/schemas/validators.py index c822a82f..a82e1c60 100644 --- a/openapi_core/validation/schemas/validators.py +++ b/openapi_core/validation/schemas/validators.py @@ -1,6 +1,7 @@ import logging from functools import cached_property from functools import partial +from typing import TYPE_CHECKING from typing import Any from typing import Iterator from typing import Optional @@ -13,6 +14,9 @@ from openapi_core.validation.schemas.exceptions import InvalidSchemaValue from openapi_core.validation.schemas.exceptions import ValidateError +if TYPE_CHECKING: + from openapi_core.casting.schemas.casters import SchemaCaster + log = logging.getLogger(__name__) @@ -32,14 +36,17 @@ def validate(self, value: Any) -> None: errors_iter = self.validator.iter_errors(value) errors = tuple(errors_iter) if errors: - schema_type = self.schema.getkey("type", "any") + schema_type = (self.schema / "type").read_str_or_list("any") raise InvalidSchemaValue(value, schema_type, schema_errors=errors) def evolve(self, schema: SchemaPath) -> "SchemaValidator": cls = self.__class__ - with schema.open() as schema_dict: - return cls(schema, self.validator.evolve(schema=schema_dict)) + with schema.resolve() as resolved: + validator = self.validator.evolve( + schema=resolved.contents, _resolver=resolved.resolver + ) + return cls(schema, validator) def type_validator( self, value: Any, type_override: Optional[str] = None @@ -60,7 +67,7 @@ def format_validator(self, value: Any) -> bool: def get_type_validator_callable( self, type_override: Optional[str] = None ) -> FormatValidator: - schema_type = type_override or self.schema.getkey("type") + schema_type = type_override or (self.schema / "type").read_str(None) if schema_type in self.validator.TYPE_CHECKER._type_checkers: return partial( self.validator.TYPE_CHECKER.is_type, type=schema_type @@ -70,7 +77,7 @@ def get_type_validator_callable( @cached_property def format_validator_callable(self) -> FormatValidator: - schema_format = self.schema.getkey("format") + schema_format = (self.schema / "format").read_str(None) if schema_format in self.validator.format_checker.checkers: return partial( self.validator.format_checker.check, format=schema_format @@ -79,7 +86,7 @@ def format_validator_callable(self) -> FormatValidator: return lambda x: True def get_primitive_type(self, value: Any) -> Optional[str]: - schema_types = self.schema.getkey("type") + schema_types = (self.schema / "type").read_str_or_list(None) if isinstance(schema_types, str): return schema_types if schema_types is None: @@ -94,6 +101,7 @@ def get_primitive_type(self, value: Any) -> Optional[str]: continue assert isinstance(schema_type, (str, type(None))) return schema_type + # OpenAPI 3.0: None is not a primitive type so None value will not find any type return None def iter_valid_schemas(self, value: Any) -> Iterator[SchemaPath]: @@ -109,7 +117,15 @@ def iter_valid_schemas(self, value: Any) -> Iterator[SchemaPath]: def get_one_of_schema( self, value: Any, + caster: Optional["SchemaCaster"] = None, ) -> Optional[SchemaPath]: + """Find the matching oneOf schema. + + Args: + value: The value to match against schemas + caster: Optional caster for type coercion during matching. + Useful for form-encoded data where types need casting. + """ if "oneOf" not in self.schema: return None @@ -117,7 +133,23 @@ def get_one_of_schema( for subschema in one_of_schemas: validator = self.evolve(subschema) try: - validator.validate(value) + test_value = value + # Only cast if caster provided (opt-in behavior) + if caster is not None: + try: + # Convert to dict if it's not exactly a plain dict + # (e.g., ImmutableMultiDict from werkzeug) + if type(value) is not dict: + test_value = dict(value) + else: + test_value = value + test_value = caster.evolve(subschema).cast(test_value) + except (ValueError, TypeError, Exception): + # If casting fails, try validation with original value + # We catch generic Exception to handle CastError without circular import + test_value = value + + validator.validate(test_value) except ValidateError: continue else: @@ -129,7 +161,15 @@ def get_one_of_schema( def iter_any_of_schemas( self, value: Any, + caster: Optional["SchemaCaster"] = None, ) -> Iterator[SchemaPath]: + """Iterate matching anyOf schemas. + + Args: + value: The value to match against schemas + caster: Optional caster for type coercion during matching. + Useful for form-encoded data where types need casting. + """ if "anyOf" not in self.schema: return @@ -137,7 +177,22 @@ def iter_any_of_schemas( for subschema in any_of_schemas: validator = self.evolve(subschema) try: - validator.validate(value) + test_value = value + # Only cast if caster provided (opt-in behavior) + if caster is not None: + try: + # Convert to dict if it's not exactly a plain dict + if type(value) is not dict: + test_value = dict(value) + else: + test_value = value + test_value = caster.evolve(subschema).cast(test_value) + except (ValueError, TypeError, Exception): + # If casting fails, try validation with original value + # We catch generic Exception to handle CastError without circular import + test_value = value + + validator.validate(test_value) except ValidateError: continue else: diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index 09275368..116a3735 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -12,16 +12,14 @@ from openapi_spec_validator.validation.types import SpecValidatorType from openapi_core.casting.schemas.factories import SchemaCastersFactory -from openapi_core.deserializing.media_types import ( - media_type_deserializers_factory, -) +from openapi_core.deserializing.media_types import media_type_deserializers from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory +from openapi_core.deserializing.styles import style_deserializers from openapi_core.deserializing.styles.exceptions import ( EmptyQueryParameterValue, ) @@ -51,8 +49,12 @@ def __init__( self, spec: SchemaPath, base_url: Optional[str] = None, - style_deserializers_factory: StyleDeserializersFactory = style_deserializers_factory, - media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + style_deserializers_factory: Optional[ + StyleDeserializersFactory + ] = None, + media_type_deserializers_factory: Optional[ + MediaTypeDeserializersFactory + ] = None, schema_casters_factory: Optional[SchemaCastersFactory] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, path_finder_cls: Optional[PathFinderType] = None, @@ -62,6 +64,8 @@ def __init__( extra_media_type_deserializers: Optional[ MediaTypeDeserializersDict ] = None, + forbid_unspecified_additional_properties: bool = False, + enforce_properties_required: bool = False, ): self.spec = spec self.base_url = base_url @@ -71,9 +75,19 @@ def __init__( ) if self.schema_casters_factory is NotImplemented: raise NotImplementedError("schema_casters_factory is not assigned") - self.style_deserializers_factory = style_deserializers_factory + self.style_deserializers_factory = ( + style_deserializers_factory + or StyleDeserializersFactory( + self.schema_casters_factory, + style_deserializers=style_deserializers, + ) + ) self.media_type_deserializers_factory = ( media_type_deserializers_factory + or MediaTypeDeserializersFactory( + self.style_deserializers_factory, + media_type_deserializers=media_type_deserializers, + ) ) self.schema_validators_factory = ( schema_validators_factory or self.schema_validators_factory @@ -83,12 +97,16 @@ def __init__( "schema_validators_factory is not assigned" ) self.path_finder_cls = path_finder_cls or self.path_finder_cls - if self.path_finder_cls is NotImplemented: # type: ignore[comparison-overlap] + if self.path_finder_cls is NotImplemented: raise NotImplementedError("path_finder_cls is not assigned") self.spec_validator_cls = spec_validator_cls or self.spec_validator_cls self.format_validators = format_validators self.extra_format_validators = extra_format_validators self.extra_media_type_deserializers = extra_media_type_deserializers + self.forbid_unspecified_additional_properties = ( + forbid_unspecified_additional_properties + ) + self.enforce_properties_required = enforce_properties_required @cached_property def path_finder(self) -> BasePathFinder: @@ -122,9 +140,21 @@ def _deserialise_media_type( encoding = None if "encoding" in media_type: encoding = media_type.get("encoding") + schema_validator = None + if schema is not None: + schema_validator = self.schema_validators_factory.create( + self.spec, + schema, + format_validators=self.format_validators, + extra_format_validators=self.extra_format_validators, + forbid_unspecified_additional_properties=self.forbid_unspecified_additional_properties, + enforce_properties_required=self.enforce_properties_required, + ) deserializer = self.media_type_deserializers_factory.create( + self.spec, mimetype, schema=schema, + schema_validator=schema_validator, parameters=parameters, encoding=encoding, extra_media_type_deserializers=self.extra_media_type_deserializers, @@ -137,23 +167,22 @@ def _deserialise_style( location: Mapping[str, Any], name: Optional[str] = None, ) -> Any: - name = name or param_or_header["name"] + name = name or (param_or_header / "name").read_str() style, explode = get_style_and_explode(param_or_header) schema = param_or_header / "schema" deserializer = self.style_deserializers_factory.create( - style, explode, schema, name=name + self.spec, schema, style, explode, name=name ) return deserializer.deserialize(location) - def _cast(self, schema: SchemaPath, value: Any) -> Any: - caster = self.schema_casters_factory.create(schema) - return caster.cast(value) - def _validate_schema(self, schema: SchemaPath, value: Any) -> None: validator = self.schema_validators_factory.create( + self.spec, schema, format_validators=self.format_validators, extra_format_validators=self.extra_format_validators, + forbid_unspecified_additional_properties=self.forbid_unspecified_additional_properties, + enforce_properties_required=self.enforce_properties_required, ) validator.validate(value) @@ -186,7 +215,9 @@ def _get_simple_param_or_header( location: Mapping[str, Any], name: Optional[str] = None, ) -> Tuple[Any, SchemaPath]: - allow_empty_values = param_or_header.getkey("allowEmptyValue") + allow_empty_values = (param_or_header / "allowEmptyValue").read_bool( + default=None + ) if allow_empty_values: warnings.warn( "Use of allowEmptyValue property is deprecated", @@ -201,7 +232,7 @@ def _get_simple_param_or_header( except KeyError: if "default" not in schema: raise - return schema["default"], schema + return (schema / "default").read_value(), schema if allow_empty_values is not None: warnings.warn( "Use of allowEmptyValue property is deprecated", @@ -209,16 +240,15 @@ def _get_simple_param_or_header( ) if allow_empty_values is None or not allow_empty_values: # if "in" not defined then it's a Header - location_name = param_or_header.getkey("in", "header") + location_name = (param_or_header / "in").read_str("header") if ( location_name == "query" and deserialised == "" and not allow_empty_values ): - param_or_header_name = param_or_header["name"] + param_or_header_name = (param_or_header / "name").read_str() raise EmptyQueryParameterValue(param_or_header_name) - casted = self._cast(schema, deserialised) - return casted, schema + return deserialised, schema def _get_complex_param_or_header( self, @@ -249,22 +279,18 @@ def _get_content_schema_value_and_schema( return deserialised, None schema = media_type / "schema" - # cast for urlencoded content - # FIXME: don't cast data from media type deserializer - # See https://github.com/python-openapi/openapi-core/issues/706 - casted = self._cast(schema, deserialised) - return casted, schema + return deserialised, schema def _get_content_and_schema( self, raw: bytes, content: SchemaPath, mimetype: Optional[str] = None ) -> Tuple[Any, Optional[SchemaPath]]: - casted, schema = self._get_content_schema_value_and_schema( + deserialised, schema = self._get_content_schema_value_and_schema( raw, content, mimetype ) if schema is None: - return casted, None - self._validate_schema(schema, casted) - return casted, schema + return deserialised, None + self._validate_schema(schema, deserialised) + return deserialised, schema def _get_media_type_value( self, @@ -272,7 +298,7 @@ def _get_media_type_value( location: Mapping[str, Any], name: Optional[str] = None, ) -> Any: - name = name or param_or_header["name"] + name = name or (param_or_header / "name").read_str() return location[name] diff --git a/poetry.lock b/poetry.lock index 50ae97f2..d9f4d4d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,164 +1,229 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] +markers = {main = "extra == \"aiohttp\""} [[package]] name = "aiohttp" -version = "3.9.5" +version = "3.13.4" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} + {file = "aiohttp-3.13.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6290fe12fe8cefa6ea3c1c5b969d32c010dfe191d4392ff9b599a3f473cbe722"}, + {file = "aiohttp-3.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7520d92c0e8fbbe63f36f20a5762db349ff574ad38ad7bc7732558a650439845"}, + {file = "aiohttp-3.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2710ae1e1b81d0f187883b6e9d66cecf8794b50e91aa1e73fc78bfb5503b5d9"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:717d17347567ded1e273aa09918650dfd6fd06f461549204570c7973537d4123"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:383880f7b8de5ac208fa829c7038d08e66377283b2de9e791b71e06e803153c2"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1867087e2c1963db1216aedf001efe3b129835ed2b05d97d058176a6d08b5726"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6234bf416a38d687c3ab7f79934d7fb2a42117a5b9813aca07de0a5398489023"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdd3393130bf6588962441ffd5bde1d3ea2d63a64afa7119b3f3ba349cebbe7"}, + {file = "aiohttp-3.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d0dbc6c76befa76865373d6aa303e480bb8c3486e7763530f7f6e527b471118"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10fb7b53262cf4144a083c9db0d2b4d22823d6708270a9970c4627b248c6064c"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:eb10ce8c03850e77f4d9518961c227be569e12f71525a7e90d17bca04299921d"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7c65738ac5ae32b8feef699a4ed0dc91a0c8618b347781b7461458bbcaaac7eb"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6b335919ffbaf98df8ff3c74f7a6decb8775882632952fd1810a017e38f15aee"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ec75fc18cb9f4aca51c2cbace20cf6716e36850f44189644d2d69a875d5e0532"}, + {file = "aiohttp-3.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:463fa18a95c5a635d2b8c09babe240f9d7dbf2a2010a6c0b35d8c4dff2a0e819"}, + {file = "aiohttp-3.13.4-cp310-cp310-win32.whl", hash = "sha256:13168f5645d9045522c6cef818f54295376257ed8d02513a37c2ef3046fc7a97"}, + {file = "aiohttp-3.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:a7058af1f53209fdf07745579ced525d38d481650a989b7aa4a3b484b901cdab"}, + {file = "aiohttp-3.13.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ea0c64d1bcbf201b285c2246c51a0c035ba3bbd306640007bc5844a3b4658c1"}, + {file = "aiohttp-3.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f742e1fa45c0ed522b00ede565e18f97e4cf8d1883a712ac42d0339dfb0cce7"}, + {file = "aiohttp-3.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dcfb50ee25b3b7a1222a9123be1f9f89e56e67636b561441f0b304e25aaef8f"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3262386c4ff370849863ea93b9ea60fd59c6cf56bf8f93beac625cf4d677c04d"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:473bb5aa4218dd254e9ae4834f20e31f5a0083064ac0136a01a62ddbae2eaa42"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56423766399b4c77b965f6aaab6c9546617b8994a956821cc507d00b91d978c"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8af249343fafd5ad90366a16d230fc265cf1149f26075dc9fe93cfd7c7173942"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc0a5cf4f10ef5a2c94fdde488734b582a3a7a000b131263e27c9295bd682d9"}, + {file = "aiohttp-3.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c7ff1028e3c9fc5123a865ce17df1cb6424d180c503b8517afbe89aa566e6be"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ba5cf98b5dcb9bddd857da6713a503fa6d341043258ca823f0f5ab7ab4a94ee8"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d85965d3ba21ee4999e83e992fecb86c4614d6920e40705501c0a1f80a583c12"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:49f0b18a9b05d79f6f37ddd567695943fcefb834ef480f17a4211987302b2dc7"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7f78cb080c86fbf765920e5f1ef35af3f24ec4314d6675d0a21eaf41f6f2679c"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:67a3ec705534a614b68bbf1c70efa777a21c3da3895d1c44510a41f5a7ae0453"}, + {file = "aiohttp-3.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6630ec917e85c5356b2295744c8a97d40f007f96a1c76bf1928dc2e27465393"}, + {file = "aiohttp-3.13.4-cp311-cp311-win32.whl", hash = "sha256:54049021bc626f53a5394c29e8c444f726ee5a14b6e89e0ad118315b1f90f5e3"}, + {file = "aiohttp-3.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:c033f2bc964156030772d31cbf7e5defea181238ce1f87b9455b786de7d30145"}, + {file = "aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360"}, + {file = "aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d"}, + {file = "aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e"}, + {file = "aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9"}, + {file = "aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d"}, + {file = "aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791"}, + {file = "aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77"}, + {file = "aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538"}, + {file = "aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e"}, + {file = "aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a"}, + {file = "aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069"}, + {file = "aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5"}, + {file = "aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70"}, + {file = "aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3"}, + {file = "aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57"}, + {file = "aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933"}, + {file = "aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed"}, + {file = "aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb"}, + {file = "aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165"}, + {file = "aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9"}, + {file = "aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8"}, + {file = "aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1"}, + {file = "aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c"}, + {file = "aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27"}, + {file = "aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b"}, + {file = "aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba"}, + {file = "aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30"}, + {file = "aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144"}, + {file = "aiohttp-3.13.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3f00bb9403728b08eb3951e982ca0a409c7a871d709684623daeab79465b181"}, + {file = "aiohttp-3.13.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cb15595eb52870f84248d7cc97013a76f52ab02ff74d394be093b1d9b8b82bc0"}, + {file = "aiohttp-3.13.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:907ad36b6a65cff7d88d7aca0f77c650546ba850a4f92c92ecb83590d4613249"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5539ec0d6a3a5c6799b661b7e79166ad1b7ae71ccb59a92fcb6b4ef89295bc94"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b4e07d8803a70dd886b5f38588e5b49f894995ca8e132b06c31a2583ae2ef6e"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce7320a945aac4bf0bb8901600e4f9409eb602f25ce3ef4d275b48f6d704a862"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:26ed03f7d3d6453634729e2c7600d7255d65e879559c5a48fe1bb78355cde74b"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3f733916e85506b8000dddc071c6b82f8c68f56c99adb328d6550017db062d"}, + {file = "aiohttp-3.13.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3d525648fe7c8b4977e460c18098f9f81d7991d72edfdc2f13cf96068f279bc"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e2e68085730a03704beb2cff035fa8648f62c9f93758d7e6d70add7f7bb5b3b"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:797613182ffaaca0b9ad5f3b3d3ce5d21242c768f75e66c750b8292bd97c9de3"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2d15e7e4f1099d9e4d863eaf77a8eee5dcb002b7d7188061b0fbee37f845899e"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:19f60011ad60e40a01d242238bb335399e3a4d8df958c63cbb835add8d5c3b5a"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c344c47e85678e410b064fc2ace14db86bb69db7ed5520c234bf13aed603ec30"}, + {file = "aiohttp-3.13.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d904084985ca66459e93797e5e05985c048a9c0633655331144c089943e53d12"}, + {file = "aiohttp-3.13.4-cp39-cp39-win32.whl", hash = "sha256:1746338dc2a33cf706cd7446575d13d451f28f9860bebc908c7632b22e71ae3f"}, + {file = "aiohttp-3.13.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5444dce2e6fba0a1dc2d58d026e674f25f21de178c6f844342629bcef019f2f"}, + {file = "aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38"}, +] +markers = {main = "extra == \"aiohttp\""} + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aioitertools" -version = "0.11.0" +version = "0.13.0" description = "itertools and builtins for AsyncIO and mixed iterables" optional = true -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"fastapi\" or extra == \"starlette\"" files = [ - {file = "aioitertools-0.11.0-py3-none-any.whl", hash = "sha256:04b95e3dab25b449def24d7df809411c10e62aab0cbe31a50ca4e68748c43394"}, - {file = "aioitertools-0.11.0.tar.gz", hash = "sha256:42c68b8dd3a69c2bf7f2233bf7df4bb58b557bca5252ac02ed5187bbc67d6831"}, + {file = "aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be"}, + {file = "aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c"}, ] -[package.dependencies] -typing_extensions = {version = ">=4.0", markers = "python_version < \"3.10\""} - [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, ] +markers = {main = "extra == \"aiohttp\""} [package.dependencies] frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -optional = false -python-versions = ">=3.6" +name = "annotated-doc" +version = "0.0.4" +description = "Document parameters, class attributes, return types, and variables inline, with Annotated." +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"fastapi\"" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, + {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, ] [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] +markers = {main = "extra == \"fastapi\" or extra == \"starlette\""} [package.dependencies] exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} @@ -167,36 +232,56 @@ sniffio = ">=1.1" [package.extras] doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; python_version < \"3.12\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (<0.22)"] [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" optional = false -python-versions = "*" +python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, ] [[package]] name = "asgiref" -version = "3.7.2" +version = "3.11.1" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, - {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, + {file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"}, + {file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"}, ] +markers = {main = "extra == \"django\""} [package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] + +[[package]] +name = "asttokens" +version = "3.0.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, + {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, +] [package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +astroid = ["astroid (>=2,<5)"] +test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "async-timeout" @@ -204,10 +289,12 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] +markers = {main = "extra == \"aiohttp\" and python_version < \"3.11\"", dev = "python_version < \"3.11\""} [[package]] name = "attrs" @@ -215,6 +302,7 @@ version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, @@ -225,7 +313,7 @@ cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.1.1) ; platform_python_implementation == \"CPython\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", "pytest-xdist[psutil]"] [[package]] name = "babel" @@ -233,131 +321,234 @@ version = "2.13.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] [package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} setuptools = {version = "*", markers = "python_version >= \"3.12\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] -name = "backports-zoneinfo" -version = "0.2.1" -description = "Backport of the standard library zoneinfo module" +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false -python-versions = ">=3.6" +python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "backrefs" +version = "5.7.post1" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.8" +groups = ["docs"] files = [ - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, - {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, + {file = "backrefs-5.7.post1-py310-none-any.whl", hash = "sha256:c5e3fd8fd185607a7cb1fefe878cfb09c34c0be3c18328f12c574245f1c0287e"}, + {file = "backrefs-5.7.post1-py311-none-any.whl", hash = "sha256:712ea7e494c5bf3291156e28954dd96d04dc44681d0e5c030adf2623d5606d51"}, + {file = "backrefs-5.7.post1-py312-none-any.whl", hash = "sha256:a6142201c8293e75bce7577ac29e1a9438c12e730d73a59efdd1b75528d1a6c5"}, + {file = "backrefs-5.7.post1-py38-none-any.whl", hash = "sha256:ec61b1ee0a4bfa24267f6b67d0f8c5ffdc8e0d7dc2f18a2685fd1d8d9187054a"}, + {file = "backrefs-5.7.post1-py39-none-any.whl", hash = "sha256:05c04af2bf752bb9a6c9dcebb2aff2fab372d3d9d311f2a138540e307756bd3a"}, + {file = "backrefs-5.7.post1.tar.gz", hash = "sha256:8b0f83b770332ee2f1c8244f4e03c77d127a0fa529328e6a0e77fa25bee99678"}, ] [package.extras] -tzdata = ["tzdata"] +extras = ["regex"] [[package]] name = "black" -version = "24.4.2" +version = "26.3.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, + {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, + {file = "black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac"}, + {file = "black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a"}, + {file = "black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a"}, + {file = "black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff"}, + {file = "black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c"}, + {file = "black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5"}, + {file = "black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e"}, + {file = "black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5"}, + {file = "black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1"}, + {file = "black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f"}, + {file = "black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7"}, + {file = "black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983"}, + {file = "black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb"}, + {file = "black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54"}, + {file = "black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f"}, + {file = "black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56"}, + {file = "black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839"}, + {file = "black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2"}, + {file = "black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78"}, + {file = "black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568"}, + {file = "black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f"}, + {file = "black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c"}, + {file = "black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1"}, + {file = "black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b"}, + {file = "black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" -pathspec = ">=0.9.0" +pathspec = ">=1.0.0" platformdirs = ">=2" +pytokens = ">=0.4.0,<0.5.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] [[package]] name = "blinker" -version = "1.7.0" +version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"flask\"" files = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, ] [[package]] -name = "bump2version" -version = "1.0.1" -description = "Version-bump your software with a single command!" +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" +groups = ["main", "dev", "docs"] files = [ - {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, - {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] +markers = {main = "extra == \"requests\""} [[package]] -name = "certifi" -version = "2023.7.22" -description = "Python package for providing Mozilla's CA Bundle." +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -369,6 +560,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev", "docs"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -461,6 +653,24 @@ files = [ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +markers = {main = "extra == \"requests\""} + +[[package]] +name = "cli-ui" +version = "0.19.0" +description = "Build Nice User Interfaces In The Terminal" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["dev"] +files = [ + {file = "cli_ui-0.19.0-py3-none-any.whl", hash = "sha256:1cf1b93328f7377730db29507e10bcb29ccc1427ceef45714b522d1f2055e7cd"}, + {file = "cli_ui-0.19.0.tar.gz", hash = "sha256:59cdab0c6a2a6703c61b31cb75a1943076888907f015fffe15c5a8eb41a933aa"}, +] + +[package.dependencies] +colorama = ">=0.4.1,<0.5.0" +tabulate = ">=0.9.0,<0.10.0" +unidecode = ">=1.3.6,<2.0.0" [[package]] name = "click" @@ -468,10 +678,12 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "docs"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] +markers = {main = "extra == \"flask\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -482,101 +694,232 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"flask\" and platform_system == \"Windows\""} + +[[package]] +name = "comm" +version = "0.2.3" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, + {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, +] + +[package.extras] +test = ["pytest"] [[package]] name = "coverage" -version = "7.3.2" +version = "7.10.7" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "debugpy" +version = "1.8.20" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64"}, + {file = "debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642"}, + {file = "debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2"}, + {file = "debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893"}, + {file = "debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b"}, + {file = "debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344"}, + {file = "debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec"}, + {file = "debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb"}, + {file = "debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d"}, + {file = "debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b"}, + {file = "debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390"}, + {file = "debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3"}, + {file = "debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a"}, + {file = "debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf"}, + {file = "debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393"}, + {file = "debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7"}, + {file = "debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173"}, + {file = "debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad"}, + {file = "debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f"}, + {file = "debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be"}, + {file = "debugpy-1.8.20-cp38-cp38-macosx_15_0_x86_64.whl", hash = "sha256:b773eb026a043e4d9c76265742bc846f2f347da7e27edf7fe97716ea19d6bfc5"}, + {file = "debugpy-1.8.20-cp38-cp38-manylinux_2_34_x86_64.whl", hash = "sha256:20d6e64ea177ab6732bffd3ce8fc6fb8879c60484ce14c3b3fe183b1761459ca"}, + {file = "debugpy-1.8.20-cp38-cp38-win32.whl", hash = "sha256:0dfd9adb4b3c7005e9c33df430bcdd4e4ebba70be533e0066e3a34d210041b66"}, + {file = "debugpy-1.8.20-cp38-cp38-win_amd64.whl", hash = "sha256:60f89411a6c6afb89f18e72e9091c3dfbcfe3edc1066b2043a1f80a3bbb3e11f"}, + {file = "debugpy-1.8.20-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:bff8990f040dacb4c314864da95f7168c5a58a30a66e0eea0fb85e2586a92cd6"}, + {file = "debugpy-1.8.20-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:70ad9ae09b98ac307b82c16c151d27ee9d68ae007a2e7843ba621b5ce65333b5"}, + {file = "debugpy-1.8.20-cp39-cp39-win32.whl", hash = "sha256:9eeed9f953f9a23850c85d440bf51e3c56ed5d25f8560eeb29add815bd32f7ee"}, + {file = "debugpy-1.8.20-cp39-cp39-win_amd64.whl", hash = "sha256:760813b4fff517c75bfe7923033c107104e76acfef7bda011ffea8736e9a66f8"}, + {file = "debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7"}, + {file = "debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33"}, +] + +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] [[package]] name = "deptry" -version = "0.16.1" +version = "0.24.0" description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "deptry-0.16.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:29ed8ae61b8f5664dd484717c79eef7ec66d965940efd828fca0d3c09220a1db"}, - {file = "deptry-0.16.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:738a772b538f51e9a7bb8d5cb9a61cfea8794a79371d171919b01cff0dc895bf"}, - {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56b78f7c860def8000e93f88345a24809f1b91e2f7836ac9a08285cb405e2762"}, - {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e86a04ea87ddece0f68ba204feb950f588205808c8320e6628300f03ff66dc"}, - {file = "deptry-0.16.1-cp38-abi3-win_amd64.whl", hash = "sha256:01b5098739a56c93f3e1e40efec5f20452f22a9a8436a59809d46201fcb94bcf"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e29dc4c1bbb933c9482e8cef85fafe2be7f46aeb90a8a07ba5f2b22af60876f"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8dfab68c247566c87a40f55f405be8549ffe4cea0b9b5384b7ae73a6f1d5cd1"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1228493926b6e59cd2df7cb6016e10c255553cc31db24edcf7fc8d5474b81be6"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99c3ac60b78ad1b8fb9844c25393e7ebc969cc950601ce3c050f56d196da5a79"}, - {file = "deptry-0.16.1.tar.gz", hash = "sha256:39fb62da4a8f4d17ed282310f7bcaadec55a95a8c471b01e0fcdf5351a7ac323"}, + {file = "deptry-0.24.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a575880146bab671a62babb9825b85b4f1bda8aeaade4fcb59f9262caf91d6c7"}, + {file = "deptry-0.24.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:00ec34b968a13c03a5268ce0211f891ace31851d916415e0a748fae9596c00d5"}, + {file = "deptry-0.24.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ddfedafafe5cbfce31a50d4ea99d7b9074edcd08b9b94350dc739e2fb6ed7f9"}, + {file = "deptry-0.24.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd22fa2dbbdf4b38061ca9504f2a6ce41ec14fa5c9fe9b0b763ccc1275efebd5"}, + {file = "deptry-0.24.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0fbe50a2122d79cec53fdfd73a7092c05f316555a1139bcbacf3432572675977"}, + {file = "deptry-0.24.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:92bd8d331a5a6f8e6247436bc6fe384bcf86a8d69fe33442d195996fb9b20547"}, + {file = "deptry-0.24.0-cp39-abi3-win_amd64.whl", hash = "sha256:94b354848130d45e16d3a3039ae8177bce33828f62028c4ff8f2e1b04f7182ba"}, + {file = "deptry-0.24.0-cp39-abi3-win_arm64.whl", hash = "sha256:ea58709e5f3aa77c0737d8fb76166b7703201cf368fbbb14072ccda968b6703a"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6ae96785aaee5540c144306506f1480dcfa4d096094e6bd09dc8c9a9bfda1d46"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4267d74a600ac7fdd05a0d3e219c9386670db0d3bb316ae7b94c9b239d1187cb"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a047e53b76c36737f8bb392bb326fb66c6af4bedafeaa4ad274c7ed82e91862"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841bf35d62e1facc0c244b9430455705249cc93552ed4964d367befe9be6a313"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5152ffa478e62f9aea9df585ce49d758087fd202f6d92012216aa0ecad22c267"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68d90735042c169e2a12846ac5af9e20d0ad1a5a7a894a9e4eb0bd8f3c655add"}, + {file = "deptry-0.24.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:03d375db3e56821803aeca665dbb4c2fd935024310350cc18e8d8b6421369d2b"}, + {file = "deptry-0.24.0.tar.gz", hash = "sha256:852e88af2087e03cdf9ece6916f3f58b74191ab51cc8074897951bd496ee7dbb"}, ] [package.dependencies] click = ">=8.0.0,<9" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +packaging = ">=23.2" +requirements-parser = ">=0.11.0,<1" +tomli = {version = ">=2.0.1", markers = "python_full_version < \"3.11.0\""} [[package]] name = "distlib" @@ -584,6 +927,7 @@ version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, @@ -591,18 +935,19 @@ files = [ [[package]] name = "django" -version = "4.2.13" +version = "5.2.12" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, - {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, + {file = "django-5.2.12-py3-none-any.whl", hash = "sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7"}, + {file = "django-5.2.12.tar.gz", hash = "sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb"}, ] +markers = {main = "extra == \"django\""} [package.dependencies] -asgiref = ">=3.6.0,<4" -"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +asgiref = ">=3.8.1" sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} @@ -612,28 +957,28 @@ bcrypt = ["bcrypt"] [[package]] name = "djangorestframework" -version = "3.15.1" +version = "3.17.0" description = "Web APIs for Django, made easy." optional = false -python-versions = ">=3.6" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, - {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, + {file = "djangorestframework-3.17.0-py3-none-any.whl", hash = "sha256:d84fe85f30b7ac6e8c0076ce9ff635e4eaedca5912f8d7d2926ce448c08533ba"}, + {file = "djangorestframework-3.17.0.tar.gz", hash = "sha256:456fd992a33f9e64c9d0f47e85d9787db0efb44f894c1e513315b5e74765bd4c"}, ] [package.dependencies] -"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} -django = ">=3.0" +django = ">=4.2" [[package]] -name = "docutils" -version = "0.20.1" -description = "Docutils -- Python Documentation Utilities" +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" optional = false -python-versions = ">=3.7" +python-versions = "*" +groups = ["dev"] files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] [[package]] @@ -642,139 +987,160 @@ version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] +markers = {main = "(extra == \"fastapi\" or extra == \"starlette\") and python_version < \"3.11\"", dev = "python_version < \"3.11\""} [package.extras] test = ["pytest (>=6)"] [[package]] -name = "execnet" -version = "2.0.2" -description = "execnet: rapid multi-Python deployment" +name = "executing" +version = "2.2.1" +description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, + {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, + {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, ] [package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "falcon" -version = "3.1.3" +version = "4.2.0" description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." -optional = false -python-versions = ">=3.5" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"falcon\"" files = [ - {file = "falcon-3.1.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:094d295a767e2aa84f07bec6b23e9ebe2e43cde81d9d583bef037168bd775ad6"}, - {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b203408040e87e8323e1c1921b106353fa5fe5dc05c9b3f4881acb3af03f556"}, - {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d56d9a9886387585ce4547354c9929bf5743394df04a17df6ed51ad6bb58a4cc"}, - {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c335f1118a6e42f08cf30d56914a0bc0d470aa6db7619fdc4c546b184f38248"}, - {file = "falcon-3.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:cb6b6a79d096b3a1f2f37f66f46a2cf18deb575db6dee9935057e6036d98d01f"}, - {file = "falcon-3.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:508fdf30617cf1fa5c9d3058c14124dc8e5f7e316e26dca22d974f916493fd0e"}, - {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3c6cbcba90e272f60581fb3c4561cdcd0ac6d19672f5a11a04309b1d23fa66"}, - {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7471aab646875d4478377065246a4115aaf3c0801a6eb4b6871f9836c8ef60b1"}, - {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bbbfa1ecb1d50bed9f8ae940b0f1049d958e945f1a08891769d40cfabe6fb2"}, - {file = "falcon-3.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:24aa51ba4145f05649976c33664971ef36f92846208bd9d4d4158ceb51bc753f"}, - {file = "falcon-3.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7a1ee54bf19d9c7f998edd8ac21ab8ead1e2f73c24822237eb5485890979a25d"}, - {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db78171113a3920f0f33d8dd26364527a362db2d1c3376a95778653ff87dea24"}, - {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:656e738e0e220f4503e4f07747b564f4459da159a1f32ec6d2478efb651278dd"}, - {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e19a0a3827821bcf754a9b24217e3b8b4750f7eb437c4a8c461135a86ca9b1c5"}, - {file = "falcon-3.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:d52a05be5c2ef364853cdc6d97056dd880a534016db73b95f5a6ebc652577533"}, - {file = "falcon-3.1.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d78a6cfe2d135632673def489a19474e2508d83475c7662c4fa63be0ba82dd81"}, - {file = "falcon-3.1.3-cp36-cp36m-win_amd64.whl", hash = "sha256:adc23ced91c4690042a11a0515c5cfe93eeeb7d063940900aee85f8eae7460ec"}, - {file = "falcon-3.1.3-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:d6b7131e85dff13abaacb4ff479c456256f0d57b262b1fb1771180f7535cc902"}, - {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d51f556ece73766f07ede57f17fa65dbbc2cc5e1c7075fb606f727464ad71e"}, - {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b210c05b38a8d655e16aa3ae2befaa70ecfb49bef73c0c1995566b22afcfdd1"}, - {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04a92f159d392098a11d14b8ca71d17129d8b1ef37b7a3577f1f8bcb7b3aecba"}, - {file = "falcon-3.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9c82cb54bbf67861febe80d394c9b7bfa0d2e16cc998b69bfff4e8b003c721a2"}, - {file = "falcon-3.1.3-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:56e8a4728fb0193e2ccd5301d864fd9743a989cc228e709e5c49ff1025cc1a4f"}, - {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12432c3f6bce46fe4eec3db6db8d2df1abe43a7531219356f1ba859db207e57b"}, - {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1f622d73111912021b8311d1e5d1eabef484217d2d30abe3d237533cb225ce9"}, - {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b2ce8a613a29a9eaf8243ca285ebf80464e8a6489dff60425f850fb5548936"}, - {file = "falcon-3.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:3cda76fb21568aa058ce454fa6272ca5b2582ebb0efcb7ae0090d3bf6d0db5af"}, - {file = "falcon-3.1.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:cbd40435e99255e40ccfa849e4809cd1638fd8eccc08931fc9d355a6840a7332"}, - {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6319883789ee3abcbde2dc10fed8016cc3d9a05018ae59944838b892101111a"}, - {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:796a57046b0717bff5ac488235c37ea63834a5cfc2c9291c5eeaa43c53e5e24c"}, - {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2fe54081f1cedc71462eff8dca074045d14380a4bca163882c6c4353f65af2"}, - {file = "falcon-3.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:ad37c46322122f34e228be4fe7ae5fcfedb630eef788a198fbdff5971091d5dc"}, - {file = "falcon-3.1.3.tar.gz", hash = "sha256:23335dbccd44f29e85ec55f2f35d5a0bc12bd7a509f641ab81f5c64b65626263"}, + {file = "falcon-4.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8b179c9de6aa29eaa2ab49cac94eb304f279b66c7073be915cef5d6ae1f8b69d"}, + {file = "falcon-4.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd6b0c04c5e8ee56ec3acec2c8603cfcc39658d7793ea86ecf058b094840c222"}, + {file = "falcon-4.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05cd6dcf4cae4ad1cbbe6a11c9d63b35bb6f35422f778a292bc13f91f2504ad5"}, + {file = "falcon-4.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d045396a6d40f5d1bbe3eaf59496a382840db1c8841fe38ba8d45018fd3a184b"}, + {file = "falcon-4.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd62565115df5b8b0780713979c285f3d84d4300f8d1c367b0678315eac6db63"}, + {file = "falcon-4.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9a0e2de9bd9a9b7d8644e44e49f26675fa753665b6a2ab3e9539c64bc636e398"}, + {file = "falcon-4.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:03c80035378b8b03375f7a7debd11d3b33cdb5b732d882e65b580afe9f937832"}, + {file = "falcon-4.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2faf74b996ad36fed2981a479f1d1d5e2f01b36f648746197285f38002022ad4"}, + {file = "falcon-4.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea18a598686b6a84cb59ce9afdd518f6bd5e79d9301290636645b5c81277621"}, + {file = "falcon-4.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99ea076c290d092d052d4ec132238bbe5c414bee30b42621f814133ad62aad93"}, + {file = "falcon-4.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e146967a4ff16c1a8f84971f5d2af81ba0b4ef13caf583e8094aa5ec9511d80"}, + {file = "falcon-4.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f159b8334686716d61f7e5c82c897f2d21013f38904fe3aafe7d83c5fbd98a4d"}, + {file = "falcon-4.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9c93dd7770e3b1cc5f0bc08f23ec954ae00d1b408f7255efa806697fdf38b345"}, + {file = "falcon-4.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:429974363bbb9ed4e98401c71be54f319559695e499238a51905895371c40fa7"}, + {file = "falcon-4.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05832f66d54e178ae1df1dffe25c80a076448dc261cf6c50b271051b6cf56f0e"}, + {file = "falcon-4.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f7d454888ed6238f6d00406bfedf976b05157e001fc6a18a473ec1e2be35e6c"}, + {file = "falcon-4.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:353c69fe78b23dfa4fbe0ae78aa7d1ec2fe1c9db3c46b5a3e20d8f731b483b65"}, + {file = "falcon-4.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66db3bd0e51723b299e31746a6c28c063ee0048988d9ef2f1d05245fd97bebf8"}, + {file = "falcon-4.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d89a61285b49fb503c30cb11203694aba6d3e0f2e7cc5cad3676ce221d3a514"}, + {file = "falcon-4.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02d3b1fb18393ed55315e04533eefd3f86d85d294212bf49895c5768007e58c9"}, + {file = "falcon-4.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:d3c9882f8bf98bd2bf0ab2a9378c108dfba33a41625cfe2f8106e060258b52ef"}, + {file = "falcon-4.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:00363f9d9273a1281ca7aa1d9dbecea09c172e7bb08e0acefa0a0234a3f94593"}, + {file = "falcon-4.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd2059695f107e867fd12141d05771d5c6cbecc30a135f7d91ef06bfea94f05e"}, + {file = "falcon-4.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e0b1f69a97b3406feba07f41dde177b4c3dfa7046f6b977d4554772dc26252e7"}, + {file = "falcon-4.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a54fa6c5f8a428a2e9b7ff7b936c566fe7bdcc50f965cea37fee9523eab1b74"}, + {file = "falcon-4.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:801e2c77c72b1777d09be7a72163b38209f5f9e42930bfe3dfdf027e7d84d035"}, + {file = "falcon-4.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f998402bf889cdd23cde29e7421469cdf2ef95afc71b2cdef7ed4957d0cd97f6"}, + {file = "falcon-4.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:584d000e9ffae5044f5fe6bf74d399edebb54926bb4a133d3caf03e529b8c616"}, + {file = "falcon-4.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae9304c60b5fe84ffb35e91e1a1f071543a303edb252999800531ea01133c0d4"}, + {file = "falcon-4.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16533a0ade619cc8e7f670330d4c12fa0bff74de88bfb29f3d3cf1b2023d31b8"}, + {file = "falcon-4.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f3ddffc958d4e625281a321164c77ebbf537c0f2f5290b06ee1144b90386a5f"}, + {file = "falcon-4.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0c501f8206b9bf361826bfe8f108c7368afcae64df3ed38589b9becefdfad63"}, + {file = "falcon-4.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:402f38101b434415ecff72e5aa440c4f71ab45a879f455ab7d5655050e8ed218"}, + {file = "falcon-4.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ca9194a3e8a9eace3bc0efaef50b4244beabd75cdd716611e244646efc6828a"}, + {file = "falcon-4.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e0bd6384952b9e12d3ae84675df4862bdbaa1111cd52db17d70cdf60f8abe4b6"}, + {file = "falcon-4.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de67c7ed58a124f9f04337d254ec9db0e9fa0772d25f1c8f260c1c47878dc556"}, + {file = "falcon-4.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd8c19241aa66ecf494cd16d1cdc71de2cfbb3f76cafb7176e92708786001340"}, + {file = "falcon-4.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aef6cd21a6e1b51c79038ff2e0b30746a68c7710307e5f5f0839338d7129577c"}, + {file = "falcon-4.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c132bb94351bddde993aad5147f9f3d9a942e2d93aece9d693723fb96fc8f51"}, + {file = "falcon-4.2.0-py3-none-any.whl", hash = "sha256:1d64afeca0dc03e7bed0202681dab4844544d8f6855c23e13f11a6eb10ac50ff"}, + {file = "falcon-4.2.0.tar.gz", hash = "sha256:c13e86e49696d6655411fe09473c34997e49ff45e8cdf7576297b0ca71ceac3d"}, ] +[package.extras] +test = ["pytest"] + [[package]] name = "fastapi" -version = "0.108.0" +version = "0.135.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"fastapi\"" files = [ - {file = "fastapi-0.108.0-py3-none-any.whl", hash = "sha256:8c7bc6d315da963ee4cdb605557827071a9a7f95aeb8fcdd3bde48cdc8764dd7"}, - {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, + {file = "fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5"}, + {file = "fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.29.0,<0.33.0" +annotated-doc = ">=0.0.2" +pydantic = ">=2.9.0" +starlette = ">=0.46.0" typing-extensions = ">=4.8.0" +typing-inspection = ">=0.4.2" [package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" -version = "3.13.1" +version = "3.20.3" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - [[package]] name = "flake8" -version = "2.3.0" -description = "the modular source code checker: pep8, pyflakes and co" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "flake8-2.3.0-py2.py3-none-any.whl", hash = "sha256:c99cc9716d6655d9c8bcb1e77632b8615bf0abd282d7abd9f5c2148cad7fc669"}, - {file = "flake8-2.3.0.tar.gz", hash = "sha256:5ee1a43ccd0716d6061521eec6937c983efa027793013e572712c4da55c7c83e"}, + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, ] [package.dependencies] -mccabe = ">=0.2.1" -pep8 = ">=1.5.7" -pyflakes = ">=0.8.1" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" [[package]] name = "flask" -version = "3.0.3" +version = "3.1.3" description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"flask\"" files = [ - {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, - {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, + {file = "flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"}, + {file = "flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb"}, ] [package.dependencies] -blinker = ">=1.6.2" +blinker = ">=1.9.0" click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=3.0.0" +itsdangerous = ">=2.2.0" +jinja2 = ">=3.1.2" +markupsafe = ">=2.1.1" +werkzeug = ">=3.1.0" [package.extras] async = ["asgiref (>=3.2)"] @@ -782,115 +1148,212 @@ dotenv = ["python-dotenv"] [[package]] name = "frozenlist" -version = "1.4.0" +version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9799257237d0479736e2b4c01ff26b5c7f7694ac9692a426cb717f3dc02fff9b"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a7bb0fe1f7a70fb5c6f497dc32619db7d2cdd53164af30ade2f34673f8b1fc"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:36d2fc099229f1e4237f563b2a3e0ff7ccebc3999f729067ce4e64a97a7f2869"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f27a9f9a86dcf00708be82359db8de86b80d029814e6693259befe82bb58a106"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ecee69073312951244f11b8627e3700ec2bfe07ed24e3a685a5979f0412d24"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2c7d5aa19714b1b01a0f515d078a629e445e667b9da869a3cd0e6fe7dec78bd"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bbd454f0fb23b51cadc9bdba616c9678e4114b6f9fa372d462ff2ed9323ec8"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7daa508e75613809c7a57136dec4871a21bca3080b3a8fc347c50b187df4f00c"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89ffdb799154fd4d7b85c56d5fa9d9ad48946619e0eb95755723fffa11022d75"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:920b6bd77d209931e4c263223381d63f76828bec574440f29eb497cf3394c249"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d3ceb265249fb401702fce3792e6b44c1166b9319737d21495d3611028d95769"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52021b528f1571f98a7d4258c58aa8d4b1a96d4f01d00d51f1089f2e0323cb02"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f2ca7810b809ed0f1917293050163c7654cefc57a49f337d5cd9de717b8fad3"}, + {file = "frozenlist-1.6.0-cp310-cp310-win32.whl", hash = "sha256:0e6f8653acb82e15e5443dba415fb62a8732b68fe09936bb6d388c725b57f812"}, + {file = "frozenlist-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a39819a5a3e84304cd286e3dc62a549fe60985415851b3337b6f5cc91907f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e"}, + {file = "frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860"}, + {file = "frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770"}, + {file = "frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc"}, + {file = "frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e"}, + {file = "frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4"}, + {file = "frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:536a1236065c29980c15c7229fbb830dedf809708c10e159b8136534233545f0"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ed5e3a4462ff25ca84fb09e0fada8ea267df98a450340ead4c91b44857267d70"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e19c0fc9f4f030fcae43b4cdec9e8ab83ffe30ec10c79a4a43a04d1af6c5e1ad"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c608f833897501dac548585312d73a7dca028bf3b8688f0d712b7acfaf7fb3"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0dbae96c225d584f834b8d3cc688825911960f003a85cb0fd20b6e5512468c42"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625170a91dd7261a1d1c2a0c1a353c9e55d21cd67d0852185a5fef86587e6f5f"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1db8b2fc7ee8a940b547a14c10e56560ad3ea6499dc6875c354e2335812f739d"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4da6fc43048b648275a220e3a61c33b7fff65d11bdd6dcb9d9c145ff708b804c"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef8e7e8f2f3820c5f175d70fdd199b79e417acf6c72c5d0aa8f63c9f721646f"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa733d123cc78245e9bb15f29b44ed9e5780dc6867cfc4e544717b91f980af3b"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ba7f8d97152b61f22d7f59491a781ba9b177dd9f318486c5fbc52cde2db12189"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:56a0b8dd6d0d3d971c91f1df75e824986667ccce91e20dca2023683814344791"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5c9e89bf19ca148efcc9e3c44fd4c09d5af85c8a7dd3dbd0da1cb83425ef4983"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1330f0a4376587face7637dfd245380a57fe21ae8f9d360c1c2ef8746c4195fa"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2187248203b59625566cac53572ec8c2647a140ee2738b4e36772930377a533c"}, + {file = "frozenlist-1.6.0-cp39-cp39-win32.whl", hash = "sha256:2b8cf4cfea847d6c12af06091561a89740f1f67f331c3fa8623391905e878530"}, + {file = "frozenlist-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:1255d5d64328c5a0d066ecb0f02034d086537925f1f04b50b1ae60d37afbf572"}, + {file = "frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191"}, + {file = "frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68"}, +] +markers = {main = "extra == \"aiohttp\""} + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "1.14.0" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0"}, + {file = "griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "griffe-typingdoc" +version = "0.2.9" +description = "Griffe extension for PEP 727 – Documentation Metadata in Typing." +optional = false +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, + {file = "griffe_typingdoc-0.2.9-py3-none-any.whl", hash = "sha256:cc6b1e34d64e1659da5b3d37506214834bc8fbb62b081b2fb43563ee5cdaf8f5"}, + {file = "griffe_typingdoc-0.2.9.tar.gz", hash = "sha256:99c05bf09a9c391464e3937718c9a5a1055bb95ed549f4f7706be9a71578669c"}, ] +[package.dependencies] +griffe = ">=1.14" +typing-extensions = ">=4.7" + [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "httpcore" -version = "1.0.1" +version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "httpcore-1.0.1-py3-none-any.whl", hash = "sha256:c5e97ef177dca2023d0b9aad98e49507ef5423e9f1d94ffe2cfe250aa28e63b0"}, - {file = "httpcore-1.0.1.tar.gz", hash = "sha256:fce1ddf9b606cfb98132ab58865c3728c52c8e4c3c46e2aabb3674464a186e92"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] certifi = "*" -h11 = ">=0.13,<0.15" +h11 = ">=0.16" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] @@ -898,13 +1361,13 @@ anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" @@ -912,6 +1375,7 @@ version = "2.5.31" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, @@ -926,119 +1390,170 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev", "docs"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +markers = {main = "extra == \"aiohttp\" or extra == \"fastapi\" or extra == \"starlette\" or extra == \"requests\""} [[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" +name = "ipykernel" +version = "7.2.0" +description = "IPython Kernel for Jupyter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661"}, + {file = "ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e"}, ] [package.dependencies] -zipp = ">=0.5" +appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=8.8.0" +jupyter-core = ">=5.1,<6.0.dev0 || >=6.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = ">=1.4" +packaging = ">=22" +psutil = ">=5.7" +pyzmq = ">=25" +tornado = ">=6.4.1" +traitlets = ">=5.4.0" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx (<8.2.0)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<10)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] [[package]] -name = "importlib-resources" -version = "6.1.0" -description = "Read resources from Python packages" +name = "ipython" +version = "8.38.0" +description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, - {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, + {file = "ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86"}, + {file = "ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39"}, ] [package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "isodate" -version = "0.6.1" +version = "0.7.2" description = "An ISO 8601 date/time/duration parser and formatter" optional = false -python-versions = "*" +python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, + {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, + {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, ] -[package.dependencies] -six = "*" - [[package]] name = "isort" -version = "5.13.2" +version = "8.0.1" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.10.0" +groups = ["dev"] files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, + {file = "isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75"}, + {file = "isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d"}, ] [package.extras] -colors = ["colorama (>=0.4.6)"] +colors = ["colorama"] [[package]] name = "itsdangerous" -version = "2.1.2" +version = "2.2.0" description = "Safely pass data to untrusted environments and back." +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"flask\"" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" +groups = ["dev"] files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "docs"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] +markers = {main = "extra == \"flask\""} [package.dependencies] MarkupSafe = ">=2.0" @@ -1048,110 +1563,282 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jsonschema" -version = "4.22.0" +version = "4.24.1" description = "An implementation of JSON Schema validation for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, - {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, + {file = "jsonschema-4.24.1-py3-none-any.whl", hash = "sha256:6b916866aa0b61437785f1277aa2cbd63512e8d4b47151072ef13292049b4627"}, + {file = "jsonschema-4.24.1.tar.gz", hash = "sha256:fe45a130cc7f67cd0d67640b4e7e3e2e666919462ae355eda238296eafeb4b5d"}, ] [package.dependencies] attrs = ">=22.2.0" -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} jsonschema-specifications = ">=2023.03.6" -pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} referencing = ">=0.28.4" rpds-py = ">=0.7.1" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-path" -version = "0.3.2" +version = "0.4.5" description = "JSONSchema Spec with object-oriented paths" optional = false -python-versions = ">=3.8.0,<4.0.0" +python-versions = "<4.0.0,>=3.10" +groups = ["main"] files = [ - {file = "jsonschema_path-0.3.2-py3-none-any.whl", hash = "sha256:271aedfefcd161a0f467bdf23e1d9183691a61eaabf4b761046a914e369336c7"}, - {file = "jsonschema_path-0.3.2.tar.gz", hash = "sha256:4d0dababf341e36e9b91a5fb2a3e3fd300b0150e7fe88df4e55cc8253c5a3989"}, + {file = "jsonschema_path-0.4.5-py3-none-any.whl", hash = "sha256:7d77a2c3f3ec569a40efe5c5f942c44c1af2a6f96fe0866794c9ef5b8f87fd65"}, + {file = "jsonschema_path-0.4.5.tar.gz", hash = "sha256:c6cd7d577ae290c7defd4f4029e86fdb248ca1bd41a07557795b3c95e5144918"}, ] [package.dependencies] -pathable = ">=0.4.1,<0.5.0" +pathable = ">=0.5.0,<0.6.0" PyYAML = ">=5.1" -referencing = ">=0.28.0,<0.32.0" -requests = ">=2.31.0,<3.0.0" +referencing = "<0.38.0" + +[package.extras] +requests = ["requests (>=2.31.0,<3.0.0)"] [[package]] name = "jsonschema-specifications" -version = "2023.7.1" +version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "jupyter-client" +version = "8.8.0" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a"}, + {file = "jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e"}, +] + +[package.dependencies] +jupyter-core = ">=5.1" +python-dateutil = ">=2.8.2" +pyzmq = ">=25.0" +tornado = ">=6.4.1" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +orjson = ["orjson"] +test = ["anyio", "coverage", "ipykernel (>=6.14)", "msgpack", "mypy ; platform_python_implementation != \"PyPy\"", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.6.2)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, - {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, + {file = "jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407"}, + {file = "jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508"}, ] [package.dependencies] -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} -referencing = ">=0.28.0" +platformdirs = ">=2.5" +traitlets = ">=5.3" + +[package.extras] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] [[package]] name = "lazy-object-proxy" -version = "1.9.0" +version = "1.12.0" description = "A fast and thorough lazy object proxy." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, + {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, + {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b"}, + {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8"}, + {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8"}, + {file = "lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa"}, + {file = "lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23"}, + {file = "lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac"}, + {file = "lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5"}, + {file = "lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae575ad9b674d0029fc077c5231b3bc6b433a3d1a62a8c363df96974b5534728"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31020c84005d3daa4cc0fa5a310af2066efe6b0d82aeebf9ab199292652ff036"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800f32b00a47c27446a2b767df7538e6c66a3488632c402b4fb2224f9794f3c0"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:15400b18893f345857b9e18b9bd87bd06aba84af6ed086187add70aeaa3f93f1"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d3964fbd326578bcdfffd017ef101b6fb0484f34e731fe060ba9b8816498c36"}, + {file = "lazy_object_proxy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:424a8ab6695400845c39f13c685050eab69fa0bbac5790b201cd27375e5e41d7"}, + {file = "lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402"}, + {file = "lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61"}, +] + +[[package]] +name = "legacy-cgi" +version = "2.6.2" +description = "Fork of the standard library cgi and cgitb modules, being deprecated in PEP-594" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "legacy_cgi-2.6.2-py3-none-any.whl", hash = "sha256:a7b83afb1baf6ebeb56522537c5943ef9813cf933f6715e88a803f7edbce0bff"}, + {file = "legacy_cgi-2.6.2.tar.gz", hash = "sha256:9952471ceb304043b104c22d00b4f333cac27a6abe446d8a528fc437cf13c85f"}, +] + +[[package]] +name = "librt" +version = "0.7.4" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc300cb5a5a01947b1ee8099233156fdccd5001739e5f596ecfbc0dab07b5a3b"}, + {file = "librt-0.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee8d3323d921e0f6919918a97f9b5445a7dfe647270b2629ec1008aa676c0bc0"}, + {file = "librt-0.7.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:95cb80854a355b284c55f79674f6187cc9574df4dc362524e0cce98c89ee8331"}, + {file = "librt-0.7.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca1caedf8331d8ad6027f93b52d68ed8f8009f5c420c246a46fe9d3be06be0f"}, + {file = "librt-0.7.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a6f1236151e6fe1da289351b5b5bce49651c91554ecc7b70a947bced6fe212"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7766b57aeebaf3f1dac14fdd4a75c9a61f2ed56d8ebeefe4189db1cb9d2a3783"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1c4c89fb01157dd0a3bfe9e75cd6253b0a1678922befcd664eca0772a4c6c979"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7fa8beef580091c02b4fd26542de046b2abfe0aaefa02e8bcf68acb7618f2b3"}, + {file = "librt-0.7.4-cp310-cp310-win32.whl", hash = "sha256:543c42fa242faae0466fe72d297976f3c710a357a219b1efde3a0539a68a6997"}, + {file = "librt-0.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:25cc40d8eb63f0a7ea4c8f49f524989b9df901969cb860a2bc0e4bad4b8cb8a8"}, + {file = "librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a"}, + {file = "librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729"}, + {file = "librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed"}, + {file = "librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6"}, + {file = "librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c"}, + {file = "librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2"}, + {file = "librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e"}, + {file = "librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170"}, + {file = "librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92"}, + {file = "librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108"}, + {file = "librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94"}, + {file = "librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab"}, + {file = "librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f"}, + {file = "librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286"}, + {file = "librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20"}, + {file = "librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a"}, + {file = "librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b"}, + {file = "librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32"}, + {file = "librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67"}, + {file = "librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20"}, + {file = "librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c"}, + {file = "librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2"}, + {file = "librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e"}, + {file = "librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788"}, + {file = "librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d"}, + {file = "librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23"}, + {file = "librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063"}, + {file = "librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848"}, + {file = "librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce"}, + {file = "librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f"}, + {file = "librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a"}, + {file = "librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44"}, + {file = "librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105"}, + {file = "librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5"}, + {file = "librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb"}, + {file = "librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481"}, + {file = "librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f"}, + {file = "librt-0.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fc4aa67fedd827a601f97f0e61cc72711d0a9165f2c518e9a7c38fc1568b9ad"}, + {file = "librt-0.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e710c983d29d9cc4da29113b323647db286eaf384746344f4a233708cca1a82c"}, + {file = "librt-0.7.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:43a2515a33f2bc17b15f7fb49ff6426e49cb1d5b2539bc7f8126b9c5c7f37164"}, + {file = "librt-0.7.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd766bb9ace3498f6b93d32f30c0e7c8ce6b727fecbc84d28160e217bb66254"}, + {file = "librt-0.7.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce1b44091355b68cffd16e2abac07c1cafa953fa935852d3a4dd8975044ca3bf"}, + {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a72b905420c4bb2c10c87b5c09fe6faf4a76d64730e3802feef255e43dfbf5a"}, + {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07c4d7c9305e75a0edd3427b79c7bd1d019cd7eddaa7c89dbb10e0c7946bffbb"}, + {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2e734c2c54423c6dcc77f58a8585ba83b9f72e422f9edf09cab1096d4a4bdc82"}, + {file = "librt-0.7.4-cp39-cp39-win32.whl", hash = "sha256:a34ae11315d4e26326aaf04e21ccd8d9b7de983635fba38d73e203a9c8e3fe3d"}, + {file = "librt-0.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:7e4b5ffa1614ad4f32237d739699be444be28de95071bfa4e66a8da9fa777798"}, + {file = "librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba"}, +] + +[[package]] +name = "markdown" +version = "3.8.1" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, + {file = "markdown-3.8.1-py3-none-any.whl", hash = "sha256:46cc0c0f1e5211ab2e9d453582f0b28a1bfaf058a9f7d5c50386b99b588d8811"}, + {file = "markdown-3.8.1.tar.gz", hash = "sha256:a2e2f01cead4828ee74ecca9623045f62216aef2212a7685d6eb9163f590b8c1"}, ] +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + [[package]] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main", "docs"] files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, @@ -1215,170 +1902,429 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76"}, + {file = "matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe"}, +] + +[package.dependencies] +traitlets = "*" + +[package.extras] +test = ["flake8", "nbdime", "nbval", "notebook", "pytest"] + [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] -name = "more-itertools" -version = "10.2.0" -description = "More routines for operating on iterables, beyond itertools" +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" +groups = ["docs"] files = [ - {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, - {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] [[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["docs"] files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, ] +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +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" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "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)"] + [[package]] -name = "mypy" -version = "1.8.0" -description = "Optional static typing for Python" +name = "mkdocs-autorefs" +version = "1.4.3" +description = "Automatically link across pages in MkDocs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" + {file = "mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9"}, + {file = "mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75"}, +] -[package.extras] +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.7.6" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba"}, + {file = "mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69"}, +] + +[package.dependencies] +babel = ">=2.10" +backrefs = ">=5.7.post1" +colorama = ">=0.4" +jinja2 = ">=3.1" +markdown = ">=3.2" +mkdocs = ">=1.6,<2" +mkdocs-material-extensions = ">=1.3" +paginate = ">=0.5" +pygments = ">=2.16" +pymdown-extensions = ">=10.2" +requests = ">=2.30" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4)"] +imaging = ["cairosvg (>=2.6)", "pillow (>=10.2)"] +recommended = ["mkdocs-minify-plugin (>=0.7)", "mkdocs-redirects (>=1.2)", "mkdocs-rss-plugin (>=1.6)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.3" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.10" +groups = ["docs"] +files = [ + {file = "mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046"}, + {file = "mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434"}, +] + +[package.dependencies] +Jinja2 = ">=3.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.6" +mkdocs-autorefs = ">=1.4" +mkdocstrings-python = {version = ">=1.16.2", optional = true, markers = "extra == \"python\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=1.16.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.18.2" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d"}, + {file = "mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323"}, +] + +[package.dependencies] +griffe = ">=1.13" +mkdocs-autorefs = ">=1.4" +mkdocstrings = ">=0.30" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "more-itertools" +version = "10.8.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, + {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, +] + +[[package]] +name = "multidict" +version = "6.7.1" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, +] +markers = {main = "extra == \"aiohttp\""} + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.19.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, +] + +[package.dependencies] +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -1389,17 +2335,31 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +groups = ["dev"] files = [ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, @@ -1410,37 +2370,46 @@ setuptools = "*" [[package]] name = "openapi-schema-validator" -version = "0.6.2" +version = "0.8.1" description = "OpenAPI schema validation for Python" optional = false -python-versions = ">=3.8.0,<4.0.0" +python-versions = "<4.0.0,>=3.10.0" +groups = ["main"] files = [ - {file = "openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8"}, - {file = "openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804"}, + {file = "openapi_schema_validator-0.8.1-py3-none-any.whl", hash = "sha256:0f5859794c5bfa433d478dc5ac5e5768d50adc56b14380c8a6fd3a8113e89c9b"}, + {file = "openapi_schema_validator-0.8.1.tar.gz", hash = "sha256:4c57266ce8cbfa37bb4eb4d62cdb7d19356c3a468e3535743c4562863e1790da"}, ] [package.dependencies] jsonschema = ">=4.19.1,<5.0.0" -jsonschema-specifications = ">=2023.5.2,<2024.0.0" +jsonschema-specifications = ">=2024.10.1" +pydantic = ">=2.0.0,<3.0.0" +pydantic-settings = ">=2.0.0,<3.0.0" +referencing = ">=0.37.0,<0.38.0" rfc3339-validator = "*" +[package.extras] +ecma-regex = ["regress (>=2025.10.1)"] + [[package]] name = "openapi-spec-validator" -version = "0.7.1" +version = "0.8.4" description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator" optional = false -python-versions = ">=3.8.0,<4.0.0" +python-versions = "<4.0,>=3.10" +groups = ["main"] files = [ - {file = "openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959"}, - {file = "openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7"}, + {file = "openapi_spec_validator-0.8.4-py3-none-any.whl", hash = "sha256:cf905117063d7c4d495c8a5a167a1f2a8006da6ffa8ba234a7ed0d0f11454d51"}, + {file = "openapi_spec_validator-0.8.4.tar.gz", hash = "sha256:8bb324b9b08b9b368b1359dec14610c60a8f3a3dd63237184eb04456d4546f49"}, ] [package.dependencies] -importlib-resources = {version = ">=5.8,<7.0", markers = "python_version < \"3.9\""} -jsonschema = ">=4.18.0,<5.0.0" -jsonschema-path = ">=0.3.1,<0.4.0" -lazy-object-proxy = ">=1.7.1,<2.0.0" -openapi-schema-validator = ">=0.6.0,<0.7.0" +jsonschema = ">=4.24.0,<4.25.0" +jsonschema-path = ">=0.4.3,<0.5.0" +lazy-object-proxy = ">=1.7.1,<2.0" +openapi-schema-validator = ">=0.7.3,<0.9.0" +pydantic = ">=2.0.0,<3.0.0" +pydantic-settings = ">=2.0.0,<3.0.0" [[package]] name = "packaging" @@ -1448,65 +2417,89 @@ version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] -name = "parse" -version = "1.20.2" -description = "parse() is the opposite of format()" +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" optional = false python-versions = "*" +groups = ["docs"] files = [ - {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, - {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, ] +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + +[[package]] +name = "parso" +version = "0.8.6" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff"}, + {file = "parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "types-setuptools (==67.2.0.1)", "zuban (==0.5.1)"] +testing = ["docopt", "pytest"] + [[package]] name = "pathable" -version = "0.4.3" +version = "0.5.0" description = "Object-oriented paths" optional = false -python-versions = ">=3.7.0,<4.0.0" +python-versions = "<4.0,>=3.10" +groups = ["main"] files = [ - {file = "pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14"}, - {file = "pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab"}, + {file = "pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6"}, + {file = "pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1"}, ] [[package]] name = "pathspec" -version = "0.11.2" +version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] -name = "pep8" -version = "1.7.1" -description = "Python style guide checker" +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ - {file = "pep8-1.7.1-py2.py3-none-any.whl", hash = "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee"}, - {file = "pep8-1.7.1.tar.gz", hash = "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] -[[package]] -name = "pkgutil-resolve-name" -version = "1.3.10" -description = "Resolve a name to an object." -optional = false -python-versions = ">=3.6" -files = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] +[package.dependencies] +ptyprocess = ">=0.5" [[package]] name = "platformdirs" @@ -1514,6 +2507,7 @@ version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, @@ -1525,13 +2519,14 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1540,13 +2535,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.5.0" +version = "4.5.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, + {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, + {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, ] [package.dependencies] @@ -1556,169 +2552,408 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, + {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] +markers = {main = "extra == \"aiohttp\""} + +[[package]] +name = "psutil" +version = "7.2.2" +description = "Cross-platform lib for process and system monitoring." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, + {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, + {file = "psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63"}, + {file = "psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312"}, + {file = "psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b"}, + {file = "psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9"}, + {file = "psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00"}, + {file = "psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9"}, + {file = "psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a"}, + {file = "psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf"}, + {file = "psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1"}, + {file = "psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841"}, + {file = "psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486"}, + {file = "psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979"}, + {file = "psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9"}, + {file = "psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e"}, + {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8"}, + {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc"}, + {file = "psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988"}, + {file = "psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee"}, + {file = "psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372"}, +] + +[package.extras] +dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] +test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + +[[package]] +name = "pycparser" +version = "3.0" +description = "C parser in Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, +] + [[package]] name = "pydantic" -version = "2.4.2" +version = "2.12.5" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, - {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.10.1" -typing-extensions = ">=4.6.1" +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" -version = "2.10.1" -description = "" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, - {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, - {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, - {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, - {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, - {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, - {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, - {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, - {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, - {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, - {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, - {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, - {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, - {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, - {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, - {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydantic-extra-types" -version = "2.1.0" -description = "Extra Pydantic types." + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pydantic-settings" +version = "2.13.1" +description = "Settings management using Pydantic" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "pydantic_extra_types-2.1.0-py3-none-any.whl", hash = "sha256:1b8aa83a2986b0bc6a7179834fdb423c5e0bcef6b2b4cd9261bf753ad7dcc483"}, - {file = "pydantic_extra_types-2.1.0.tar.gz", hash = "sha256:d07b869e733d33712b07d6b8cd7b0223077c23ae5a1e23bd0699a00401259ec7"}, + {file = "pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237"}, + {file = "pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025"}, ] [package.dependencies] -pydantic = ">=2.0.3" +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" [package.extras] -all = ["phonenumbers (>=8,<9)", "pycountry (>=22,<23)"] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pyflakes" -version = "3.2.0" +version = "3.4.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, ] [[package]] @@ -1727,152 +2962,247 @@ version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] -plugins = ["importlib-metadata"] +plugins = ["importlib-metadata ; python_version < \"3.8\""] + +[[package]] +name = "pymdown-extensions" +version = "10.16.1" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.19.1)"] [[package]] name = "pytest" -version = "8.1.1" +version = "9.0.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.4,<2.0" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-aiohttp" -version = "1.0.5" +version = "1.1.0" description = "Pytest plugin for aiohttp support" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, - {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, + {file = "pytest_aiohttp-1.1.0-py3-none-any.whl", hash = "sha256:f39a11693a0dce08dd6c542d241e199dd8047a6e6596b2bcfa60d373f143456d"}, + {file = "pytest_aiohttp-1.1.0.tar.gz", hash = "sha256:147de8cb164f3fc9d7196967f109ab3c0b93ea3463ab50631e56438eab7b5adc"}, ] [package.dependencies] -aiohttp = ">=3.8.1" +aiohttp = ">=3.11.0b0" pytest = ">=6.1.0" pytest-asyncio = ">=0.17.2" [package.extras] -testing = ["coverage (==6.2)", "mypy (==0.931)"] +testing = ["coverage (==6.2)", "mypy (==1.12.1)"] [[package]] name = "pytest-asyncio" -version = "0.21.1" +version = "1.3.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, - {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, ] [package.dependencies] -pytest = ">=7.0.0" +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] -name = "pytest-cache" -version = "1.0" -description = "pytest plugin with mechanisms for caching across test runs" +name = "pytest-cov" +version = "7.0.0" +description = "Pytest plugin for measuring coverage." optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-cache-1.0.tar.gz", hash = "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9"}, + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, ] [package.dependencies] -execnet = ">=1.1.dev1" -pytest = ">=2.2" +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] -name = "pytest-cov" -version = "5.0.0" -description = "Pytest plugin for measuring coverage." +name = "pytest-flake8" +version = "1.3.0" +description = "pytest plugin to check FLAKE8 requirements" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest_flake8-1.3.0-py3-none-any.whl", hash = "sha256:de10517c59fce25c0a7abb2a2b2a9d0b0ceb59ff0add7fa8e654d613bb25e218"}, + {file = "pytest_flake8-1.3.0.tar.gz", hash = "sha256:88fb35562ce32d915c6ba41ef0d5e1cfcdd8ff884a32b7d46aa99fc77a3d1fe6"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" +flake8 = ">=4.0" +pytest = ">=7.0" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy"] [[package]] -name = "pytest-flake8" -version = "0.1" -description = "pytest plugin to check FLAKE8 requirements" +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev", "docs"] files = [ - {file = "pytest-flake8-0.1.tar.gz", hash = "sha256:6b30619538937f274a373ace5fe2895def15066f0d3bad5784458ae0bce61a60"}, - {file = "pytest_flake8-0.1-py2.py3-none-any.whl", hash = "sha256:d2ecd5343ae56b4ac27ffa09d88111cc97dd7fdbc881231dfcdbc852f9ea5121"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] -flake8 = ">=2.3" -pytest = ">=2.4.2" -pytest-cache = "*" +six = ">=1.5" [[package]] -name = "python-multipart" -version = "0.0.9" -description = "A streaming multipart parser for Python" +name = "python-dotenv" +version = "1.2.2" +description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, + {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, + {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, ] [package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] +cli = ["click (>=5.0)"] [[package]] -name = "pytz" -version = "2023.3.post1" -description = "World timezone definitions, modern and historical" +name = "python-multipart" +version = "0.0.22" +description = "A streaming multipart parser for Python" optional = false -python-versions = "*" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, + {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, ] +[[package]] +name = "pytokens" +version = "0.4.1" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, + {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, + {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, + {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, + {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, + {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, + {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, + {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, + {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, + {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, + {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, + {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, + {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, + {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, + {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, + {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, + {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, + {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, + {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, + {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, + {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, + {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, + {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, + {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, + {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, + {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main", "dev", "docs"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -1927,51 +3257,192 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +groups = ["docs"] +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "pyzmq" +version = "27.1.0" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386"}, + {file = "pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda"}, + {file = "pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f"}, + {file = "pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32"}, + {file = "pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394"}, + {file = "pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f"}, + {file = "pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97"}, + {file = "pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07"}, + {file = "pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496"}, + {file = "pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd"}, + {file = "pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf"}, + {file = "pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f"}, + {file = "pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5"}, + {file = "pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6"}, + {file = "pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e"}, + {file = "pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7"}, + {file = "pyzmq-27.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18339186c0ed0ce5835f2656cdfb32203125917711af64da64dbaa3d949e5a1b"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:753d56fba8f70962cd8295fb3edb40b9b16deaa882dd2b5a3a2039f9ff7625aa"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b721c05d932e5ad9ff9344f708c96b9e1a485418c6618d765fca95d4daacfbef"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be883ff3d722e6085ee3f4afc057a50f7f2e0c72d289fd54df5706b4e3d3a50"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e592db3a93128daf567de9650a2f3859017b3f7a66bc4ed6e4779d6034976f"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad68808a61cbfbbae7ba26d6233f2a4aa3b221de379ce9ee468aa7a83b9c36b0"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e2687c2d230e8d8584fbea433c24382edfeda0c60627aca3446aa5e58d5d1831"}, + {file = "pyzmq-27.1.0-cp38-cp38-win32.whl", hash = "sha256:a1aa0ee920fb3825d6c825ae3f6c508403b905b698b6460408ebd5bb04bbb312"}, + {file = "pyzmq-27.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:df7cd397ece96cf20a76fae705d40efbab217d217897a5053267cd88a700c266"}, + {file = "pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f"}, + {file = "pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78"}, + {file = "pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db"}, + {file = "pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50081a4e98472ba9f5a02850014b4c9b629da6710f8f14f3b15897c666a28f1b"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:510869f9df36ab97f89f4cff9d002a89ac554c7ac9cadd87d444aa4cf66abd27"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f8426a01b1c4098a750973c37131cf585f61c7911d735f729935a0c701b68d3"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726b6a502f2e34c6d2ada5e702929586d3ac948a4dbbb7fed9854ec8c0466027"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bd67e7c8f4654bef471c0b1ca6614af0b5202a790723a58b79d9584dc8022a78"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9"}, + {file = "pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + [[package]] name = "referencing" -version = "0.30.2" +version = "0.37.0" description = "JSON Referencing + Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, - {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, + {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, + {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, ] [package.dependencies] attrs = ">=22.2.0" rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "requests" -version = "2.32.3" +version = "2.33.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "dev", "docs"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b"}, + {file = "requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652"}, ] +markers = {main = "extra == \"requests\""} [package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +test = ["PySocks (>=1.5.6,!=1.5.7)", "pytest (>=3)", "pytest-cov", "pytest-httpbin (==2.1.0)", "pytest-mock", "pytest-xdist"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "requirements-parser" +version = "0.13.0" +description = "This is a small Python module for parsing Pip requirement files." +optional = false +python-versions = "<4.0,>=3.8" +groups = ["dev"] +files = [ + {file = "requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14"}, + {file = "requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418"}, +] + +[package.dependencies] +packaging = ">=23.2" [[package]] name = "responses" -version = "0.25.0" +version = "0.26.0" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "responses-0.25.0-py3-none-any.whl", hash = "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a"}, - {file = "responses-0.25.0.tar.gz", hash = "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66"}, + {file = "responses-0.26.0-py3-none-any.whl", hash = "sha256:03ec4409088cd5c66b71ecbbbd27fe2c58ddfad801c66203457b3e6a04868c37"}, + {file = "responses-0.26.0.tar.gz", hash = "sha256:c7f6923e6343ef3682816ba421c006626777893cb0d5e1434f674b649bac9eb4"}, ] [package.dependencies] @@ -1980,7 +3451,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "rfc3339-validator" @@ -1988,6 +3459,7 @@ version = "0.1.4" description = "A pure python RFC3339 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, @@ -1998,127 +3470,202 @@ six = "*" [[package]] name = "rpds-py" -version = "0.12.0" +version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "rpds_py-0.12.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46"}, - {file = "rpds_py-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de"}, - {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274"}, - {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f"}, - {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50"}, - {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e"}, - {file = "rpds_py-0.12.0-cp310-none-win32.whl", hash = "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29"}, - {file = "rpds_py-0.12.0-cp310-none-win_amd64.whl", hash = "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963"}, - {file = "rpds_py-0.12.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624"}, - {file = "rpds_py-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31"}, - {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c"}, - {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff"}, - {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2"}, - {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c"}, - {file = "rpds_py-0.12.0-cp311-none-win32.whl", hash = "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10"}, - {file = "rpds_py-0.12.0-cp311-none-win_amd64.whl", hash = "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa"}, - {file = "rpds_py-0.12.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534"}, - {file = "rpds_py-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7"}, - {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34"}, - {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206"}, - {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1"}, - {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b"}, - {file = "rpds_py-0.12.0-cp312-none-win32.whl", hash = "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978"}, - {file = "rpds_py-0.12.0-cp312-none-win_amd64.whl", hash = "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f"}, - {file = "rpds_py-0.12.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2"}, - {file = "rpds_py-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4"}, - {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5"}, - {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523"}, - {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56"}, - {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c"}, - {file = "rpds_py-0.12.0-cp38-none-win32.whl", hash = "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595"}, - {file = "rpds_py-0.12.0-cp38-none-win_amd64.whl", hash = "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543"}, - {file = "rpds_py-0.12.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a"}, - {file = "rpds_py-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81"}, - {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63"}, - {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4"}, - {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e"}, - {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0"}, - {file = "rpds_py-0.12.0-cp39-none-win32.whl", hash = "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31"}, - {file = "rpds_py-0.12.0-cp39-none-win_amd64.whl", hash = "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d"}, - {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa"}, - {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76"}, - {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4"}, - {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, + {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, + {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"}, + {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"}, + {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"}, + {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"}, + {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"}, + {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"}, + {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"}, + {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"}, + {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"}, + {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"}, + {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"}, + {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"}, + {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"}, + {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"}, + {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"}, + {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"}, + {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"}, + {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"}, + {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"}, + {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"}, + {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"}, + {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"}, + {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"}, + {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"}, + {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"}, + {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"}, + {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"}, + {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"}, + {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"}, + {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"}, + {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"}, + {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"}, + {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"}, + {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"}, + {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"}, + {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, +] + +[[package]] +name = "schema" +version = "0.7.8" +description = "Simple data validation library" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "schema-0.7.8-py2.py3-none-any.whl", hash = "sha256:00bd977fadc7d9521bf289850cd8a8aa5f4948f575476b8daaa5c1b57af2dce1"}, + {file = "schema-0.7.8.tar.gz", hash = "sha256:e86cc08edd6fe6e2522648f4e47e3a31920a76e82cce8937535422e310862ab5"}, ] [[package]] name = "setuptools" -version = "68.2.2" +version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] +markers = {docs = "python_version >= \"3.12\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -2126,6 +3673,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "dev", "docs"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2137,237 +3685,201 @@ version = "1.3.0" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +markers = {main = "extra == \"fastapi\" or extra == \"starlette\""} [[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "sphinx" -version = "7.1.2" -description = "Python documentation generator" +name = "sqlparse" +version = "0.5.4" +description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, - {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, + {file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"}, + {file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"}, ] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +markers = {main = "extra == \"django\""} [package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] +dev = ["build"] +doc = ["sphinx"] [[package]] -name = "sphinx-immaterial" -version = "0.11.11" -description = "Adaptation of mkdocs-material theme for the Sphinx documentation system" +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" optional = false -python-versions = ">=3.8" +python-versions = "*" +groups = ["dev"] files = [ - {file = "sphinx_immaterial-0.11.11-py3-none-any.whl", hash = "sha256:174c7ce0f39c87413af0fbff17d0821e2f1fbae0d578b7074c9a6e17f1ef295c"}, - {file = "sphinx_immaterial-0.11.11.tar.gz", hash = "sha256:f98f3019cbb1d77b259c7ab09dc977ca220dff931dd0f2fabee76e9f9c77b3b4"}, + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] -appdirs = "*" -markupsafe = "*" -pydantic = ">=2.4" -pydantic-extra-types = "*" -requests = "*" -sphinx = ">=4.5" -typing-extensions = "*" +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" [package.extras] -clang-format = ["clang-format"] -cpp = ["libclang"] -json = ["pyyaml"] -jsonschema-validation = ["jsonschema"] -keys = ["pymdown-extensions"] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.8" +name = "starlette" +version = "1.0.0" +description = "The little ASGI library that shines." +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"fastapi\" or extra == \"starlette\"" files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, + {file = "starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"}, + {file = "starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149"}, ] +[package.dependencies] +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} + [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +name = "strict-rfc3339" +version = "0.7" +description = "Strict, simple, lightweight RFC3339 functions" optional = false -python-versions = ">=3.5" +python-versions = "*" +groups = ["dev"] files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, + {file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"}, ] -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] +widechars = ["wcwidth"] [[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" +name = "tbump" +version = "6.11.0" +description = "Bump software releases" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7,<4.0" +groups = ["dev"] files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, + {file = "tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526"}, + {file = "tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870"}, ] -[package.extras] -test = ["flake8", "mypy", "pytest"] +[package.dependencies] +cli-ui = ">=0.10.3" +docopt = ">=0.6.2,<0.7.0" +schema = ">=0.7.1,<0.8.0" +tomlkit = ">=0.11,<0.12" [[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] -name = "sqlparse" -version = "0.5.0" -description = "A non-validating SQL parser." +name = "tornado" +version = "6.5.5" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"}, - {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"}, + {file = "tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa"}, + {file = "tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521"}, + {file = "tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5"}, + {file = "tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07"}, + {file = "tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e"}, + {file = "tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca"}, + {file = "tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7"}, + {file = "tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b"}, + {file = "tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6"}, + {file = "tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9"}, ] -[package.extras] -dev = ["build", "hatch"] -doc = ["sphinx"] - [[package]] -name = "starlette" -version = "0.32.0.post1" -description = "The little ASGI library that shines." +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "starlette-0.32.0.post1-py3-none-any.whl", hash = "sha256:cd0cb10ddb49313f609cedfac62c8c12e56c7314b66d89bb077ba228bada1b09"}, - {file = "starlette-0.32.0.post1.tar.gz", hash = "sha256:e54e2b7e2fb06dff9eac40133583f10dfa05913f5a85bf26f427c7a40a9a3d02"}, + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] -name = "strict-rfc3339" -version = "0.7" -description = "Strict, simple, lightweight RFC3339 functions" +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["main", "dev", "docs"] files = [ - {file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] -[[package]] -name = "typing-extensions" -version = "4.8.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, -] +[package.dependencies] +typing-extensions = ">=4.12.0" [[package]] name = "tzdata" @@ -2375,192 +3887,272 @@ version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "dev"] files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] +markers = {main = "extra == \"django\" and sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} + +[[package]] +name = "unidecode" +version = "1.4.0" +description = "ASCII transliterations of Unicode text" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021"}, + {file = "Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23"}, +] [[package]] name = "urllib3" -version = "2.0.7" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "dev", "docs"] files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] +markers = {main = "extra == \"requests\""} [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "20.24.6" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, - {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} +platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "watchdog" +version = "4.0.2" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, + {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, + {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, + {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, + {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, +] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.6.0" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad"}, + {file = "wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159"}, +] [[package]] name = "webob" -version = "1.8.7" +version = "1.8.9" description = "WSGI request and response object" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ - {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"}, - {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"}, + {file = "WebOb-1.8.9-py2.py3-none-any.whl", hash = "sha256:45e34c58ed0c7e2ecd238ffd34432487ff13d9ad459ddfd77895e67abba7c1f9"}, + {file = "webob-1.8.9.tar.gz", hash = "sha256:ad6078e2edb6766d1334ec3dee072ac6a7f95b1e32ce10def8ff7f0f02d56589"}, ] +[package.dependencies] +legacy-cgi = {version = ">=2.6", markers = "python_version >= \"3.13\""} + [package.extras] docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "werkzeug" -version = "3.0.3" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] -MarkupSafe = ">=2.1.1" +markupsafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] [[package]] name = "yarl" -version = "1.9.2" +version = "1.18.3" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] +markers = {main = "extra == \"aiohttp\""} [package.dependencies] idna = ">=2.0" multidict = ">=4.0" - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +propcache = ">=0.2.0" [extras] aiohttp = ["aiohttp", "multidict"] django = ["django"] falcon = ["falcon"] -fastapi = ["fastapi"] +fastapi = ["aioitertools", "fastapi"] flask = ["flask"] requests = ["requests"] starlette = ["aioitertools", "starlette"] +werkzeug = [] [metadata] -lock-version = "2.0" -python-versions = "^3.8.0" -content-hash = "71044a8cb0eff35c69c9b4e38849687d8b11814d2df86f5b547e786e7defa070" +lock-version = "2.1" +python-versions = "^3.10.0" +content-hash = "d7e0dda94eff30865daa9fe709e2d396bf6f1fa9a96ce049a577aa5f94b2e5f2" diff --git a/pyproject.toml b/pyproject.toml index d28084ee..81353c5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ module = [ "isodate.*", "jsonschema.*", "more_itertools.*", - "parse.*", "requests.*", "werkzeug.*", ] @@ -33,11 +32,11 @@ ignore_missing_imports = true [tool.poetry] name = "openapi-core" -version = "0.19.2" +version = "0.23.1" description = "client-side and server-side support for the OpenAPI Specification v3" authors = ["Artur Maciag "] license = "BSD-3-Clause" -readme = "README.rst" +readme = "README.md" repository = "https://github.com/python-openapi/openapi-core" documentation = "https://openapi-core.readthedocs.io" keywords = ["openapi", "swagger", "schema"] @@ -47,11 +46,11 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", "Typing :: Typed", ] @@ -60,62 +59,60 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.8.0" -django = {version = ">=3.0", optional = true} -falcon = {version = ">=3.0", optional = true} -flask = {version = "*", optional = true} +python = "^3.10.0" +django = {version = ">=4.0", optional = true} +falcon = {version = ">=4.0", optional = true} +flask = {version = ">=2.0", optional = true} aiohttp = {version = ">=3.0", optional = true} -starlette = {version = ">=0.26.1,<0.38.0", optional = true} +starlette = {version = ">=0.40.0,<1.1.0", optional = true} isodate = "*" more-itertools = "*" -parse = "*" -openapi-schema-validator = "^0.6.0" -openapi-spec-validator = "^0.7.1" +openapi-schema-validator = ">=0.7.0,<0.9.0" +openapi-spec-validator = "^0.8.0" requests = {version = "*", optional = true} -werkzeug = "*" -jsonschema-path = "^0.3.1" -jsonschema = "^4.18.0" +werkzeug = ">=2.1.0" +jsonschema-path = "^0.4.5" +jsonschema = "^4.23.0" multidict = {version = "^6.0.4", optional = true} -aioitertools = {version = "^0.11.0", optional = true} -fastapi = {version = "^0.108.0", optional = true} +aioitertools = {version = ">=0.11,<0.14", optional = true} +fastapi = {version = ">=0.111,<0.140", optional = true} +typing-extensions = "^4.8.0" [tool.poetry.extras] django = ["django"] falcon = ["falcon"] -fastapi = ["fastapi"] +fastapi = ["fastapi", "aioitertools"] flask = ["flask"] requests = ["requests"] aiohttp = ["aiohttp", "multidict"] starlette = ["starlette", "aioitertools"] +werkzeug = [] [tool.poetry.group.dev.dependencies] -black = ">=23.3,<25.0" -django = ">=3.0" +black = ">=23.3,<27.0" djangorestframework = "^3.11.2" -falcon = ">=3.0" -flask = "*" -isort = "^5.11.5" +isort = ">=5.11.5,<9.0.0" pre-commit = "*" -pytest = "^8" +pytest = ">=8,<10" pytest-flake8 = "*" pytest-cov = "*" python-multipart = "*" responses = "*" -starlette = ">=0.26.1,<0.38.0" strict-rfc3339 = "^0.7" webob = "*" mypy = "^1.2" -httpx = ">=0.24,<0.27" -deptry = ">=0.11,<0.17" -aiohttp = "^3.8.4" -pytest-aiohttp = "^1.0.4" -bump2version = "^1.0.1" +httpx = ">=0.24,<0.29" +deptry = ">=0.11,<0.25" +pytest-aiohttp = "^1.1.0" pyflakes = "^3.1.0" -fastapi = "^0.108.0" +tbump = "^6.11.0" +ipykernel = "^7.2.0" [tool.poetry.group.docs.dependencies] -sphinx = ">=5.3,<8.0" -sphinx-immaterial = "^0.11.0" +mkdocs = "^1.6.1" +mkdocstrings = {extras = ["python"], version = ">=0.26.1,<1.1.0"} +mkdocs-material = "^9.5.34" +griffe-typingdoc = ">=0.2.7,<0.3.0" [tool.pytest.ini_options] addopts = """ @@ -127,7 +124,7 @@ addopts = """ --cov-report=term-missing --cov-report=xml """ -asyncio_mode = "auto" +asyncio_mode = "strict" filterwarnings = [ "error", # falcon.media.handlers uses cgi to parse data @@ -142,3 +139,113 @@ line-length = 79 profile = "black" line_length = 79 force_single_line = true + +[tool.tbump] +github_url = "https://github.com/python-openapi/openapi-core" + +[tool.tbump.version] +current = "0.23.1" +regex = ''' + (?P\d+) + \. + (?P\d+) + \. + (?P\d+) + (?P[a-z]+\d+)? + ''' + +[tool.tbump.git] +message_template = "Version {new_version}" +tag_template = "{new_version}" + +[[tool.tbump.file]] +src = "openapi_core/__init__.py" + +[[tool.tbump.file]] +src = "pyproject.toml" + +[tool.tox] +min_version = "4.21" +env_list = [ + "contrib-aiohttp-3x", + "contrib-aiohttp-311plus", + "contrib-django-4x", + "contrib-django-5x", + "contrib-django-6x", + "contrib-falcon-4x", + "contrib-fastapi-011x", + "contrib-fastapi-012x", + "contrib-fastapi-013x", + "contrib-flask-2x", + "contrib-flask-3x", + "contrib-requests-default", + "contrib-starlette-04x", + "contrib-starlette-05x", + "contrib-starlette-1x", + "contrib-werkzeug-default", +] +isolated_build = true +skip_missing_interpreters = false + +[tool.tox.env_run_base] +description = "Run contrib integration and unit tests for selected framework range." +package = "skip" +allowlist_externals = ["poetry"] +labels = ["contrib"] +commands_pre = [ + ["python", "-c", "import os,subprocess; extra=os.environ.get('POETRY_EXTRA',''); cmd=['poetry','install','--with','dev','--without','docs','--no-interaction']; extra and cmd.extend(['--extras', extra]); env=dict(os.environ); env['POETRY_VIRTUALENVS_CREATE']='false'; env['POETRY_CACHE_DIR']='.tox/.poetry-cache/' + os.environ.get('TOX_ENV_NAME', 'default'); subprocess.check_call(cmd, env=env)"], + ["python", "-c", "import os,subprocess,sys; pkg=os.environ['CONTRIB_PACKAGE']; spec=os.environ.get('CONTRIB_SPEC',''); target=f'{pkg}{spec}'; label=target if spec else 'from-lock'; print(f'framework target: {label}'); spec and subprocess.check_call([sys.executable,'-m','pip','install','--upgrade','--force-reinstall','--no-cache-dir',target])"], + ["python", "-m", "pip", "check"], + ["python", "-c", "import importlib, os; name=os.environ['CONTRIB_PACKAGE']; mod=importlib.import_module(name); print(name, getattr(mod, '__version__', 'unknown'))"], +] +commands = [ + ["python", "-c", "import os,shlex,subprocess,sys; paths=shlex.split(os.environ['CONTRIB_PATHS']); cmd=[sys.executable,'-m','pytest','--override-ini','addopts=','--capture=no','--verbose','--showlocals','--color=yes','--junitxml=reports/junit-{env_name}.xml','--cov=openapi_core','--cov-report=term-missing','--cov-report=xml:reports/coverage-{env_name}.xml',*paths]; env=dict(os.environ); env['COVERAGE_FILE']='.coverage.{env_name}'; raise SystemExit(subprocess.call(cmd, env=env))"], +] + +[tool.tox.env."contrib-aiohttp-3x"] +set_env = { POETRY_EXTRA = "aiohttp", CONTRIB_PACKAGE = "aiohttp", CONTRIB_SPEC = ">=3.8,<4.0", CONTRIB_PATHS = "tests/integration/contrib/aiohttp tests/unit/contrib/aiohttp" } + +[tool.tox.env."contrib-aiohttp-311plus"] +set_env = { POETRY_EXTRA = "aiohttp", CONTRIB_PACKAGE = "aiohttp", CONTRIB_SPEC = ">=3.11,<4.0", CONTRIB_PATHS = "tests/integration/contrib/aiohttp tests/unit/contrib/aiohttp" } + +[tool.tox.env."contrib-django-4x"] +set_env = { POETRY_EXTRA = "django", CONTRIB_PACKAGE = "django", CONTRIB_SPEC = ">=4.0,<5.0", CONTRIB_PATHS = "tests/integration/contrib/django tests/unit/contrib/django" } + +[tool.tox.env."contrib-django-5x"] +set_env = { POETRY_EXTRA = "django", CONTRIB_PACKAGE = "django", CONTRIB_SPEC = ">=5.0,<6.0", CONTRIB_PATHS = "tests/integration/contrib/django tests/unit/contrib/django" } + +[tool.tox.env."contrib-django-6x"] +set_env = { POETRY_EXTRA = "django", CONTRIB_PACKAGE = "django", CONTRIB_SPEC = ">=6.0,<7.0", CONTRIB_PATHS = "tests/integration/contrib/django tests/unit/contrib/django" } + +[tool.tox.env."contrib-falcon-4x"] +set_env = { POETRY_EXTRA = "falcon", CONTRIB_PACKAGE = "falcon", CONTRIB_SPEC = ">=4.0,<5.0", CONTRIB_PATHS = "tests/integration/contrib/falcon" } + +[tool.tox.env."contrib-fastapi-011x"] +set_env = { POETRY_EXTRA = "fastapi", CONTRIB_PACKAGE = "fastapi", CONTRIB_SPEC = ">=0.111,<0.120", CONTRIB_PATHS = "tests/integration/contrib/fastapi" } + +[tool.tox.env."contrib-fastapi-012x"] +set_env = { POETRY_EXTRA = "fastapi", CONTRIB_PACKAGE = "fastapi", CONTRIB_SPEC = ">=0.120,<0.130", CONTRIB_PATHS = "tests/integration/contrib/fastapi" } + +[tool.tox.env."contrib-fastapi-013x"] +set_env = { POETRY_EXTRA = "fastapi", CONTRIB_PACKAGE = "fastapi", CONTRIB_SPEC = ">=0.130,<0.140", CONTRIB_PATHS = "tests/integration/contrib/fastapi" } + +[tool.tox.env."contrib-flask-2x"] +set_env = { POETRY_EXTRA = "flask", CONTRIB_PACKAGE = "flask", CONTRIB_SPEC = ">=2.0,<3.0", CONTRIB_PATHS = "tests/integration/contrib/flask tests/unit/contrib/flask" } + +[tool.tox.env."contrib-flask-3x"] +set_env = { POETRY_EXTRA = "flask", CONTRIB_PACKAGE = "flask", CONTRIB_SPEC = ">=3.0,<4.0", CONTRIB_PATHS = "tests/integration/contrib/flask tests/unit/contrib/flask" } + +[tool.tox.env."contrib-requests-default"] +set_env = { POETRY_EXTRA = "requests", CONTRIB_PACKAGE = "requests", CONTRIB_SPEC = "", CONTRIB_PATHS = "tests/integration/contrib/requests tests/unit/contrib/requests" } + +[tool.tox.env."contrib-starlette-04x"] +set_env = { POETRY_EXTRA = "starlette", CONTRIB_PACKAGE = "starlette", CONTRIB_SPEC = ">=0.40.0,<0.50.0", CONTRIB_PATHS = "tests/integration/contrib/starlette" } + +[tool.tox.env."contrib-starlette-05x"] +set_env = { POETRY_EXTRA = "starlette", CONTRIB_PACKAGE = "starlette", CONTRIB_SPEC = ">=0.50.0,<0.60.0", CONTRIB_PATHS = "tests/integration/contrib/starlette" } + +[tool.tox.env."contrib-starlette-1x"] +set_env = { POETRY_EXTRA = "starlette", CONTRIB_PACKAGE = "starlette", CONTRIB_SPEC = ">=1.0.0,<2.0.0", CONTRIB_PATHS = "tests/integration/contrib/starlette" } + +[tool.tox.env."contrib-werkzeug-default"] +set_env = { CONTRIB_PACKAGE = "werkzeug", CONTRIB_SPEC = "", CONTRIB_PATHS = "tests/integration/contrib/werkzeug" } diff --git a/tests/benchmarks/bench_paths.py b/tests/benchmarks/bench_paths.py new file mode 100644 index 00000000..c567a237 --- /dev/null +++ b/tests/benchmarks/bench_paths.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +import argparse +import gc +import json +import random +import statistics +import time +from dataclasses import dataclass +from typing import Any +from typing import Dict +from typing import List + +from jsonschema_path import SchemaPath + +from openapi_core.templating.paths.finders import APICallPathFinder + + +@dataclass(frozen=True) +class Result: + paths: int + templates_ratio: float + lookups: int + repeats: int + warmup: int + seconds: List[float] + + def as_dict(self) -> Dict[str, Any]: + return { + "paths": self.paths, + "templates_ratio": self.templates_ratio, + "lookups": self.lookups, + "repeats": self.repeats, + "warmup": self.warmup, + "seconds": self.seconds, + "median_s": statistics.median(self.seconds), + "mean_s": statistics.mean(self.seconds), + "stdev_s": statistics.pstdev(self.seconds), + "ops_per_sec_median": self.lookups + / statistics.median(self.seconds), + } + + +def build_spec(paths: int, templates_ratio: float) -> SchemaPath: + # Mix of exact and templated paths. + # Keep it minimal so we measure finder cost, not schema complexity. + tmpl = int(paths * templates_ratio) + exact = paths - tmpl + + paths_obj: Dict[str, Any] = {} + + # Exact paths (fast case) + for i in range(exact): + p = f"/resource/{i}/sub" + paths_obj[p] = {"get": {"responses": {"200": {"description": "ok"}}}} + + # Template paths (slow case) + for i in range(tmpl): + p = f"/resource/{i}" + "/{item_id}/sub/{sub_id}" + paths_obj[p] = {"get": {"responses": {"200": {"description": "ok"}}}} + + spec_dict = { + "openapi": "3.0.0", + "info": {"title": "bench", "version": "0"}, + "servers": [{"url": "http://example.com"}], + "paths": paths_obj, + } + return SchemaPath.from_dict(spec_dict) + + +def build_urls( + paths: int, templates_ratio: float, lookups: int, seed: int +) -> List[str]: + rnd = random.Random(seed) + tmpl = int(paths * templates_ratio) + exact = paths - tmpl + + urls: List[str] = [] + for _ in range(lookups): + # 50/50 choose from each population, weighted by how many exist + if tmpl > 0 and (exact == 0 or rnd.random() < (tmpl / paths)): + i = rnd.randrange(tmpl) # matches template bucket + item_id = rnd.randrange(1_000_000) + sub_id = rnd.randrange(1_000_000) + urls.append( + f"http://example.com/resource/{i}/{item_id}/sub/{sub_id}" + ) + else: + i = rnd.randrange(exact) if exact > 0 else 0 + urls.append(f"http://example.com/resource/{i}/sub") + return urls + + +def run_once(finder: APICallPathFinder, urls: List[str]) -> float: + t0 = time.perf_counter() + for u in urls: + finder.find("get", u) + return time.perf_counter() - t0 + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--paths", type=int, default=2000) + ap.add_argument("--templates-ratio", type=float, default=0.6) + ap.add_argument("--lookups", type=int, default=100_000) + ap.add_argument("--repeats", type=int, default=7) + ap.add_argument("--warmup", type=int, default=2) + ap.add_argument("--seed", type=int, default=1) + ap.add_argument("--output", type=str, default="") + ap.add_argument("--no-gc", action="store_true") + args = ap.parse_args() + + spec = build_spec(args.paths, args.templates_ratio) + finder = APICallPathFinder(spec) + + urls = build_urls( + args.paths, args.templates_ratio, args.lookups, args.seed + ) + + if args.no_gc: + gc.disable() + + # Warmup (JIT-less, but warms caches, alloc patterns, etc.) + for _ in range(args.warmup): + run_once(finder, urls) + + seconds: List[float] = [] + for _ in range(args.repeats): + seconds.append(run_once(finder, urls)) + + if args.no_gc: + gc.enable() + + result = Result( + paths=args.paths, + templates_ratio=args.templates_ratio, + lookups=args.lookups, + repeats=args.repeats, + warmup=args.warmup, + seconds=seconds, + ) + + payload = result.as_dict() + print(json.dumps(payload, indent=2, sort_keys=True)) + + if args.output: + with open(args.output, "w", encoding="utf-8") as f: + json.dump(payload, f, indent=2, sort_keys=True) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index cea4a154..b9e5eff4 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -7,8 +7,6 @@ from openapi_spec_validator.readers import read_from_filename from yaml import safe_load -from openapi_core import Spec - def content_from_file(spec_file): directory = path.abspath(path.dirname(__file__)) @@ -27,27 +25,15 @@ def schema_path_from_url(base_uri): return SchemaPath.from_dict(spec_dict, base_uri=base_uri) -def spec_from_file(spec_file): - schema_path = schema_path_from_file(spec_file) - return Spec(schema_path) - - -def spec_from_url(base_uri): - schema_path = schema_path_from_url(base_uri) - return Spec(schema_path) - - @pytest.fixture(scope="session") def data_gif(): - return b64decode( - """ + return b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" - ) +""") class Factory(dict): @@ -70,14 +56,6 @@ def schema_path_factory(): ) -@pytest.fixture(scope="session") -def spec_factory(schema_path_factory): - return Factory( - from_file=spec_from_file, - from_url=spec_from_url, - ) - - @pytest.fixture(scope="session") def v30_petstore_content(content_factory): content, _ = content_factory.from_file("data/v3.0/petstore.yaml") diff --git a/tests/integration/contrib/aiohttp/conftest.py b/tests/integration/contrib/aiohttp/conftest.py index ead341a5..56a0c9c2 100644 --- a/tests/integration/contrib/aiohttp/conftest.py +++ b/tests/integration/contrib/aiohttp/conftest.py @@ -1,9 +1,11 @@ import asyncio import pathlib +from collections.abc import AsyncGenerator from typing import Any from unittest import mock import pytest +import pytest_asyncio from aiohttp import web from aiohttp.test_utils import TestClient @@ -114,6 +116,10 @@ def app(router): return app -@pytest.fixture -async def client(app, aiohttp_client) -> TestClient: - return await aiohttp_client(app) +@pytest_asyncio.fixture +async def client(app, aiohttp_client) -> AsyncGenerator[TestClient, None]: + test_client = await aiohttp_client(app) + try: + yield test_client + finally: + await test_client.close() diff --git a/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/__main__.py b/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/__main__.py index 13109d64..f6e9d9f3 100644 --- a/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/__main__.py +++ b/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/__main__.py @@ -6,8 +6,8 @@ ] -def get_app(loop=None): - app = web.Application(loop=loop) +def get_app(): + app = web.Application() app.add_routes(routes) return app diff --git a/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/views.py b/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/views.py index ad721df3..f017e438 100644 --- a/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/views.py +++ b/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/views.py @@ -8,15 +8,13 @@ class PetPhotoView(web.View): - OPENID_LOGO = b64decode( - """ + OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" - ) +""") async def get(self): request_body = await self.request.text() diff --git a/tests/integration/contrib/aiohttp/test_aiohttp_project.py b/tests/integration/contrib/aiohttp/test_aiohttp_project.py index 54f7297d..8aa01ff1 100644 --- a/tests/integration/contrib/aiohttp/test_aiohttp_project.py +++ b/tests/integration/contrib/aiohttp/test_aiohttp_project.py @@ -1,9 +1,13 @@ import os import sys from base64 import b64encode +from collections.abc import AsyncGenerator from io import BytesIO import pytest +import pytest_asyncio + +pytestmark = pytest.mark.asyncio @pytest.fixture(autouse=True, scope="session") @@ -22,9 +26,13 @@ def app(project_setup): return get_app() -@pytest.fixture -async def client(app, aiohttp_client): - return await aiohttp_client(app) +@pytest_asyncio.fixture +async def client(app, aiohttp_client) -> AsyncGenerator: + test_client = await aiohttp_client(app) + try: + yield test_client + finally: + await test_client.close() class BaseTestPetstore: diff --git a/tests/integration/contrib/aiohttp/test_aiohttp_validation.py b/tests/integration/contrib/aiohttp/test_aiohttp_validation.py index 134e530d..3d11ae8c 100644 --- a/tests/integration/contrib/aiohttp/test_aiohttp_validation.py +++ b/tests/integration/contrib/aiohttp/test_aiohttp_validation.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import TYPE_CHECKING from unittest import mock @@ -9,7 +7,10 @@ from aiohttp.test_utils import TestClient -async def test_aiohttp_integration_valid_input(client: TestClient): +pytestmark = pytest.mark.asyncio + + +async def test_aiohttp_integration_valid_input(client: "TestClient"): # Given given_query_string = { "q": "string", @@ -34,7 +35,9 @@ async def test_aiohttp_integration_valid_input(client: TestClient): assert response_data == expected_response_data -async def test_aiohttp_integration_invalid_server(client: TestClient, request): +async def test_aiohttp_integration_invalid_server( + client: "TestClient", request +): if "no_validation" in request.node.name: pytest.skip("No validation for given handler.") # Given @@ -71,7 +74,7 @@ async def test_aiohttp_integration_invalid_server(client: TestClient, request): async def test_aiohttp_integration_invalid_input( - client: TestClient, response_getter, request + client: "TestClient", response_getter, request ): if "no_validation" in request.node.name: pytest.skip("No validation for given handler.") diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py b/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py index 1cdb3c4e..78adc0d9 100644 --- a/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py +++ b/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py @@ -87,15 +87,13 @@ def get_extra_actions(): class PetPhotoView(APIView): - OPENID_LOGO = b64decode( - """ + OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" - ) +""") def get(self, request, petId): assert request.openapi diff --git a/openapi_core/spec/__init__.py b/tests/integration/contrib/django/data/v3.0/djangoproject/status/__init__.py similarity index 100% rename from openapi_core/spec/__init__.py rename to tests/integration/contrib/django/data/v3.0/djangoproject/status/__init__.py diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/status/migrations/__init__.py b/tests/integration/contrib/django/data/v3.0/djangoproject/status/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/status/views.py b/tests/integration/contrib/django/data/v3.0/djangoproject/status/views.py new file mode 100644 index 00000000..10d87749 --- /dev/null +++ b/tests/integration/contrib/django/data/v3.0/djangoproject/status/views.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from django.http import HttpResponse +from jsonschema_path import SchemaPath + +from openapi_core.contrib.django.decorators import DjangoOpenAPIViewDecorator + +check_minimal_spec = DjangoOpenAPIViewDecorator.from_spec( + SchemaPath.from_file_path( + Path("tests/integration/data/v3.0/minimal_with_servers.yaml") + ) +) + + +@check_minimal_spec +def get_status(request): + return HttpResponse("OK") diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py b/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py index ff987972..be4e9781 100644 --- a/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py +++ b/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py @@ -20,6 +20,7 @@ from djangoproject.pets.views import PetDetailView from djangoproject.pets.views import PetListView from djangoproject.pets.views import PetPhotoView +from djangoproject.status.views import get_status from djangoproject.tags.views import TagListView urlpatterns = [ @@ -48,4 +49,9 @@ TagListView.as_view(), name="tag_list_view", ), + path( + "status", + get_status, + name="get_status_view", + ), ] diff --git a/tests/integration/contrib/django/test_django_project.py b/tests/integration/contrib/django/test_django_project.py index 6614eeaf..8a0697e1 100644 --- a/tests/integration/contrib/django/test_django_project.py +++ b/tests/integration/contrib/django/test_django_project.py @@ -184,7 +184,7 @@ def test_post_media_type_invalid(self, client): "title": ( "Content for the following mimetype not found: " "text/html. " - "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']" + "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']" ), } ] @@ -422,3 +422,41 @@ def test_post_valid(self, client, data_gif): assert response.status_code == 201 assert not response.content + + +class TestStatusView(BaseTestDjangoProject): + + def test_get_valid(self, client, data_gif): + headers = { + "HTTP_AUTHORIZATION": "Basic testuser", + "HTTP_HOST": "petstore.swagger.io", + } + from django.conf import settings + + MIDDLEWARE = [ + v for v in settings.MIDDLEWARE if "openapi_core" not in v + ] + with override_settings(MIDDLEWARE=MIDDLEWARE): + response = client.get("/status", **headers) + + assert response.status_code == 200 + assert response.content.decode() == "OK" + + def test_post_valid(self, client): + data = {"key": "value"} + content_type = "application/json" + headers = { + "HTTP_AUTHORIZATION": "Basic testuser", + "HTTP_HOST": "petstore.swagger.io", + } + from django.conf import settings + + MIDDLEWARE = [ + v for v in settings.MIDDLEWARE if "openapi_core" not in v + ] + with override_settings(MIDDLEWARE=MIDDLEWARE): + response = client.post( + "/status", data=data, content_type=content_type, **headers + ) + + assert response.status_code == 405 # Method Not Allowed diff --git a/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py b/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py index d6e903da..c716580e 100644 --- a/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py +++ b/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py @@ -87,15 +87,13 @@ def on_get(self, request, response, petId=None): class PetPhotoResource: - OPENID_LOGO = b64decode( - """ + OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" - ) +""") def on_get(self, request, response, petId=None): response.content_type = MEDIA_JPEG diff --git a/tests/integration/contrib/falcon/test_falcon_asgi_middleware.py b/tests/integration/contrib/falcon/test_falcon_asgi_middleware.py new file mode 100644 index 00000000..eeca2e17 --- /dev/null +++ b/tests/integration/contrib/falcon/test_falcon_asgi_middleware.py @@ -0,0 +1,214 @@ +from json import dumps +from pathlib import Path +from typing import Any +from typing import cast + +import pytest +import yaml +from falcon import status_codes +from falcon.asgi import App +from falcon.asgi import Response +from falcon.constants import MEDIA_JSON +from falcon.testing import ASGIConductor +from jsonschema_path import SchemaPath + +from openapi_core.contrib.falcon.middlewares import FalconASGIOpenAPIMiddleware +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware +from openapi_core.contrib.falcon.requests import FalconAsgiOpenAPIRequest +from openapi_core.contrib.falcon.responses import FalconAsgiOpenAPIResponse +from openapi_core.contrib.falcon.util import serialize_body + + +@pytest.fixture +def spec(): + openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml") + spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader) + return SchemaPath.from_dict(spec_dict) + + +class PetListResource: + async def on_get(self, req, resp): + assert req.context.openapi + assert not req.context.openapi.errors + resp.status = status_codes.HTTP_200 + resp.content_type = MEDIA_JSON + resp.text = dumps( + { + "data": [ + { + "id": 12, + "name": "Cat", + "ears": { + "healthy": True, + }, + } + ] + } + ) + resp.set_header("X-Rate-Limit", "12") + + +class InvalidPetListResource: + async def on_get(self, req, resp): + assert req.context.openapi + assert not req.context.openapi.errors + resp.status = status_codes.HTTP_200 + resp.content_type = MEDIA_JSON + resp.text = dumps({"data": [{"id": "12", "name": 13}]}) + resp.set_header("X-Rate-Limit", "12") + + +class _AsyncStream: + def __init__(self, chunks): + self._chunks = chunks + self._index = 0 + + def __aiter__(self): + return self + + async def __anext__(self): + if self._index >= len(self._chunks): + raise StopAsyncIteration + + chunk = self._chunks[self._index] + self._index += 1 + return chunk + + +@pytest.mark.asyncio +async def test_dual_mode_sync_middleware_works_with_asgi_app(spec): + middleware = FalconOpenAPIMiddleware.from_spec(spec) + app = App(middleware=[middleware]) + app.add_route("/v1/pets", PetListResource()) + + async with ASGIConductor(app) as conductor: + with pytest.warns(DeprecationWarning): + response = await conductor.simulate_get( + "/v1/pets", + host="petstore.swagger.io", + query_string="limit=12", + ) + + assert response.status_code == 200 + assert response.json == { + "data": [ + { + "id": 12, + "name": "Cat", + "ears": { + "healthy": True, + }, + } + ] + } + + +@pytest.mark.asyncio +async def test_explicit_asgi_middleware_handles_request_validation(spec): + middleware = FalconASGIOpenAPIMiddleware.from_spec(spec) + app = App(middleware=[middleware]) + app.add_route("/v1/pets", PetListResource()) + + async with ASGIConductor(app) as conductor: + with pytest.warns(DeprecationWarning): + response = await conductor.simulate_get( + "/v1/pets", + host="petstore.swagger.io", + ) + + assert response.status_code == 400 + assert response.json == { + "errors": [ + { + "type": ( + "" + ), + "status": 400, + "title": "Missing required query parameter: limit", + } + ] + } + + +@pytest.mark.asyncio +async def test_explicit_asgi_middleware_validates_response(spec): + middleware = FalconASGIOpenAPIMiddleware.from_spec(spec) + app = App(middleware=[middleware]) + app.add_route("/v1/pets", InvalidPetListResource()) + + async with ASGIConductor(app) as conductor: + with pytest.warns(DeprecationWarning): + response = await conductor.simulate_get( + "/v1/pets", + host="petstore.swagger.io", + query_string="limit=12", + ) + + assert response.status_code == 400 + assert "errors" in response.json + + +@pytest.mark.asyncio +async def test_asgi_response_adapter_handles_stream_without_charset(): + chunks = [ + b'{"data": [', + b'{"id": 12, "name": "Cat", "ears": {"healthy": true}}', + b"]}", + ] + response = Response() + response.content_type = MEDIA_JSON + response.stream = _AsyncStream(chunks) + + openapi_response = await FalconAsgiOpenAPIResponse.from_response(response) + + assert openapi_response.data == b"".join(chunks) + assert response.stream is not None + + replayed_chunks = [] + async for chunk in response.stream: + replayed_chunks.append(chunk) + assert b"".join(replayed_chunks) == b"".join(chunks) + + +def test_asgi_request_body_cached_none_skips_media_deserialization(): + class _DummyRequest: + def get_media(self, *args, **kwargs): + raise AssertionError("get_media should not be called") + + openapi_request = object.__new__(FalconAsgiOpenAPIRequest) + openapi_request.request = cast(Any, _DummyRequest()) + openapi_request._body = None + + assert openapi_request.body is None + + +def test_multipart_unsupported_serialization_warns_and_returns_none(): + content_type = "multipart/form-data; boundary=test" + + class _DummyHandler: + def serialize(self, media, content_type): + raise NotImplementedError( + "multipart form serialization unsupported" + ) + + class _DummyMediaHandlers: + def _resolve(self, content_type, default_media_type): + return (_DummyHandler(), content_type, None) + + class _DummyOptions: + media_handlers = _DummyMediaHandlers() + default_media_type = MEDIA_JSON + + class _DummyRequest: + options = _DummyOptions() + + with pytest.warns( + UserWarning, + match="body serialization for multipart/form-data", + ): + body = serialize_body( + cast(Any, _DummyRequest()), {"name": "Cat"}, content_type + ) + + assert body is None diff --git a/tests/integration/contrib/falcon/test_falcon_project.py b/tests/integration/contrib/falcon/test_falcon_project.py index 69e11974..252e0d6a 100644 --- a/tests/integration/contrib/falcon/test_falcon_project.py +++ b/tests/integration/contrib/falcon/test_falcon_project.py @@ -2,6 +2,7 @@ from json import dumps import pytest +from urllib3 import encode_multipart_formdata class BaseTestFalconProject: @@ -204,7 +205,7 @@ def test_post_media_type_invalid(self, client): "title": ( "Content for the following mimetype not found: " f"{content_type}. " - "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']" + "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']" ), } ] @@ -292,6 +293,43 @@ def test_post_valid(self, client, data_json): assert response.status_code == 201 assert not response.content + @pytest.mark.xfail( + reason="falcon multipart form serialization unsupported", + strict=True, + ) + def test_post_multipart_valid(self, client, data_gif): + cookies = {"user": 1} + auth = "authuser" + fields = { + "name": "Cat", + "address": ( + "aaddress.json", + dumps(dict(city="Warsaw")), + "application/json", + ), + "photo": ( + "photo.jpg", + data_gif, + "image/jpeg", + ), + } + body, content_type_header = encode_multipart_formdata(fields) + headers = { + "Authorization": f"Basic {auth}", + "Content-Type": content_type_header, + } + + response = client.simulate_post( + "/v1/pets", + host="staging.gigantic-server.com", + headers=headers, + body=body, + cookies=cookies, + protocol="https", + ) + + assert response.status_code == 200 + class TestPetDetailResource: def test_get_server_invalid(self, client): diff --git a/tests/integration/contrib/falcon/test_falcon_wsgi_middleware.py b/tests/integration/contrib/falcon/test_falcon_wsgi_middleware.py new file mode 100644 index 00000000..d130ebc5 --- /dev/null +++ b/tests/integration/contrib/falcon/test_falcon_wsgi_middleware.py @@ -0,0 +1,68 @@ +from json import dumps +from pathlib import Path + +import pytest +import yaml +from falcon import App +from falcon.constants import MEDIA_JSON +from falcon.status_codes import HTTP_200 +from falcon.testing import TestClient +from jsonschema_path import SchemaPath + +from openapi_core.contrib.falcon.middlewares import FalconWSGIOpenAPIMiddleware + + +@pytest.fixture +def spec(): + openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml") + spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader) + return SchemaPath.from_dict(spec_dict) + + +class PetListResource: + def on_get(self, req, resp): + assert req.context.openapi + assert not req.context.openapi.errors + resp.status = HTTP_200 + resp.content_type = MEDIA_JSON + resp.text = dumps( + { + "data": [ + { + "id": 12, + "name": "Cat", + "ears": { + "healthy": True, + }, + } + ] + } + ) + resp.set_header("X-Rate-Limit", "12") + + +def test_explicit_wsgi_middleware_works(spec): + middleware = FalconWSGIOpenAPIMiddleware.from_spec(spec) + app = App(middleware=[middleware]) + app.add_route("/v1/pets", PetListResource()) + client = TestClient(app) + + with pytest.warns(DeprecationWarning): + response = client.simulate_get( + "/v1/pets", + host="petstore.swagger.io", + query_string="limit=12", + ) + + assert response.status_code == 200 + assert response.json == { + "data": [ + { + "id": 12, + "name": "Cat", + "ears": { + "healthy": True, + }, + } + ] + } diff --git a/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers/pets.py b/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers/pets.py index d4b763b9..48650ef0 100644 --- a/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers/pets.py +++ b/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers/pets.py @@ -11,15 +11,13 @@ except ImportError: from typing_extensions import Annotated -OPENID_LOGO = b64decode( - """ +OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" -) +""") router = APIRouter( diff --git a/tests/integration/contrib/fastapi/test_fastapi_project.py b/tests/integration/contrib/fastapi/test_fastapi_project.py index e8d795c6..c43e0950 100644 --- a/tests/integration/contrib/fastapi/test_fastapi_project.py +++ b/tests/integration/contrib/fastapi/test_fastapi_project.py @@ -24,7 +24,8 @@ def app(): @pytest.fixture def client(app): - return TestClient(app, base_url="http://petstore.swagger.io") + with TestClient(app, base_url="http://petstore.swagger.io") as test_client: + yield test_client class BaseTestPetstore: @@ -183,7 +184,7 @@ def test_post_media_type_invalid(self, client): "title": ( "Content for the following mimetype not found: " "text/html. " - "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']" + "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']" ), } ] diff --git a/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/views.py b/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/views.py index f9b55a03..1f396730 100644 --- a/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/views.py +++ b/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/views.py @@ -9,15 +9,13 @@ class PetPhotoView(FlaskOpenAPIView): - OPENID_LOGO = b64decode( - """ + OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" - ) +""") def get(self, petId): fp = BytesIO(self.OPENID_LOGO) diff --git a/tests/integration/contrib/starlette/data/v3.0/starletteproject/__main__.py b/tests/integration/contrib/starlette/data/v3.0/starletteproject/__main__.py index 79b47802..27e37192 100644 --- a/tests/integration/contrib/starlette/data/v3.0/starletteproject/__main__.py +++ b/tests/integration/contrib/starlette/data/v3.0/starletteproject/__main__.py @@ -5,6 +5,7 @@ from starletteproject.pets.endpoints import pet_detail_endpoint from starletteproject.pets.endpoints import pet_list_endpoint from starletteproject.pets.endpoints import pet_photo_endpoint +from starletteproject.tags.endpoints import tag_list_endpoint from openapi_core.contrib.starlette.middlewares import ( StarletteOpenAPIMiddleware, @@ -16,6 +17,13 @@ openapi=openapi, ), ] +middleware_skip_response = [ + Middleware( + StarletteOpenAPIMiddleware, + openapi=openapi, + response_cls=None, + ), +] routes = [ Route("/v1/pets", pet_list_endpoint, methods=["GET", "POST"]), @@ -23,6 +31,7 @@ Route( "/v1/pets/{petId}/photo", pet_photo_endpoint, methods=["GET", "POST"] ), + Route("/v1/tags", tag_list_endpoint, methods=["GET"]), ] app = Starlette( @@ -30,3 +39,8 @@ middleware=middleware, routes=routes, ) +app_skip_response = Starlette( + debug=True, + middleware=middleware_skip_response, + routes=routes, +) diff --git a/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets/endpoints.py b/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets/endpoints.py index b17b3029..9a2ce5eb 100644 --- a/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets/endpoints.py +++ b/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets/endpoints.py @@ -4,15 +4,13 @@ from starlette.responses import Response from starlette.responses import StreamingResponse -OPENID_LOGO = b64decode( - """ +OPENID_LOGO = b64decode(""" R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d 3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD Fzk0lpcjIQA7 -""" -) +""") async def pet_list_endpoint(request): diff --git a/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags/__init__.py b/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags/endpoints.py b/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags/endpoints.py new file mode 100644 index 00000000..62cd6094 --- /dev/null +++ b/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags/endpoints.py @@ -0,0 +1,11 @@ +from starlette.responses import Response + + +async def tag_list_endpoint(request): + assert request.scope["openapi"] + assert not request.scope["openapi"].errors + assert request.method == "GET" + headers = { + "X-Rate-Limit": "12", + } + return Response(status_code=201, headers=headers) diff --git a/tests/integration/contrib/starlette/test_starlette_project.py b/tests/integration/contrib/starlette/test_starlette_project.py index fc799a30..0207316d 100644 --- a/tests/integration/contrib/starlette/test_starlette_project.py +++ b/tests/integration/contrib/starlette/test_starlette_project.py @@ -15,20 +15,22 @@ def project_setup(): sys.path.remove(project_dir) -@pytest.fixture -def app(): - from starletteproject.__main__ import app - - return app - +class BaseTestPetstore: + api_key = "12345" -@pytest.fixture -def client(app): - return TestClient(app, base_url="http://petstore.swagger.io") + @pytest.fixture + def app(self): + from starletteproject.__main__ import app + return app -class BaseTestPetstore: - api_key = "12345" + @pytest.fixture + def client(self, app): + with TestClient( + app, + base_url="http://petstore.swagger.io", + ) as test_client: + yield test_client @property def api_key_encoded(self): @@ -37,6 +39,23 @@ def api_key_encoded(self): return str(api_key_bytes_enc, "utf8") +class BaseTestPetstoreSkipReponse: + + @pytest.fixture + def app(self): + from starletteproject.__main__ import app_skip_response + + return app_skip_response + + @pytest.fixture + def client(self, app): + with TestClient( + app, + base_url="http://petstore.swagger.io", + ) as test_client: + yield test_client + + class TestPetListEndpoint(BaseTestPetstore): def test_get_no_required_param(self, client): headers = { @@ -183,7 +202,7 @@ def test_post_media_type_invalid(self, client): "title": ( "Content for the following mimetype not found: " "text/html. " - "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']" + "Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']" ), } ] @@ -381,3 +400,43 @@ def test_post_valid(self, client, data_gif): assert not response.text assert response.status_code == 201 + + +class TestTagListEndpoint(BaseTestPetstore): + + def test_get_invalid(self, client): + headers = { + "Authorization": "Basic testuser", + } + + response = client.get( + "/v1/tags", + headers=headers, + ) + + assert response.status_code == 400 + assert response.json() == { + "errors": [ + { + "title": "Missing response data", + "status": 400, + "type": "", + }, + ], + } + + +class TestSkipResponseTagListEndpoint(BaseTestPetstoreSkipReponse): + + def test_get_valid(self, client): + headers = { + "Authorization": "Basic testuser", + } + + response = client.get( + "/v1/tags", + headers=headers, + ) + + assert not response.text + assert response.status_code == 201 diff --git a/tests/integration/contrib/starlette/test_starlette_validation.py b/tests/integration/contrib/starlette/test_starlette_validation.py index 6bebcfbb..03c42b63 100644 --- a/tests/integration/contrib/starlette/test_starlette_validation.py +++ b/tests/integration/contrib/starlette/test_starlette_validation.py @@ -43,7 +43,8 @@ async def test_route(scope, receive, send): @pytest.fixture def client(self, app): - return TestClient(app, base_url="http://localhost") + with TestClient(app, base_url="http://localhost") as test_client: + yield test_client def test_request_validator_path_pattern(self, client, schema_path): response_data = {"data": "data"} @@ -65,18 +66,18 @@ async def test_route(request): Route("/browse/12/", test_route, methods=["POST"]), ], ) - client = TestClient(app, base_url="http://localhost") - query_string = { - "q": "string", - } - headers = {"content-type": "application/json"} - data = {"param1": 1} - response = client.post( - "/browse/12/", - params=query_string, - json=data, - headers=headers, - ) + with TestClient(app, base_url="http://localhost") as client: + query_string = { + "q": "string", + } + headers = {"content-type": "application/json"} + data = {"param1": 1} + response = client.post( + "/browse/12/", + params=query_string, + json=data, + headers=headers, + ) assert response.status_code == 200 assert response.json() == response_data @@ -104,18 +105,18 @@ def test_route(request): Route("/browse/12/", test_route, methods=["POST"]), ], ) - client = TestClient(app, base_url="http://localhost") - query_string = { - "q": "string", - } - headers = {"content-type": "application/json"} - data = {"param1": 1} - response = client.post( - "/browse/12/", - params=query_string, - json=data, - headers=headers, - ) + with TestClient(app, base_url="http://localhost") as client: + query_string = { + "q": "string", + } + headers = {"content-type": "application/json"} + data = {"param1": 1} + response = client.post( + "/browse/12/", + params=query_string, + json=data, + headers=headers, + ) assert response.status_code == 200 assert response.json() == response_data diff --git a/tests/integration/data/v3.0/parent-reference/openapi.yaml b/tests/integration/data/v3.0/parent-reference/openapi.yaml new file mode 100644 index 00000000..51150416 --- /dev/null +++ b/tests/integration/data/v3.0/parent-reference/openapi.yaml @@ -0,0 +1,7 @@ +openapi: "3.0.0" +info: + title: sample + version: "0.1" +paths: + /books: + $ref: "./paths/books.yaml" \ No newline at end of file diff --git a/tests/integration/data/v3.0/parent-reference/paths/books.yaml b/tests/integration/data/v3.0/parent-reference/paths/books.yaml new file mode 100644 index 00000000..d625f4f5 --- /dev/null +++ b/tests/integration/data/v3.0/parent-reference/paths/books.yaml @@ -0,0 +1,10 @@ +get: + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "../schemas/book.yaml#/Book" \ No newline at end of file diff --git a/tests/integration/data/v3.0/parent-reference/schemas/book.yaml b/tests/integration/data/v3.0/parent-reference/schemas/book.yaml new file mode 100644 index 00000000..1bf35402 --- /dev/null +++ b/tests/integration/data/v3.0/parent-reference/schemas/book.yaml @@ -0,0 +1,9 @@ +Book: + type: object + properties: + id: + $ref: "#/BookId" + title: + type: string +BookId: + type: string \ No newline at end of file diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index d26816ac..e0ea394f 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -150,6 +150,9 @@ paths: application/x-www-form-urlencoded: schema: $ref: '#/components/schemas/PetCreate' + multipart/form-data: + schema: + $ref: '#/components/schemas/PetWithPhotoCreate' text/plain: {} responses: '201': @@ -283,6 +286,12 @@ paths: operationId: deleteTag tags: - tags + parameters: + - name: x-delete-force + in: header + schema: + type: boolean + required: false requestBody: required: false content: @@ -375,6 +384,16 @@ components: oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Bird" + PetWithPhotoCreate: + type: object + x-model: PetWithPhotoCreate + allOf: + - $ref: "#/components/schemas/PetCreatePartOne" + - $ref: "#/components/schemas/PetCreatePartTwo" + - $ref: "#/components/schemas/PetCreatePartPhoto" + oneOf: + - $ref: "#/components/schemas/Cat" + - $ref: "#/components/schemas/Bird" PetCreatePartOne: type: object x-model: PetCreatePartOne @@ -395,6 +414,15 @@ components: $ref: "#/components/schemas/Position" healthy: type: boolean + PetCreatePartPhoto: + type: object + x-model: PetCreatePartPhoto + properties: + photo: + $ref: "#/components/schemas/PetPhoto" + PetPhoto: + type: string + format: binary Bird: type: object x-model: Bird diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index d8191f3e..edf95061 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -3,6 +3,7 @@ import pytest from jsonschema_path import SchemaPath +from openapi_core.schema.servers import get_server_default_variables from openapi_core.schema.servers import get_server_url from openapi_core.schema.specs import get_spec_url @@ -69,13 +70,19 @@ def test_spec(self, schema_path, spec_dict): for variable_name, variable in variables.items(): variable_spec = server_spec["variables"][variable_name] assert variable["default"] == variable_spec["default"] - assert variable["enum"] == variable_spec.get("enum") + assert (variable / "enum").read_value() == variable_spec.get( + "enum" + ) paths = schema_path / "paths" for path_name, path in paths.items(): path_spec = spec_dict["paths"][path_name] - assert path.getkey("summary") == path_spec.get("summary") - assert path.getkey("description") == path_spec.get("description") + assert (path / "summary").read_str(None) == path_spec.get( + "summary" + ) + assert (path / "description").read_str(None) == path_spec.get( + "description" + ) servers = path.get("servers", []) servers_spec = path_spec.get("servers", []) @@ -89,7 +96,9 @@ def test_spec(self, schema_path, spec_dict): for variable_name, variable in variables.items(): variable_spec = server_spec["variables"][variable_name] assert variable["default"] == variable_spec["default"] - assert variable.getkey("enum") == variable_spec.get("enum") + assert ( + variable / "enum" + ).read_value() == variable_spec.get("enum") operations = [ "get", @@ -108,20 +117,22 @@ def test_spec(self, schema_path, spec_dict): operation_spec = path_spec[http_method] assert operation["operationId"] is not None - assert operation["tags"] == operation_spec["tags"] + assert (operation / "tags").read_str_or_list( + None + ) == operation_spec["tags"] assert operation["summary"] == operation_spec.get("summary") - assert operation.getkey("description") == operation_spec.get( - "description" - ) + assert (operation / "description").read_str( + None + ) == operation_spec.get("description") ext_docs = operation.get("externalDocs") ext_docs_spec = operation_spec.get("externalDocs") assert bool(ext_docs_spec) == bool(ext_docs) if ext_docs_spec: assert ext_docs["url"] == ext_docs_spec["url"] - assert ext_docs.getkey("description") == ext_docs_spec.get( - "description" - ) + assert (ext_docs / "description").read_str( + None + ) == ext_docs_spec.get("description") servers = operation.get("servers", []) servers_spec = operation_spec.get("servers", []) @@ -137,9 +148,9 @@ def test_spec(self, schema_path, spec_dict): for variable_name, variable in variables.items(): variable_spec = server_spec["variables"][variable_name] assert variable["default"] == variable_spec["default"] - assert variable.getkey("enum") == variable_spec.get( - "enum" - ) + assert ( + variable / "enum" + ).read_value() == variable_spec.get("enum") security = operation.get("security", []) security_spec = operation_spec.get("security") @@ -163,7 +174,9 @@ def test_spec(self, schema_path, spec_dict): description_spec = response_spec["description"] - assert response.getkey("description") == description_spec + assert (response / "description").read_str( + None + ) == description_spec headers = response.get("headers", {}) for parameter_name, parameter in headers.items(): @@ -182,12 +195,12 @@ def test_spec(self, schema_path, spec_dict): continue assert schema["type"] == schema_spec["type"] - assert schema.getkey("format") == schema_spec.get( - "format" - ) - assert schema.getkey("required") == schema_spec.get( - "required" - ) + assert (schema / "format").read_str( + None + ) == schema_spec.get("format") + assert (schema / "required").read_str( + None + ) == schema_spec.get("required") content = parameter.get("content", {}) content_spec = parameter_spec.get("content") @@ -210,12 +223,12 @@ def test_spec(self, schema_path, spec_dict): continue assert schema["type"] == schema_spec["type"] - assert schema.getkey("format") == schema_spec.get( - "format" - ) - assert schema.getkey( - "required" - ) == schema_spec.get("required") + assert (schema / "format").read_str( + None + ) == schema_spec.get("format") + assert ( + schema / "required" + ).read_bool() == schema_spec.get("required") content_spec = response_spec.get("content") @@ -227,7 +240,9 @@ def test_spec(self, schema_path, spec_dict): content_spec = response_spec["content"][mimetype] example_spec = content_spec.get("example") - assert media_type.getkey("example") == example_spec + assert (media_type / "example").read_str_or_list( + None + ) == example_spec schema = media_type.get("schema") schema_spec = content_spec.get("schema") @@ -241,9 +256,9 @@ def test_spec(self, schema_path, spec_dict): continue assert schema["type"] == schema_spec["type"] - assert schema.getkey("required") == schema_spec.get( - "required" - ) + assert (schema / "required").read_bool( + None + ) == schema_spec.get("required") request_body = operation.get("requestBody") request_body_spec = operation_spec.get("requestBody") @@ -253,7 +268,7 @@ def test_spec(self, schema_path, spec_dict): continue assert bool( - request_body.getkey("required") + (request_body / "required").read_bool() ) == request_body_spec.get("required") content = request_body / "content" @@ -272,10 +287,12 @@ def test_spec(self, schema_path, spec_dict): assert bool(schema_spec) == bool(schema) assert schema["type"] == schema_spec["type"] - assert schema.getkey("format") == schema_spec.get("format") - assert schema.getkey("required") == schema_spec.get( - "required" - ) + assert (schema / "format").read_str( + None + ) == schema_spec.get("format") + assert (schema / "required").read_bool( + None + ) == schema_spec.get("required") components = schema_path.get("components") if not components: @@ -284,8 +301,12 @@ def test_spec(self, schema_path, spec_dict): schemas = components.get("schemas", {}) for schema_name, schema in schemas.items(): schema_spec = spec_dict["components"]["schemas"][schema_name] - assert schema.getkey("readOnly") == schema_spec.get("readOnly") - assert schema.getkey("writeOnly") == schema_spec.get("writeOnly") + assert (schema / "readOnly").read_bool(None) == schema_spec.get( + "readOnly" + ) + assert (schema / "writeOnly").read_bool(None) == schema_spec.get( + "writeOnly" + ) class TestWebhook: @@ -323,7 +344,7 @@ def test_spec(self, schema_path, spec_dict): webhooks = schema_path / "webhooks" webhooks_spec = spec_dict["webhooks"] - assert webhooks["newPet"] == webhooks_spec["newPet"] + assert (webhooks / "newPet").read_value() == webhooks_spec["newPet"] components = schema_path.get("components") if not components: @@ -332,3 +353,18 @@ def test_spec(self, schema_path, spec_dict): schemas = components.get("schemas", {}) for schema_name, schema in schemas.items(): assert spec_dict["components"]["schemas"][schema_name] is not None + + +def test_get_server_default_variables(): + server_spec = { + "url": "https://{host}.example.com:{port}/v1", + "variables": { + "host": {"default": "api"}, + "port": {"default": "8080"}, + }, + } + server = SchemaPath.from_dict(server_spec) + + defaults = get_server_default_variables(server) + + assert defaults == {"host": "api", "port": "8080"} diff --git a/tests/integration/test_petstore.py b/tests/integration/test_petstore.py index 58fbb760..d122ba49 100644 --- a/tests/integration/test_petstore.py +++ b/tests/integration/test_petstore.py @@ -532,18 +532,15 @@ def test_get_pets_raises_missing_required_param(self, spec): ) with pytest.warns( - DeprecationWarning, match="limit parameter is deprecated" + DeprecationWarning, + match="Use of allowEmptyValue property is deprecated", ): - with pytest.warns( - DeprecationWarning, - match="Use of allowEmptyValue property is deprecated", - ): - with pytest.raises(MissingRequiredParameter): - validate_request( - request, - spec=spec, - cls=V30RequestParametersValidator, - ) + with pytest.raises(MissingRequiredParameter): + validate_request( + request, + spec=spec, + cls=V30RequestParametersValidator, + ) result = unmarshal_request( request, spec=spec, cls=V30RequestBodyUnmarshaller @@ -1689,7 +1686,7 @@ def test_post_tags_wrong_property_type(self, spec): spec=spec, cls=V30RequestBodyValidator, ) - assert type(exc_info.value.__cause__) is CastError + assert type(exc_info.value.__cause__) is InvalidSchemaValue def test_post_tags_additional_properties(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -2091,6 +2088,51 @@ def test_delete_tags_no_requestbody(self, spec): assert result.body is None + @pytest.mark.parametrize( + "header_value,expexted_value", + [ + ("y", True), + ("t", True), + ("yes", True), + ("on", True), + ("true", True), + ("1", True), + ("n", False), + ("f", False), + ("no", False), + ("off", False), + ("false", False), + ("0", False), + ], + ) + def test_delete_tags_header(self, spec, header_value, expexted_value): + host_url = "http://petstore.swagger.io/v1" + path_pattern = "/v1/tags" + headers = { + "x-delete-force": header_value, + } + request = MockRequest( + host_url, + "DELETE", + "/tags", + headers=headers, + path_pattern=path_pattern, + ) + + validate_request(request, spec=spec) + + result = unmarshal_request( + request, + spec=spec, + cls=V30RequestParametersUnmarshaller, + ) + + assert result.parameters == Parameters( + header={ + "x-delete-force": expexted_value, + }, + ) + def test_delete_tags_raises_missing_required_response_header( self, spec, response_unmarshaller ): @@ -2120,10 +2162,7 @@ def test_delete_tags_raises_missing_required_response_header( data = None response = MockResponse(data, status_code=200) - with pytest.warns(DeprecationWarning): - response_result = response_unmarshaller.unmarshal( - request, response - ) + response_result = response_unmarshaller.unmarshal(request, response) assert response_result.errors == [ MissingRequiredHeader(name="x-delete-confirm"), diff --git a/tests/integration/unmarshalling/test_request_unmarshaller.py b/tests/integration/unmarshalling/test_request_unmarshaller.py index 2993275b..ed7d5794 100644 --- a/tests/integration/unmarshalling/test_request_unmarshaller.py +++ b/tests/integration/unmarshalling/test_request_unmarshaller.py @@ -163,6 +163,12 @@ def test_missing_body(self, request_unmarshaller): assert len(result.errors) == 1 assert type(result.errors[0]) == MissingRequiredRequestBody + assert result.errors[0].details == { + "message": "Missing required request body", + "error_type": "MissingRequiredRequestBody", + "cause_type": None, + "schema_errors": [], + } assert result.body is None assert result.parameters == Parameters( header={ @@ -201,6 +207,7 @@ def test_invalid_content_type(self, request_unmarshaller): availableMimetypes=[ "application/json", "application/x-www-form-urlencoded", + "multipart/form-data", "text/plain", ], ) @@ -416,3 +423,47 @@ def test_get_pet(self, request_unmarshaller): assert result.security == { "petstore_auth": self.api_key_encoded, } + + def test_request_body_with_object_default(self): + from openapi_core import OpenAPI + + spec = OpenAPI.from_dict( + { + "openapi": "3.1.0", + "info": {"version": "0", "title": "test"}, + "paths": { + "/test": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "default": [], + } + }, + } + } + } + }, + "responses": {"200": {"description": ""}}, + }, + } + }, + } + ) + request = MockRequest( + "http://localhost", + "post", + "/test", + content_type="application/json", + data=b"{}", + ) + + result = spec.unmarshal_request(request) + + assert result.errors == [] + assert result.body == {"tags": []} diff --git a/tests/integration/unmarshalling/test_response_unmarshaller.py b/tests/integration/unmarshalling/test_response_unmarshaller.py index 3c67cf60..f06f688c 100644 --- a/tests/integration/unmarshalling/test_response_unmarshaller.py +++ b/tests/integration/unmarshalling/test_response_unmarshaller.py @@ -19,6 +19,7 @@ from openapi_core.validation.response.exceptions import InvalidData from openapi_core.validation.response.exceptions import InvalidHeader from openapi_core.validation.response.exceptions import MissingData +from openapi_core.validation.response.exceptions import MissingRequiredHeader from openapi_core.validation.schemas.exceptions import InvalidSchemaValue @@ -173,6 +174,35 @@ def test_invalid_header(self, response_unmarshaller): assert result.data is None assert result.headers == {"x-delete-confirm": True} + def test_missing_deprecated_required_header(self, response_unmarshaller): + request = MockRequest( + self.host_url, + "delete", + "/v1/tags", + path_pattern="/v1/tags", + ) + response_json = { + "data": [ + { + "id": 1, + "name": "Sparky", + "ears": { + "healthy": True, + }, + }, + ], + } + response_data = json.dumps(response_json).encode() + response = MockResponse(response_data) + + result = response_unmarshaller.unmarshal(request, response) + + assert result.errors == [ + MissingRequiredHeader(name="x-delete-confirm") + ] + assert result.data is None + assert result.headers == {} + def test_get_pets(self, response_unmarshaller): request = MockRequest(self.host_url, "get", "/v1/pets") response_json = { diff --git a/tests/integration/unmarshalling/test_response_unmarshaller_response_properties_default_policy.py b/tests/integration/unmarshalling/test_response_unmarshaller_response_properties_default_policy.py new file mode 100644 index 00000000..7d712775 --- /dev/null +++ b/tests/integration/unmarshalling/test_response_unmarshaller_response_properties_default_policy.py @@ -0,0 +1,102 @@ +import json + +from openapi_core import Config +from openapi_core import OpenAPI +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse +from openapi_core.validation.response.exceptions import InvalidData + + +def _spec_dict(): + return { + "openapi": "3.0.3", + "info": { + "title": "Strict response properties", + "version": "1.0.0", + }, + "servers": [{"url": "http://example.com"}], + "paths": { + "/resources": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + }, + } + } + } + } + }, + "components": { + "schemas": { + "Resource": { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "name": {"type": "string"}, + "secret": { + "type": "string", + "writeOnly": True, + }, + }, + "required": ["id"], + } + } + }, + } + + +def test_response_unmarshal_default_allows_missing_optional_properties(): + openapi = OpenAPI.from_dict(_spec_dict()) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps({"id": 1}).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + result = openapi.unmarshal_response(request, response) + + assert result.errors == [] + + +def test_response_unmarshal_strict_rejects_missing_documented_properties(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps({"id": 1}).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + result = openapi.unmarshal_response(request, response) + + assert result.errors == [InvalidData()] + assert result.data is None + + +def test_response_unmarshal_strict_excludes_write_only_properties(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps( + { + "id": 1, + "name": "resource", + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + result = openapi.unmarshal_response(request, response) + + assert result.errors == [] diff --git a/tests/integration/unmarshalling/test_unmarshallers.py b/tests/integration/unmarshalling/test_unmarshallers.py index 764fc3af..ddb322be 100644 --- a/tests/integration/unmarshalling/test_unmarshallers.py +++ b/tests/integration/unmarshalling/test_unmarshallers.py @@ -26,31 +26,36 @@ class BaseTestOASSchemaUnmarshallersFactoryCall: - def test_create_no_schema(self, unmarshallers_factory): + @pytest.fixture + def spec(self): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + def test_create_no_schema(self, spec, unmarshallers_factory): with pytest.raises(TypeError): unmarshallers_factory.create(None) - def test_create_schema_deprecated(self, unmarshallers_factory): - schema = { + def test_create_schema_deprecated(self, spec, unmarshallers_factory): + schema_dict = { "deprecated": True, } - spec = SchemaPath.from_dict(schema) + schema = SchemaPath.from_dict(schema_dict) with pytest.warns(DeprecationWarning): - unmarshallers_factory.create(spec) + unmarshallers_factory.create(spec, schema) - def test_create_formatter_not_found(self, unmarshallers_factory): + def test_create_formatter_not_found(self, spec, unmarshallers_factory): custom_format = "custom" - schema = { + schema_dict = { "type": "string", "format": custom_format, } - spec = SchemaPath.from_dict(schema) + schema = SchemaPath.from_dict(schema_dict) with pytest.raises( FormatterNotFoundError, match="Formatter not found for custom format", ): - unmarshallers_factory.create(spec) + unmarshallers_factory.create(spec, schema) @pytest.mark.parametrize( "value", @@ -64,10 +69,27 @@ def test_create_formatter_not_found(self, unmarshallers_factory): False, ], ) - def test_no_type(self, unmarshallers_factory, value): - schema = {} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_no_type(self, spec, unmarshallers_factory, value): + schema_dict = {} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) + + result = unmarshaller.unmarshal(value) + + assert result == value + + @pytest.mark.parametrize( + "factory", + [ + oas30_write_schema_unmarshallers_factory, + oas31_schema_unmarshallers_factory, + ], + ) + def test_no_type_object_with_array_of_null(self, spec, factory): + schema_dict = {} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = factory.create(spec, schema) + value = {"foo": [None]} result = unmarshaller.unmarshal(value) @@ -85,12 +107,12 @@ def test_no_type(self, unmarshallers_factory, value): ("boolean", False), ], ) - def test_basic_types(self, unmarshallers_factory, type, value): - schema = { + def test_basic_types(self, spec, unmarshallers_factory, type, value): + schema_dict = { "type": type, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -140,12 +162,14 @@ def test_basic_types(self, unmarshallers_factory, type, value): ("object", ["one", "two"]), ], ) - def test_basic_types_invalid(self, unmarshallers_factory, type, value): - schema = { + def test_basic_types_invalid( + self, spec, unmarshallers_factory, type, value + ): + schema_dict = { "type": type, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises( InvalidSchemaValue, @@ -185,13 +209,13 @@ def test_basic_types_invalid(self, unmarshallers_factory, type, value): ], ) def test_basic_formats( - self, unmarshallers_factory, format, value, unmarshalled + self, spec, unmarshallers_factory, format, value, unmarshalled ): - schema = { + schema_dict = { "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -227,14 +251,14 @@ def test_basic_formats( ], ) def test_basic_type_formats( - self, unmarshallers_factory, type, format, value, unmarshalled + self, spec, unmarshallers_factory, type, format, value, unmarshalled ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -251,14 +275,14 @@ def test_basic_type_formats( ], ) def test_basic_type_formats_ignored( - self, unmarshallers_factory, type, format, value + self, spec, unmarshallers_factory, type, format, value ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -273,14 +297,14 @@ def test_basic_type_formats_ignored( ], ) def test_basic_type_formats_invalid( - self, unmarshallers_factory, type, format, value + self, spec, unmarshallers_factory, type, format, value ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -295,25 +319,25 @@ def test_basic_type_formats_invalid( ("dGVzdA==", "test"), ], ) - def test_string_byte(self, unmarshallers_factory, value, expected): - schema = { + def test_string_byte(self, spec, unmarshallers_factory, value, expected): + schema_dict = { "type": "string", "format": "byte", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == expected - def test_string_date(self, unmarshallers_factory): - schema = { + def test_string_date(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", "format": "date", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02" result = unmarshaller.unmarshal(value) @@ -330,25 +354,27 @@ def test_string_date(self, unmarshallers_factory): ), ], ) - def test_string_datetime(self, unmarshallers_factory, value, expected): - schema = { + def test_string_datetime( + self, spec, unmarshallers_factory, value, expected + ): + schema_dict = { "type": "string", "format": "date-time", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == expected - def test_string_datetime_invalid(self, unmarshallers_factory): - schema = { + def test_string_datetime_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", "format": "date-time", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02T00:00:00" with pytest.raises(InvalidSchemaValue) as exc_info: @@ -358,39 +384,39 @@ def test_string_datetime_invalid(self, unmarshallers_factory): "is not a 'date-time'" in exc_info.value.schema_errors[0].message ) - def test_string_password(self, unmarshallers_factory): - schema = { + def test_string_password(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", "format": "password", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "passwd" result = unmarshaller.unmarshal(value) assert result == value - def test_string_uuid(self, unmarshallers_factory): - schema = { + def test_string_uuid(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", "format": "uuid", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = str(uuid4()) result = unmarshaller.unmarshal(value) assert result == UUID(value) - def test_string_uuid_invalid(self, unmarshallers_factory): - schema = { + def test_string_uuid_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", "format": "uuid", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "test" with pytest.raises(InvalidSchemaValue) as exc_info: @@ -412,27 +438,27 @@ def test_string_uuid_invalid(self, unmarshallers_factory): ], ) def test_formats_ignored( - self, unmarshallers_factory, type, format, value, expected + self, spec, unmarshallers_factory, type, format, value, expected ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == expected @pytest.mark.parametrize("value", ["bar", "foobar"]) - def test_string_pattern(self, unmarshallers_factory, value): - schema = { + def test_string_pattern(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "string", "pattern": "bar", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -446,14 +472,14 @@ def test_string_pattern(self, unmarshallers_factory, value): ], ) def test_string_pattern_invalid( - self, unmarshallers_factory, value, pattern + self, spec, unmarshallers_factory, value, pattern ): - schema = { + schema_dict = { "type": "string", "pattern": pattern, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -464,26 +490,28 @@ def test_string_pattern_invalid( ) @pytest.mark.parametrize("value", ["abc", "abcd"]) - def test_string_min_length(self, unmarshallers_factory, value): - schema = { + def test_string_min_length(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "string", "minLength": 3, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize("value", ["", "a", "ab"]) - def test_string_min_length_invalid(self, unmarshallers_factory, value): - schema = { + def test_string_min_length_invalid( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "string", "minLength": 3, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -494,26 +522,28 @@ def test_string_min_length_invalid(self, unmarshallers_factory, value): ) @pytest.mark.parametrize("value", ["", "a"]) - def test_string_max_length(self, unmarshallers_factory, value): - schema = { + def test_string_max_length(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "string", "maxLength": 1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize("value", ["ab", "abc"]) - def test_string_max_length_invalid(self, unmarshallers_factory, value): - schema = { + def test_string_max_length_invalid( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "string", "maxLength": 1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -529,39 +559,39 @@ def test_string_max_length_invalid(self, unmarshallers_factory, value): ], ) def test_string_max_length_invalid_schema( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "string", "maxLength": -1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) - def test_integer_enum(self, unmarshallers_factory): - schema = { + def test_integer_enum(self, spec, unmarshallers_factory): + schema_dict = { "type": "integer", "enum": [1, 2, 3], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = 2 result = unmarshaller.unmarshal(value) assert result == int(value) - def test_integer_enum_invalid(self, unmarshallers_factory): + def test_integer_enum_invalid(self, spec, unmarshallers_factory): enum = [1, 2, 3] - schema = { + schema_dict = { "type": "integer", "enum": enum, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = 12 with pytest.raises(InvalidSchemaValue) as exc_info: @@ -584,15 +614,15 @@ def test_integer_enum_invalid(self, unmarshallers_factory): ("boolean", False), ], ) - def test_array(self, unmarshallers_factory, type, value): - schema = { + def test_array(self, spec, unmarshallers_factory, type, value): + schema_dict = { "type": "array", "items": { "type": type, }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value_list = [value] * 3 result = unmarshaller.unmarshal(value_list) @@ -610,15 +640,15 @@ def test_array(self, unmarshallers_factory, type, value): ("boolean", "123"), ], ) - def test_array_invalid(self, unmarshallers_factory, type, value): - schema = { + def test_array_invalid(self, spec, unmarshallers_factory, type, value): + schema_dict = { "type": "array", "items": { "type": type, }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal([value]) @@ -629,16 +659,16 @@ def test_array_invalid(self, unmarshallers_factory, type, value): ) @pytest.mark.parametrize("value", [[], [1], [1, 2]]) - def test_array_min_items_invalid(self, unmarshallers_factory, value): - schema = { + def test_array_min_items_invalid(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "array", "items": { "type": "number", }, "minItems": 3, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -648,16 +678,16 @@ def test_array_min_items_invalid(self, unmarshallers_factory, value): ) @pytest.mark.parametrize("value", [[], [1], [1, 2]]) - def test_array_min_items(self, unmarshallers_factory, value): - schema = { + def test_array_min_items(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "array", "items": { "type": "number", }, "minItems": 0, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -670,32 +700,32 @@ def test_array_min_items(self, unmarshallers_factory, value): ], ) def test_array_max_items_invalid_schema( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "array", "items": { "type": "number", }, "maxItems": -1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize("value", [[1, 2], [2, 3, 4]]) - def test_array_max_items_invalid(self, unmarshallers_factory, value): - schema = { + def test_array_max_items_invalid(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "array", "items": { "type": "number", }, "maxItems": 1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -705,16 +735,18 @@ def test_array_max_items_invalid(self, unmarshallers_factory, value): ) @pytest.mark.parametrize("value", [[1, 2, 1], [2, 2]]) - def test_array_unique_items_invalid(self, unmarshallers_factory, value): - schema = { + def test_array_unique_items_invalid( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "array", "items": { "type": "number", }, "uniqueItems": True, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -724,8 +756,8 @@ def test_array_unique_items_invalid(self, unmarshallers_factory, value): in exc_info.value.schema_errors[0].message ) - def test_object_any_of(self, unmarshallers_factory): - schema = { + def test_object_any_of(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "anyOf": [ { @@ -740,16 +772,16 @@ def test_object_any_of(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = {"someint": 1} result = unmarshaller.unmarshal(value) assert result == value - def test_object_any_of_invalid(self, unmarshallers_factory): - schema = { + def test_object_any_of_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "anyOf": [ { @@ -764,14 +796,14 @@ def test_object_any_of_invalid(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal({"someint": "1"}) - def test_object_one_of_default(self, unmarshallers_factory): - schema = { + def test_object_one_of_default(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "oneOf": [ { @@ -799,16 +831,16 @@ def test_object_one_of_default(self, unmarshallers_factory): }, }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) assert unmarshaller.unmarshal({"someint": 1}) == { "someint": 1, "somestr": "defaultstring", } - def test_object_any_of_default(self, unmarshallers_factory): - schema = { + def test_object_any_of_default(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "anyOf": [ { @@ -830,16 +862,16 @@ def test_object_any_of_default(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) assert unmarshaller.unmarshal({"someint": "1"}) == { "someint": "1", "somestr": "defaultstring", } - def test_object_all_of_default(self, unmarshallers_factory): - schema = { + def test_object_all_of_default(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "allOf": [ { @@ -862,8 +894,8 @@ def test_object_all_of_default(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) assert unmarshaller.unmarshal({}) == { "someint": 1, @@ -885,8 +917,8 @@ def test_object_all_of_default(self, unmarshallers_factory): }, ], ) - def test_object_with_properties(self, unmarshallers_factory, value): - schema = { + def test_object_with_properties(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "object", "properties": { "somestr": { @@ -897,8 +929,8 @@ def test_object_with_properties(self, unmarshallers_factory, value): }, }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -927,9 +959,9 @@ def test_object_with_properties(self, unmarshallers_factory, value): ], ) def test_object_with_properties_invalid( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "object", "properties": { "somestr": { @@ -941,8 +973,8 @@ def test_object_with_properties_invalid( }, "additionalProperties": False, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -953,8 +985,8 @@ def test_object_with_properties_invalid( {}, ], ) - def test_object_default_property(self, unmarshallers_factory, value): - schema = { + def test_object_default_property(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "object", "properties": { "prop": { @@ -963,8 +995,8 @@ def test_object_default_property(self, unmarshallers_factory, value): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -977,14 +1009,14 @@ def test_object_default_property(self, unmarshallers_factory, value): ], ) def test_object_additional_properties_false( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "object", "additionalProperties": False, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -999,23 +1031,25 @@ def test_object_additional_properties_false( ) @pytest.mark.parametrize("additional_properties", [True, {}]) def test_object_additional_properties_free_form_object( - self, value, additional_properties, unmarshallers_factory + self, value, additional_properties, spec, unmarshallers_factory ): - schema = { + schema_dict = { "type": "object", "additionalProperties": additional_properties, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == value - def test_object_additional_properties_list(self, unmarshallers_factory): - schema = {"type": "object"} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_object_additional_properties_list( + self, spec, unmarshallers_factory + ): + schema_dict = {"type": "object"} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal({"user_ids": [1, 2, 3, 4]}) @@ -1029,12 +1063,14 @@ def test_object_additional_properties_list(self, unmarshallers_factory): {"additional": 1}, ], ) - def test_object_additional_properties(self, unmarshallers_factory, value): - schema = { + def test_object_additional_properties( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "object", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1047,17 +1083,17 @@ def test_object_additional_properties(self, unmarshallers_factory, value): ], ) def test_object_additional_properties_object( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): additional_properties = { "type": "integer", } - schema = { + schema_dict = { "type": "object", "additionalProperties": additional_properties, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1071,14 +1107,14 @@ def test_object_additional_properties_object( {"a": 1, "b": 2, "c": 3}, ], ) - def test_object_min_properties(self, unmarshallers_factory, value): - schema = { + def test_object_min_properties(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "minProperties": 1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1092,14 +1128,16 @@ def test_object_min_properties(self, unmarshallers_factory, value): {"a": 1, "b": 2, "c": 3}, ], ) - def test_object_min_properties_invalid(self, unmarshallers_factory, value): - schema = { + def test_object_min_properties_invalid( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "minProperties": 4, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1111,14 +1149,14 @@ def test_object_min_properties_invalid(self, unmarshallers_factory, value): ], ) def test_object_min_properties_invalid_schema( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "object", "minProperties": 2, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1131,14 +1169,14 @@ def test_object_min_properties_invalid_schema( {"a": 1, "b": 2, "c": 3}, ], ) - def test_object_max_properties(self, unmarshallers_factory, value): - schema = { + def test_object_max_properties(self, spec, unmarshallers_factory, value): + schema_dict = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "maxProperties": 3, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1152,14 +1190,16 @@ def test_object_max_properties(self, unmarshallers_factory, value): {"a": 1, "b": 2, "c": 3}, ], ) - def test_object_max_properties_invalid(self, unmarshallers_factory, value): - schema = { + def test_object_max_properties_invalid( + self, spec, unmarshallers_factory, value + ): + schema_dict = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "maxProperties": 0, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1171,20 +1211,20 @@ def test_object_max_properties_invalid(self, unmarshallers_factory, value): ], ) def test_object_max_properties_invalid_schema( - self, unmarshallers_factory, value + self, spec, unmarshallers_factory, value ): - schema = { + schema_dict = { "type": "object", "maxProperties": -1, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) - def test_any_one_of(self, unmarshallers_factory): - schema = { + def test_any_one_of(self, spec, unmarshallers_factory): + schema_dict = { "oneOf": [ { "type": "string", @@ -1197,16 +1237,16 @@ def test_any_one_of(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = ["hello"] result = unmarshaller.unmarshal(value) assert result == value - def test_any_any_of(self, unmarshallers_factory): - schema = { + def test_any_any_of(self, spec, unmarshallers_factory): + schema_dict = { "anyOf": [ { "type": "string", @@ -1219,16 +1259,16 @@ def test_any_any_of(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = ["hello"] result = unmarshaller.unmarshal(value) assert result == value - def test_any_all_of(self, unmarshallers_factory): - schema = { + def test_any_all_of(self, spec, unmarshallers_factory): + schema_dict = { "allOf": [ { "type": "array", @@ -1238,8 +1278,8 @@ def test_any_all_of(self, unmarshallers_factory): } ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = ["hello"] result = unmarshaller.unmarshal(value) @@ -1268,8 +1308,10 @@ def test_any_all_of(self, unmarshallers_factory): }, ], ) - def test_any_all_of_invalid_properties(self, value, unmarshallers_factory): - schema = { + def test_any_all_of_invalid_properties( + self, value, spec, unmarshallers_factory + ): + schema_dict = { "allOf": [ { "type": "object", @@ -1292,14 +1334,14 @@ def test_any_all_of_invalid_properties(self, value, unmarshallers_factory): ], "additionalProperties": False, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) - def test_any_format_one_of(self, unmarshallers_factory): - schema = { + def test_any_format_one_of(self, spec, unmarshallers_factory): + schema_dict = { "format": "date", "oneOf": [ {"type": "integer"}, @@ -1308,16 +1350,16 @@ def test_any_format_one_of(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) - def test_any_one_of_any(self, unmarshallers_factory): - schema = { + def test_any_one_of_any(self, spec, unmarshallers_factory): + schema_dict = { "oneOf": [ {"type": "integer"}, { @@ -1326,16 +1368,16 @@ def test_any_one_of_any(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) - def test_any_any_of_any(self, unmarshallers_factory): - schema = { + def test_any_any_of_any(self, spec, unmarshallers_factory): + schema_dict = { "anyOf": [ {}, { @@ -1344,16 +1386,16 @@ def test_any_any_of_any(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) - def test_any_all_of_any(self, unmarshallers_factory): - schema = { + def test_any_all_of_any(self, spec, unmarshallers_factory): + schema_dict = { "allOf": [ {}, { @@ -1362,8 +1404,8 @@ def test_any_all_of_any(self, unmarshallers_factory): }, ], } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = "2018-01-02" result = unmarshaller.unmarshal(value) @@ -1376,7 +1418,7 @@ def test_any_all_of_any(self, unmarshallers_factory): {}, ], ) - def test_any_of_no_valid(self, unmarshallers_factory, value): + def test_any_of_no_valid(self, spec, unmarshallers_factory, value): any_of = [ { "type": "object", @@ -1397,11 +1439,11 @@ def test_any_of_no_valid(self, unmarshallers_factory, value): }, }, ] - schema = { + schema_dict = { "anyOf": any_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1412,7 +1454,7 @@ def test_any_of_no_valid(self, unmarshallers_factory, value): {}, ], ) - def test_any_one_of_no_valid(self, unmarshallers_factory, value): + def test_any_one_of_no_valid(self, spec, unmarshallers_factory, value): one_of = [ { "type": "object", @@ -1437,11 +1479,11 @@ def test_any_one_of_no_valid(self, unmarshallers_factory, value): }, }, ] - schema = { + schema_dict = { "oneOf": one_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1452,13 +1494,15 @@ def test_any_one_of_no_valid(self, unmarshallers_factory, value): {}, ], ) - def test_any_any_of_different_type(self, unmarshallers_factory, value): + def test_any_any_of_different_type( + self, spec, unmarshallers_factory, value + ): any_of = [{"type": "integer"}, {"type": "string"}] - schema = { + schema_dict = { "anyOf": any_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1469,7 +1513,9 @@ def test_any_any_of_different_type(self, unmarshallers_factory, value): {}, ], ) - def test_any_one_of_different_type(self, unmarshallers_factory, value): + def test_any_one_of_different_type( + self, spec, unmarshallers_factory, value + ): one_of = [ { "type": "integer", @@ -1478,11 +1524,11 @@ def test_any_one_of_different_type(self, unmarshallers_factory, value): "type": "string", }, ] - schema = { + schema_dict = { "oneOf": one_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1499,7 +1545,7 @@ def test_any_one_of_different_type(self, unmarshallers_factory, value): }, ], ) - def test_any_any_of_unambiguous(self, unmarshallers_factory, value): + def test_any_any_of_unambiguous(self, spec, unmarshallers_factory, value): any_of = [ { "type": "object", @@ -1525,11 +1571,11 @@ def test_any_any_of_unambiguous(self, unmarshallers_factory, value): "additionalProperties": False, }, ] - schema = { + schema_dict = { "anyOf": any_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1541,7 +1587,7 @@ def test_any_any_of_unambiguous(self, unmarshallers_factory, value): {}, ], ) - def test_object_multiple_any_of(self, unmarshallers_factory, value): + def test_object_multiple_any_of(self, spec, unmarshallers_factory, value): any_of = [ { "type": "object", @@ -1550,12 +1596,12 @@ def test_object_multiple_any_of(self, unmarshallers_factory, value): "type": "object", }, ] - schema = { + schema_dict = { "type": "object", "anyOf": any_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1567,7 +1613,7 @@ def test_object_multiple_any_of(self, unmarshallers_factory, value): dict(), ], ) - def test_object_multiple_one_of(self, unmarshallers_factory, value): + def test_object_multiple_one_of(self, spec, unmarshallers_factory, value): one_of = [ { "type": "object", @@ -1576,12 +1622,12 @@ def test_object_multiple_one_of(self, unmarshallers_factory, value): "type": "object", }, ] - schema = { + schema_dict = { "type": "object", "oneOf": one_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @@ -1598,7 +1644,7 @@ def test_object_multiple_one_of(self, unmarshallers_factory, value): }, ], ) - def test_any_one_of_unambiguous(self, unmarshallers_factory, value): + def test_any_one_of_unambiguous(self, spec, unmarshallers_factory, value): one_of = [ { "type": "object", @@ -1626,11 +1672,11 @@ def test_any_one_of_unambiguous(self, unmarshallers_factory, value): "additionalProperties": False, }, ] - schema = { + schema_dict = { "oneOf": one_of, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1638,10 +1684,10 @@ def test_any_one_of_unambiguous(self, unmarshallers_factory, value): class BaseTestOASS30chemaUnmarshallersFactoryCall: - def test_null_undefined(self, unmarshallers_factory): - schema = {"type": "null"} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_null_undefined(self, spec, unmarshallers_factory): + schema_dict = {"type": "null"} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(UnknownType): unmarshaller.unmarshal(None) @@ -1656,10 +1702,10 @@ def test_null_undefined(self, unmarshallers_factory): "string", ], ) - def test_nullable(self, unmarshallers_factory, type): - schema = {"type": type, "nullable": True} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_nullable(self, spec, unmarshallers_factory, type): + schema_dict = {"type": type, "nullable": True} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(None) @@ -1675,10 +1721,10 @@ def test_nullable(self, unmarshallers_factory, type): "string", ], ) - def test_not_nullable(self, unmarshallers_factory, type): - schema = {"type": type} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_not_nullable(self, spec, unmarshallers_factory, type): + schema_dict = {"type": type} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises( InvalidSchemaValue, @@ -1702,14 +1748,14 @@ def test_not_nullable(self, unmarshallers_factory, type): ], ) def test_basic_type_oas30_formats( - self, unmarshallers_factory, type, format, value, unmarshalled + self, spec, unmarshallers_factory, type, format, value, unmarshalled ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -1719,18 +1765,17 @@ def test_basic_type_oas30_formats( "type,format,value", [ ("string", "byte", "passwd"), - ("string", "binary", "test"), ], ) def test_basic_type_oas30_formats_invalid( - self, unmarshallers_factory, type, format, value + self, spec, unmarshallers_factory, type, format, value ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises( InvalidSchemaValue, @@ -1749,12 +1794,12 @@ def test_basic_type_oas30_formats_invalid( ), strict=True, ) - def test_string_format_binary_invalid(self, unmarshallers_factory): - schema = { + def test_string_format_binary_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "string", } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = b"true" with pytest.raises( @@ -1782,48 +1827,48 @@ def test_string_format_binary_invalid(self, unmarshallers_factory): ], ) def test_nultiple_types_undefined( - self, unmarshallers_factory, types, value + self, spec, unmarshallers_factory, types, value ): - schema = {"type": types} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema_dict = {"type": types} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(SchemaError): unmarshaller.unmarshal(value) - def test_integer_default_nullable(self, unmarshallers_factory): + def test_integer_default_nullable(self, spec, unmarshallers_factory): default_value = 123 - schema = { + schema_dict = { "type": "integer", "default": default_value, "nullable": True, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = None result = unmarshaller.unmarshal(value) assert result is None - def test_array_nullable(self, unmarshallers_factory): - schema = { + def test_array_nullable(self, spec, unmarshallers_factory): + schema_dict = { "type": "array", "items": { "type": "integer", }, "nullable": True, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = None result = unmarshaller.unmarshal(value) assert result is None - def test_object_property_nullable(self, unmarshallers_factory): - schema = { + def test_object_property_nullable(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "properties": { "foo": { @@ -1832,14 +1877,33 @@ def test_object_property_nullable(self, unmarshallers_factory): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = {"foo": None} result = unmarshaller.unmarshal(value) assert result == value + def test_subschema_nullable(self, spec, unmarshallers_factory): + schema_dict = { + "oneOf": [ + { + "type": "integer", + }, + { + "nullable": True, + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) + value = None + + result = unmarshaller.unmarshal(value) + + assert result is None + class TestOAS30RequestSchemaUnmarshallersFactory( BaseTestOASSchemaUnmarshallersFactoryCall, @@ -1849,8 +1913,8 @@ class TestOAS30RequestSchemaUnmarshallersFactory( def unmarshallers_factory(self): return oas30_write_schema_unmarshallers_factory - def test_write_only_properties(self, unmarshallers_factory): - schema = { + def test_write_only_properties(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "required": ["id"], "properties": { @@ -1860,8 +1924,8 @@ def test_write_only_properties(self, unmarshallers_factory): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = {"id": 10} # readOnly properties may be admitted in a Response context @@ -1869,8 +1933,8 @@ def test_write_only_properties(self, unmarshallers_factory): assert result == value - def test_read_only_properties_invalid(self, unmarshallers_factory): - schema = { + def test_read_only_properties_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "required": ["id"], "properties": { @@ -1880,8 +1944,8 @@ def test_read_only_properties_invalid(self, unmarshallers_factory): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) value = {"id": 10} # readOnly properties are not admitted on a Request context @@ -1897,8 +1961,8 @@ class TestOAS30ResponseSchemaUnmarshallersFactory( def unmarshallers_factory(self): return oas30_read_schema_unmarshallers_factory - def test_read_only_properties(self, unmarshallers_factory): - schema = { + def test_read_only_properties(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "required": ["id"], "properties": { @@ -1908,8 +1972,8 @@ def test_read_only_properties(self, unmarshallers_factory): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) # readOnly properties may be admitted in a Response context result = unmarshaller.unmarshal({"id": 10}) @@ -1918,8 +1982,8 @@ def test_read_only_properties(self, unmarshallers_factory): "id": 10, } - def test_write_only_properties_invalid(self, unmarshallers_factory): - schema = { + def test_write_only_properties_invalid(self, spec, unmarshallers_factory): + schema_dict = { "type": "object", "required": ["id"], "properties": { @@ -1929,8 +1993,8 @@ def test_write_only_properties_invalid(self, unmarshallers_factory): } }, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) # readOnly properties are not admitted on a Request context with pytest.raises(InvalidSchemaValue): @@ -1946,7 +2010,8 @@ def unmarshallers_factory(self): @pytest.mark.xfail( reason=( - "OpenAPI 3.1 schema validator uses OpenAPI 3.0 format checker." + "Intentional backward compatibility: OAS 3.1 currently uses " + "OAS 3.0-style format checker behavior in openapi-core. " "See https://github.com/python-openapi/openapi-core/issues/506" ), strict=True, @@ -1959,16 +2024,16 @@ def unmarshallers_factory(self): ], ) def test_create_oas30_formatter_not_found( - self, unmarshallers_factory, type, format + self, spec, unmarshallers_factory, type, format ): - schema = { + schema_dict = { "type": type, "format": format, } - spec = SchemaPath.from_dict(schema) + schema = SchemaPath.from_dict(schema_dict) with pytest.raises(FormatterNotFoundError): - unmarshallers_factory.create(spec) + unmarshallers_factory.create(spec, schema) @pytest.mark.parametrize( "type,value", @@ -1981,12 +2046,14 @@ def test_create_oas30_formatter_not_found( ("object", b"test"), ], ) - def test_basic_types_invalid(self, unmarshallers_factory, type, value): - schema = { + def test_basic_types_invalid( + self, spec, unmarshallers_factory, type, value + ): + schema_dict = { "type": type, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises( InvalidSchemaValue, @@ -1994,20 +2061,20 @@ def test_basic_types_invalid(self, unmarshallers_factory, type, value): ): unmarshaller.unmarshal(value) - def test_null(self, unmarshallers_factory): - schema = {"type": "null"} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_null(self, spec, unmarshallers_factory): + schema_dict = {"type": "null"} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(None) assert result is None @pytest.mark.parametrize("value", ["string", 2, 3.14, True, [1, 2], {}]) - def test_null_invalid(self, unmarshallers_factory, value): - schema = {"type": "null"} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_null_invalid(self, spec, unmarshallers_factory, value): + schema_dict = {"type": "null"} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -2027,10 +2094,10 @@ def test_null_invalid(self, unmarshallers_factory, value): (["object", "null"], {}), ], ) - def test_nultiple_types(self, unmarshallers_factory, types, value): - schema = {"type": types} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_nultiple_types(self, spec, unmarshallers_factory, types, value): + schema_dict = {"type": types} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) @@ -2047,10 +2114,12 @@ def test_nultiple_types(self, unmarshallers_factory, types, value): (["object", "null"], [1, 2]), ], ) - def test_nultiple_types_invalid(self, unmarshallers_factory, types, value): - schema = {"type": types} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_nultiple_types_invalid( + self, spec, unmarshallers_factory, types, value + ): + schema_dict = {"type": types} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) with pytest.raises(InvalidSchemaValue) as exc_info: unmarshaller.unmarshal(value) @@ -2065,24 +2134,43 @@ def test_nultiple_types_invalid(self, unmarshallers_factory, types, value): ], ) def test_multiple_types_format_valid_or_ignored( - self, unmarshallers_factory, types, format, value, expected + self, spec, unmarshallers_factory, types, format, value, expected ): - schema = { + schema_dict = { "type": types, "format": format, } - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(value) assert result == expected - def test_any_null(self, unmarshallers_factory): - schema = {} - spec = SchemaPath.from_dict(schema) - unmarshaller = unmarshallers_factory.create(spec) + def test_any_null(self, spec, unmarshallers_factory): + schema_dict = {} + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) result = unmarshaller.unmarshal(None) assert result is None + + def test_subschema_null(self, spec, unmarshallers_factory): + schema_dict = { + "oneOf": [ + { + "type": "integer", + }, + { + "type": "null", + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + unmarshaller = unmarshallers_factory.create(spec, schema) + value = None + + result = unmarshaller.unmarshal(value) + + assert result is None diff --git a/tests/integration/validation/test_additional_properties_default_policy.py b/tests/integration/validation/test_additional_properties_default_policy.py new file mode 100644 index 00000000..32a1b551 --- /dev/null +++ b/tests/integration/validation/test_additional_properties_default_policy.py @@ -0,0 +1,192 @@ +import json + +import pytest + +from openapi_core import Config +from openapi_core import OpenAPI +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse +from openapi_core.validation.request.exceptions import InvalidRequestBody +from openapi_core.validation.response.exceptions import InvalidData + + +def _spec_dict(): + return { + "openapi": "3.0.3", + "info": {"title": "Strict additionalProperties", "version": "1.0.0"}, + "servers": [{"url": "http://example.com"}], + "paths": { + "/tags": { + "post": { + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Tag"} + } + }, + }, + "responses": {"204": {"description": "No content"}}, + }, + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Tag" + } + } + }, + } + } + }, + } + }, + "components": { + "schemas": { + "Tag": { + "type": "object", + "properties": { + "tag_name": { + "type": "string", + } + }, + "required": ["tag_name"], + } + } + }, + } + + +def test_request_validation_default_allows_extra_properties(): + openapi = OpenAPI.from_dict(_spec_dict()) + request = MockRequest( + "http://example.com", + "post", + "/tags", + content_type="application/json", + data=json.dumps( + { + "tag_name": "my-tag", + "sneaky_property": "sneaky data", + } + ).encode("utf-8"), + ) + + openapi.validate_request(request) + + +def test_request_validation_strict_rejects_extra_properties(): + config = Config(additional_properties_default_policy="forbid") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest( + "http://example.com", + "post", + "/tags", + content_type="application/json", + data=json.dumps( + { + "tag_name": "my-tag", + "sneaky_property": "sneaky data", + } + ).encode("utf-8"), + ) + + with pytest.raises(InvalidRequestBody): + openapi.validate_request(request) + + +def test_response_validation_default_allows_extra_properties(): + openapi = OpenAPI.from_dict(_spec_dict()) + request = MockRequest("http://example.com", "get", "/tags") + response = MockResponse( + data=json.dumps( + { + "tag_name": "my-tag", + "sneaky_property": "sneaky data", + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + openapi.validate_response(request, response) + + +def test_response_validation_strict_rejects_extra_properties(): + config = Config(additional_properties_default_policy="forbid") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/tags") + response = MockResponse( + data=json.dumps( + { + "tag_name": "my-tag", + "sneaky_property": "sneaky data", + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + with pytest.raises(InvalidData): + openapi.validate_response(request, response) + + +def test_request_validation_strict_error_message_is_stable(): + """Test that error messages are deterministic when multiple extra properties exist.""" + config = Config(additional_properties_default_policy="forbid") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + + request = MockRequest( + "http://example.com", + "post", + "/tags", + content_type="application/json", + data=json.dumps( + { + "tag_name": "my-tag", + "zebra": "z data", + "apple": "a data", + "mango": "m data", + } + ).encode("utf-8"), + ) + + # Collect error messages from multiple validation attempts + messages = [] + for _ in range(10): + with pytest.raises(InvalidRequestBody) as exc_info: + openapi.validate_request(request) + messages.append(str(exc_info.value)) + + assert ( + len(set(messages)) == 1 + ), f"Error messages are not stable: {messages}" + + error_message = messages[0] + assert ( + "'apple', 'mango', 'zebra'" in error_message + ), f"Properties not in alphabetical order: {error_message}" + + +def test_response_validation_strict_allows_explicit_additional_properties_true(): + spec_dict = _spec_dict() + spec_dict["components"]["schemas"]["Tag"]["additionalProperties"] = True + + config = Config(additional_properties_default_policy="forbid") + openapi = OpenAPI.from_dict(spec_dict, config=config) + request = MockRequest("http://example.com", "get", "/tags") + response = MockResponse( + data=json.dumps( + { + "tag_name": "my-tag", + "sneaky_property": "sneaky data", + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + openapi.validate_response(request, response) diff --git a/tests/integration/validation/test_dialect_validators.py b/tests/integration/validation/test_dialect_validators.py new file mode 100644 index 00000000..d2929c28 --- /dev/null +++ b/tests/integration/validation/test_dialect_validators.py @@ -0,0 +1,155 @@ +from typing import Any +from typing import Dict +from typing import Optional +from typing import Type + +import pytest +from jsonschema_path import SchemaPath + +from openapi_core import V31RequestValidator +from openapi_core import V32RequestValidator +from openapi_core.testing import MockRequest +from openapi_core.validation.request.exceptions import InvalidRequestBody + + +def _spec_dict( + openapi_version: str, + dialect: Optional[str] = None, + schema_dialect: Optional[str] = None, +) -> Dict[str, Any]: + schema = {"type": "integer", "minimum": 10, "exclusiveMinimum": True} + if schema_dialect is not None: + schema["$schema"] = schema_dialect + + spec = { + "openapi": openapi_version, + "info": {"title": "Dialect Validation", "version": "1.0.0"}, + "servers": [{"url": "http://example.com"}], + "paths": { + "/users": { + "post": { + "requestBody": { + "required": True, + "content": {"application/json": {"schema": schema}}, + }, + "responses": {"200": {"description": "OK"}}, + } + } + }, + } + if dialect is not None: + spec["jsonSchemaDialect"] = dialect + + return spec + + +@pytest.mark.parametrize( + "openapi_version, validator_cls", + [ + ("3.1.0", V31RequestValidator), + ("3.2.0", V32RequestValidator), + ], +) +class TestDialectValidators: + def test_default_dialect_valid( + self, openapi_version: str, validator_cls: Type[Any] + ) -> None: + spec = _spec_dict(openapi_version=openapi_version) + spec_path = SchemaPath.from_dict(spec) + validator = validator_cls(spec_path) + + request = MockRequest( + "http://example.com", + "POST", + "/users", + data=b"10", + content_type="application/json", + ) + validator.validate(request) + + def test_unsupported_json_schema_dialect( + self, openapi_version: str, validator_cls: Type[Any] + ) -> None: + spec = _spec_dict( + openapi_version=openapi_version, + dialect="http://unsupported.dialect", + ) + spec_path = SchemaPath.from_dict(spec) + + validator = validator_cls(spec_path) + request = MockRequest( + "http://example.com", + "POST", + "/users", + data=b"10", + content_type="application/json", + ) + with pytest.raises( + ValueError, + match="Unknown JSON Schema dialect: 'http://unsupported.dialect'", + ): + validator.validate(request) + + def test_unsupported_schema_dialect( + self, openapi_version: str, validator_cls: Type[Any] + ) -> None: + spec = _spec_dict( + openapi_version=openapi_version, + schema_dialect="http://unsupported.dialect", + ) + spec_path = SchemaPath.from_dict(spec) + + validator = validator_cls(spec_path) + request = MockRequest( + "http://example.com", + "POST", + "/users", + data=b"10", + content_type="application/json", + ) + with pytest.raises( + ValueError, + match="Unknown JSON Schema dialect: 'http://unsupported.dialect'", + ): + validator.validate(request) + + def test_valid_json_schema_dialect( + self, openapi_version: str, validator_cls: Type[Any] + ) -> None: + # Using draft-04 dialect + spec = _spec_dict( + openapi_version=openapi_version, + dialect="http://json-schema.org/draft-04/schema#", + ) + spec_path = SchemaPath.from_dict(spec) + + validator = validator_cls(spec_path) + request = MockRequest( + "http://example.com", + "POST", + "/users", + data=b"15", + content_type="application/json", + ) + validator.validate(request) + + def test_valid_json_schema_dialect_invalid_data( + self, openapi_version: str, validator_cls: Type[Any] + ) -> None: + # Using draft-04 dialect, where `exclusiveMinimum: true` makes 10 invalid + spec = _spec_dict( + openapi_version=openapi_version, + dialect="http://json-schema.org/draft-04/schema#", + ) + spec_path = SchemaPath.from_dict(spec) + + validator = validator_cls(spec_path) + request = MockRequest( + "http://example.com", + "POST", + "/users", + data=b"10", + content_type="application/json", + ) + with pytest.raises(InvalidRequestBody): + validator.validate(request) diff --git a/tests/integration/validation/test_parent_reference.py b/tests/integration/validation/test_parent_reference.py new file mode 100644 index 00000000..21e37351 --- /dev/null +++ b/tests/integration/validation/test_parent_reference.py @@ -0,0 +1,45 @@ +import json + +import pytest +from jsonschema_path import SchemaPath + +from openapi_core import Config +from openapi_core import OpenAPI +from openapi_core import V30ResponseUnmarshaller +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse + + +class TestParentReference: + + spec_path = "data/v3.0/parent-reference/openapi.yaml" + + @pytest.fixture + def unmarshaller(self, content_factory): + content, base_uri = content_factory.from_file(self.spec_path) + return V30ResponseUnmarshaller( + spec=SchemaPath.from_dict(content, base_uri=base_uri) + ) + + @pytest.fixture + def openapi(self, content_factory): + content, base_uri = content_factory.from_file(self.spec_path) + spec = SchemaPath.from_dict(content, base_uri=base_uri) + config = Config(spec_base_uri=base_uri) + return OpenAPI(spec, config=config) + + def test_valid(self, openapi): + request = MockRequest(host_url="", method="GET", path="/books") + response = MockResponse( + data=json.dumps([{"id": "BOOK:01", "title": "Test Book"}]).encode() + ) + + openapi.validate_response(request, response) + + def test_unmarshal(self, unmarshaller): + request = MockRequest(host_url="", method="GET", path="/books") + response = MockResponse( + data=json.dumps([{"id": "BOOK:01", "title": "Test Book"}]).encode() + ) + + unmarshaller.unmarshal(request, response) diff --git a/tests/integration/validation/test_request_validators.py b/tests/integration/validation/test_request_validators.py index 14a7e6d6..43c2cc99 100644 --- a/tests/integration/validation/test_request_validators.py +++ b/tests/integration/validation/test_request_validators.py @@ -2,6 +2,7 @@ import pytest +from openapi_core import OpenAPI from openapi_core import V30RequestValidator from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import OperationNotFound @@ -63,6 +64,61 @@ def test_missing_parameter(self, request_validator): with pytest.warns(DeprecationWarning): request_validator.validate(request) + def test_omitted_required_deprecated_parameter(self): + spec = OpenAPI.from_dict( + { + "openapi": "3.1.0", + "info": {"version": "0", "title": "test"}, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "foo", + "in": "query", + "schema": {}, + "deprecated": True, + "required": True, + }, + ] + } + } + }, + } + ) + + request = MockRequest("http://localhost", "get", "/test") + + with pytest.raises(MissingRequiredParameter): + spec.validate_request(request) + + def test_omitted_optional_deprecated_parameter(self): + spec = OpenAPI.from_dict( + { + "openapi": "3.1.0", + "info": {"version": "0", "title": "test"}, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "foo", + "in": "query", + "schema": {}, + "deprecated": True, + }, + ] + } + } + }, + } + ) + + request = MockRequest("http://localhost", "get", "/test") + result = spec.validate_request(request) + + assert result is None + def test_security_not_found(self, request_validator): request = MockRequest( self.host_url, @@ -106,6 +162,7 @@ def test_media_type_not_found(self, request_validator): availableMimetypes=[ "application/json", "application/x-www-form-urlencoded", + "multipart/form-data", "text/plain", ], ) @@ -127,3 +184,60 @@ def test_valid(self, request_validator): result = request_validator.validate(request) assert result is None + + def test_array_parameter_with_empty_default(self): + spec = OpenAPI.from_dict( + { + "openapi": "3.1.0", + "info": {"version": "0", "title": "test"}, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "foo", + "in": "query", + "schema": {"type": "array", "default": []}, + } + ], + "responses": {"200": {"description": ""}}, + }, + } + }, + } + ) + request = MockRequest("http://localhost", "get", "/test") + + result = spec.validate_request(request) + + assert result is None + + def test_array_parameter_with_populated_default(self): + spec = OpenAPI.from_dict( + { + "openapi": "3.1.0", + "info": {"version": "0", "title": "test"}, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "foo", + "in": "query", + "schema": { + "type": "array", + "default": ["a", "b", "c"], + }, + } + ], + "responses": {"200": {"description": ""}}, + }, + } + }, + } + ) + request = MockRequest("http://localhost", "get", "/test") + + result = spec.validate_request(request) + + assert result is None diff --git a/tests/integration/validation/test_response_properties_default_policy.py b/tests/integration/validation/test_response_properties_default_policy.py new file mode 100644 index 00000000..595eddf0 --- /dev/null +++ b/tests/integration/validation/test_response_properties_default_policy.py @@ -0,0 +1,153 @@ +import json + +import pytest + +from openapi_core import Config +from openapi_core import OpenAPI +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse +from openapi_core.validation.response.exceptions import InvalidData + + +def _spec_dict(): + return { + "openapi": "3.0.3", + "info": { + "title": "Strict response properties", + "version": "1.0.0", + }, + "servers": [{"url": "http://example.com"}], + "paths": { + "/resources": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + }, + } + } + }, + "post": { + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + }, + }, + "responses": { + "201": { + "description": "Created", + } + }, + }, + } + }, + "components": { + "schemas": { + "Resource": { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "name": {"type": "string"}, + "description": { + "type": "string", + "nullable": True, + }, + "secret": { + "type": "string", + "writeOnly": True, + }, + }, + "required": ["id"], + } + } + }, + } + + +def test_response_validation_default_allows_missing_optional_properties(): + openapi = OpenAPI.from_dict(_spec_dict()) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps({"id": 1}).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + openapi.validate_response(request, response) + + +def test_response_validation_strict_rejects_missing_documented_properties(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps({"id": 1}).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + with pytest.raises(InvalidData): + openapi.validate_response(request, response) + + +def test_response_validation_strict_allows_nullable_properties_when_present(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps( + { + "id": 1, + "name": "resource", + "description": None, + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + openapi.validate_response(request, response) + + +def test_response_validation_strict_excludes_write_only_properties(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest("http://example.com", "get", "/resources") + response = MockResponse( + data=json.dumps( + { + "id": 1, + "name": "resource", + "description": "description", + } + ).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + openapi.validate_response(request, response) + + +def test_request_validation_ignores_response_properties_default_policy_flag(): + config = Config(response_properties_default_policy="required") + openapi = OpenAPI.from_dict(_spec_dict(), config=config) + request = MockRequest( + "http://example.com", + "post", + "/resources", + content_type="application/json", + data=json.dumps({"id": 1}).encode("utf-8"), + ) + + openapi.validate_request(request) diff --git a/tests/integration/validation/test_response_validators.py b/tests/integration/validation/test_response_validators.py index dcc1c0a3..ea5c93d7 100644 --- a/tests/integration/validation/test_response_validators.py +++ b/tests/integration/validation/test_response_validators.py @@ -16,6 +16,7 @@ from openapi_core.validation.response.exceptions import InvalidData from openapi_core.validation.response.exceptions import InvalidHeader from openapi_core.validation.response.exceptions import MissingData +from openapi_core.validation.response.exceptions import MissingRequiredHeader from openapi_core.validation.schemas.exceptions import InvalidSchemaValue @@ -135,6 +136,32 @@ def test_invalid_header(self, response_validator): with pytest.warns(DeprecationWarning): response_validator.validate(request, response) + def test_missing_deprecated_required_header(self, response_validator): + request = MockRequest( + self.host_url, + "delete", + "/v1/tags", + path_pattern="/v1/tags", + ) + response_json = { + "data": [ + { + "id": 1, + "name": "Sparky", + "ears": { + "healthy": True, + }, + }, + ], + } + response_data = json.dumps(response_json).encode() + response = MockResponse(response_data) + + with pytest.raises(MissingRequiredHeader) as exc_info: + response_validator.validate(request, response) + + assert exc_info.value == MissingRequiredHeader(name="x-delete-confirm") + def test_valid(self, response_validator): request = MockRequest(self.host_url, "get", "/v1/pets") response_json = { diff --git a/tests/integration/validation/test_strict_json_validation.py b/tests/integration/validation/test_strict_json_validation.py new file mode 100644 index 00000000..eb5c4e04 --- /dev/null +++ b/tests/integration/validation/test_strict_json_validation.py @@ -0,0 +1,318 @@ +import json + +import pytest +from jsonschema_path import SchemaPath + +from openapi_core import V30RequestValidator +from openapi_core import V30ResponseValidator +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse +from openapi_core.validation.request.exceptions import InvalidRequestBody +from openapi_core.validation.response.exceptions import InvalidData + + +def _spec_schema_path() -> SchemaPath: + spec_dict = { + "openapi": "3.0.3", + "info": {"title": "Strict JSON Validation", "version": "1.0.0"}, + "servers": [{"url": "http://example.com"}], + "paths": { + "/users": { + "post": { + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + }, + "application/problem+json": { + "schema": {"$ref": "#/components/schemas/User"} + }, + }, + }, + "responses": { + "204": {"description": "No content"}, + }, + }, + "put": { + "requestBody": { + "required": True, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + }, + "encoding": { + "age": {"contentType": "application/json"}, + }, + } + }, + }, + "responses": { + "204": {"description": "No content"}, + }, + }, + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + }, + } + } + }, + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "id": {"type": "string", "format": "uuid"}, + "username": {"type": "string"}, + "age": {"type": "integer"}, + }, + "required": ["id", "username", "age"], + } + } + }, + } + return SchemaPath.from_dict(spec_dict) + + +@pytest.mark.parametrize( + "content_type", + [ + "application/json", + "application/problem+json", + ], +) +def test_response_validator_strict_json_types(content_type: str) -> None: + spec = _spec_schema_path() + validator = V30ResponseValidator(spec) + + request = MockRequest("http://example.com", "get", "/users") + response_json = { + "id": "123e4567-e89b-12d3-a456-426614174000", + "username": "Test User", + "age": "30", + } + response = MockResponse( + json.dumps(response_json).encode("utf-8"), + status_code=200, + content_type=content_type, + ) + + with pytest.raises(InvalidData): + validator.validate(request, response) + + +@pytest.mark.parametrize( + "content_type", + [ + "application/json", + "application/problem+json", + ], +) +def test_request_validator_strict_json_types(content_type: str) -> None: + spec = _spec_schema_path() + validator = V30RequestValidator(spec) + + request_json = { + "id": "123e4567-e89b-12d3-a456-426614174000", + "username": "Test User", + "age": "30", + } + request = MockRequest( + "http://example.com", + "post", + "/users", + content_type=content_type, + data=json.dumps(request_json).encode("utf-8"), + ) + + with pytest.raises(InvalidRequestBody): + validator.validate(request) + + +def test_request_validator_urlencoded_json_part_strict() -> None: + spec = _spec_schema_path() + validator = V30RequestValidator(spec) + + # urlencoded field age is declared as application/json (via encoding) + # and contains a JSON string "30" (invalid for integer schema) + request = MockRequest( + "http://example.com", + "put", + "/users", + content_type="application/x-www-form-urlencoded", + data=( + b"id=123e4567-e89b-12d3-a456-426614174000&" + b"username=Test+User&" + b"age=%2230%22" + ), + ) + + with pytest.raises(InvalidRequestBody): + validator.validate(request) + + +def test_request_validator_error_message_includes_cause_details() -> None: + spec = _spec_schema_path() + validator = V30RequestValidator(spec) + + request_json = { + "id": "123e4567-e89b-12d3-a456-426614174000", + "username": "Test User", + "age": "30", + } + request = MockRequest( + "http://example.com", + "post", + "/users", + content_type="application/json", + data=json.dumps(request_json).encode("utf-8"), + ) + + with pytest.raises(InvalidRequestBody) as exc_info: + validator.validate(request) + + error_message = str(exc_info.value) + assert error_message.startswith("Request body validation error:") + assert "'30' is not of type 'integer'" in error_message + + +def test_request_validator_error_details_are_structured() -> None: + spec = _spec_schema_path() + validator = V30RequestValidator(spec) + + request_json = { + "id": "123e4567-e89b-12d3-a456-426614174000", + "username": "Test User", + "age": "30", + } + request = MockRequest( + "http://example.com", + "post", + "/users", + content_type="application/json", + data=json.dumps(request_json).encode("utf-8"), + ) + + with pytest.raises(InvalidRequestBody) as exc_info: + validator.validate(request) + + details = exc_info.value.details + assert details["error_type"] == "InvalidRequestBody" + assert details["cause_type"] == "InvalidSchemaValue" + assert details["schema_errors"] == [ + { + "message": "'30' is not of type 'integer'", + "path": ["age"], + } + ] + + +def test_response_validator_error_details_are_structured() -> None: + spec = _spec_schema_path() + validator = V30ResponseValidator(spec) + + request = MockRequest("http://example.com", "get", "/users") + response_json = { + "id": "123e4567-e89b-12d3-a456-426614174000", + "username": "Test User", + "age": "30", + } + response = MockResponse( + json.dumps(response_json).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + + with pytest.raises(InvalidData) as exc_info: + validator.validate(request, response) + + details = exc_info.value.details + assert details["error_type"] == "InvalidData" + assert details["cause_type"] == "InvalidSchemaValue" + assert details["schema_errors"] == [ + { + "message": "'30' is not of type 'integer'", + "path": ["age"], + } + ] + + +def test_response_validator_strict_json_nested_types() -> None: + """Test that nested JSON structures (arrays, objects) remain strict.""" + spec_dict = { + "openapi": "3.0.3", + "info": {"title": "Nested JSON Test", "version": "1.0.0"}, + "servers": [{"url": "http://example.com"}], + "paths": { + "/data": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": {"type": "integer"}, + }, + "metadata": { + "type": "object", + "properties": { + "count": { + "type": "integer" + } + }, + }, + }, + } + } + }, + } + } + } + } + }, + } + spec = SchemaPath.from_dict(spec_dict) + validator = V30ResponseValidator(spec) + + request = MockRequest("http://example.com", "get", "/data") + + # Test nested array with string integers (should fail) + response_json = {"ids": ["10", "20", "30"], "metadata": {"count": 5}} + response = MockResponse( + json.dumps(response_json).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + with pytest.raises(InvalidData): + validator.validate(request, response) + + # Test nested object with string integer (should fail) + response_json2 = {"ids": [10, 20, 30], "metadata": {"count": "5"}} + response2 = MockResponse( + json.dumps(response_json2).encode("utf-8"), + status_code=200, + content_type="application/json", + ) + with pytest.raises(InvalidData): + validator.validate(request, response2) diff --git a/tests/unit/casting/test_schema_casters.py b/tests/unit/casting/test_schema_casters.py index 39c0235c..bad8098e 100644 --- a/tests/unit/casting/test_schema_casters.py +++ b/tests/unit/casting/test_schema_casters.py @@ -7,9 +7,14 @@ class TestSchemaCaster: @pytest.fixture - def caster_factory(self): + def spec(self): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + @pytest.fixture + def caster_factory(self, spec): def create_caster(schema): - return oas31_schema_casters_factory.create(schema) + return oas31_schema_casters_factory.create(spec, schema) return create_caster @@ -61,3 +66,69 @@ def test_array_invalid_value(self, value, caster_factory): CastError, match=f"Failed to cast value to array type: {value}" ): caster_factory(schema).cast(value) + + @pytest.mark.parametrize( + "composite_type,schema_type,value,expected", + [ + ("allOf", "integer", "2", 2), + ("anyOf", "number", "3.14", 3.14), + ("oneOf", "boolean", "false", False), + ("oneOf", "boolean", "true", True), + ], + ) + def test_composite_primitive( + self, caster_factory, composite_type, schema_type, value, expected + ): + spec = { + composite_type: [{"type": schema_type}], + } + schema = SchemaPath.from_dict(spec) + + result = caster_factory(schema).cast(value) + + assert result == expected + + @pytest.mark.parametrize( + "schemas,value,expected", + [ + # If string is evaluated first, it succeeds and returns string + ([{"type": "string"}, {"type": "integer"}], "123", "123"), + # If integer is evaluated first, it succeeds and returns int + ([{"type": "integer"}, {"type": "string"}], "123", 123), + ], + ) + def test_oneof_greedy_casting_edge_case( + self, caster_factory, schemas, value, expected + ): + """ + Documents the edge case that AnyCaster's oneOf/anyOf logic is greedy. + It returns the first successfully casted value based on the order in the list. + """ + spec = { + "oneOf": schemas, + } + schema = SchemaPath.from_dict(spec) + + result = caster_factory(schema).cast(value) + + assert result == expected + # Ensure exact type matches to prevent 123 == "123" test bypass issues + assert type(result) is type(expected) + + def test_allof_sequential_mutation_edge_case(self, caster_factory): + """ + Documents the edge case that AnyCaster's allOf logic sequentially mutates the value. + The first schema casts "2" to an int (2). The second schema (number) + receives the int 2, casts it to float (2.0), and returns the float. + """ + spec = { + "allOf": [{"type": "integer"}, {"type": "number"}], + } + schema = SchemaPath.from_dict(spec) + value = "2" + + result = caster_factory(schema).cast(value) + + # "2" -> int(2) -> float(2.0) + assert result == 2.0 + assert type(result) is float diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index cb19dafb..20fba75c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -48,6 +48,20 @@ def spec_v31(): ) +@pytest.fixture +def spec_v32(): + return SchemaPath.from_dict( + { + "openapi": "3.2.0", + "info": { + "title": "Spec", + "version": "0.0.1", + }, + "paths": {}, + } + ) + + @pytest.fixture def spec_invalid(): return SchemaPath.from_dict({}) diff --git a/tests/unit/deserializing/test_media_types_deserializers.py b/tests/unit/deserializing/test_media_types_deserializers.py index 5b8104a2..fd5b3b85 100644 --- a/tests/unit/deserializing/test_media_types_deserializers.py +++ b/tests/unit/deserializing/test_media_types_deserializers.py @@ -3,31 +3,43 @@ import pytest from jsonschema_path import SchemaPath +from openapi_core.casting.schemas import oas31_schema_casters_factory from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.deserializing.media_types import media_type_deserializers +from openapi_core.deserializing.media_types import ( + media_type_deserializers as default_media_type_deserializers, +) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) -from openapi_core.deserializing.styles import style_deserializers_factory +from openapi_core.validation.schemas import oas31_schema_validators_factory class TestMediaTypeDeserializer: @pytest.fixture - def deserializer_factory(self): + def spec(self): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + @pytest.fixture + def deserializer_factory(self, spec): def create_deserializer( mimetype, schema=None, + schema_validator=None, encoding=None, parameters=None, - media_type_deserializers=media_type_deserializers, + media_type_deserializers=default_media_type_deserializers, extra_media_type_deserializers=None, ): - return MediaTypeDeserializersFactory( - style_deserializers_factory, - media_type_deserializers, + + return MediaTypeDeserializersFactory.from_schema_casters_factory( + oas31_schema_casters_factory, + media_type_deserializers=media_type_deserializers, ).create( + spec, mimetype, schema=schema, + schema_validator=schema_validator, parameters=parameters, encoding=encoding, extra_media_type_deserializers=extra_media_type_deserializers, @@ -201,6 +213,24 @@ def test_urlencoded_form_empty(self, deserializer_factory): assert result == {} + def test_urlencoded_form_empty_value(self, deserializer_factory): + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "type": "object", + "properties": { + "name": { + "type": "string", + }, + }, + } + schema = SchemaPath.from_dict(schema_dict) + deserializer = deserializer_factory(mimetype, schema=schema) + value = b"name=" + + result = deserializer.deserialize(value) + + assert result == {"name": ""} + def test_urlencoded_form_simple(self, deserializer_factory): mimetype = "application/x-www-form-urlencoded" schema_dict = { @@ -229,7 +259,7 @@ def test_urlencoded_form_simple(self, deserializer_factory): "name": "foo bar", } - def test_urlencoded_complex(self, deserializer_factory): + def test_urlencoded_complex_cast_error(self, deserializer_factory): mimetype = "application/x-www-form-urlencoded" schema_dict = { "type": "object", @@ -246,10 +276,30 @@ def test_urlencoded_complex(self, deserializer_factory): deserializer = deserializer_factory(mimetype, schema=schema) value = b"prop=a&prop=b&prop=c" + with pytest.raises(DeserializeError): + deserializer.deserialize(value) + + def test_urlencoded_complex(self, deserializer_factory): + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "type": "object", + "properties": { + "prop": { + "type": "array", + "items": { + "type": "integer", + }, + }, + }, + } + schema = SchemaPath.from_dict(schema_dict) + deserializer = deserializer_factory(mimetype, schema=schema) + value = b"prop=1&prop=2&prop=3" + result = deserializer.deserialize(value) assert result == { - "prop": ["a", "b", "c"], + "prop": [1, 2, 3], } def test_urlencoded_content_type(self, deserializer_factory): @@ -321,9 +371,9 @@ def test_urlencoded_deepobject(self, deserializer_factory): assert result == { "color": { - "R": "100", - "G": "200", - "B": "150", + "R": 100, + "G": 200, + "B": 150, }, } @@ -438,3 +488,171 @@ def custom_deserializer(value): ) assert result == deserialized + + def test_urlencoded_oneof_integer_field(self, spec, deserializer_factory): + """Test issue #932: oneOf with urlencoded should match schema with integer field""" + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "oneOf": [ + { + "type": "object", + "properties": { + "typeA": {"type": "string"}, + "fieldA": {"type": "string"}, + }, + "required": ["typeA", "fieldA"], + }, + { + "type": "object", + "properties": { + "typeB": {"type": "string"}, + "fieldB": {"type": "integer"}, + }, + "required": ["typeB", "fieldB"], + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + schema_validator = oas31_schema_validators_factory.create(spec, schema) + deserializer = deserializer_factory( + mimetype, schema=schema, schema_validator=schema_validator + ) + # String "123" should be cast to integer 123 to match the second oneOf option + value = b"typeB=test&fieldB=123" + + result = deserializer.deserialize(value) + + assert result == { + "typeB": "test", + "fieldB": 123, + } + + def test_urlencoded_oneof_string_field(self, spec, deserializer_factory): + """Test issue #932: oneOf with urlencoded should match schema with string fields""" + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "oneOf": [ + { + "type": "object", + "properties": { + "typeA": {"type": "string"}, + "fieldA": {"type": "string"}, + }, + "required": ["typeA", "fieldA"], + }, + { + "type": "object", + "properties": { + "typeB": {"type": "string"}, + "fieldB": {"type": "integer"}, + }, + "required": ["typeB", "fieldB"], + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + schema_validator = oas31_schema_validators_factory.create(spec, schema) + deserializer = deserializer_factory( + mimetype, schema=schema, schema_validator=schema_validator + ) + value = b"typeA=test&fieldA=value" + + result = deserializer.deserialize(value) + + assert result == { + "typeA": "test", + "fieldA": "value", + } + + def test_urlencoded_anyof_with_types(self, spec, deserializer_factory): + """Test anyOf with urlencoded and type coercion""" + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "anyOf": [ + { + "type": "object", + "properties": { + "count": {"type": "integer"}, + "active": {"type": "boolean"}, + }, + }, + { + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + schema_validator = oas31_schema_validators_factory.create(spec, schema) + deserializer = deserializer_factory( + mimetype, schema=schema, schema_validator=schema_validator + ) + # Should match both schemas after type coercion + value = b"count=42&active=true&name=test" + + result = deserializer.deserialize(value) + + assert result == { + "count": 42, + "active": True, + "name": "test", + } + + def test_urlencoded_oneof_boolean_field(self, spec, deserializer_factory): + """Test oneOf with boolean field requiring type coercion""" + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "oneOf": [ + { + "type": "object", + "properties": { + "enabled": {"type": "boolean"}, + "mode": {"type": "string"}, + }, + "required": ["enabled"], + }, + { + "type": "object", + "properties": { + "disabled": {"type": "boolean"}, + "reason": {"type": "string"}, + }, + "required": ["disabled"], + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + schema_validator = oas31_schema_validators_factory.create(spec, schema) + deserializer = deserializer_factory( + mimetype, schema=schema, schema_validator=schema_validator + ) + # String "true" should be cast to boolean True + value = b"enabled=true&mode=auto" + + result = deserializer.deserialize(value) + + assert result == { + "enabled": True, + "mode": "auto", + } + + def test_urlencoded_form_with_array_default(self, deserializer_factory): + mimetype = "application/x-www-form-urlencoded" + schema_dict = { + "type": "object", + "properties": { + "tags": { + "type": "array", + "default": [], + }, + }, + } + schema = SchemaPath.from_dict(schema_dict) + deserializer = deserializer_factory(mimetype, schema=schema) + value = b"" + + result = deserializer.deserialize(value) + + assert result == {"tags": []} diff --git a/tests/unit/deserializing/test_styles_deserializers.py b/tests/unit/deserializing/test_styles_deserializers.py index 29e52d25..2262fcd5 100644 --- a/tests/unit/deserializing/test_styles_deserializers.py +++ b/tests/unit/deserializing/test_styles_deserializers.py @@ -2,20 +2,34 @@ from jsonschema_path import SchemaPath from werkzeug.datastructures import ImmutableMultiDict +from openapi_core.casting.schemas import oas31_schema_casters_factory from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.deserializing.styles import style_deserializers_factory +from openapi_core.deserializing.styles import style_deserializers +from openapi_core.deserializing.styles.factories import ( + StyleDeserializersFactory, +) from openapi_core.schema.parameters import get_style_and_explode class TestParameterStyleDeserializer: @pytest.fixture - def deserializer_factory(self): + def spec(self): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + @pytest.fixture + def deserializer_factory(self, spec): + style_deserializers_factory = StyleDeserializersFactory( + oas31_schema_casters_factory, + style_deserializers=style_deserializers, + ) + def create_deserializer(param, name=None): name = name or param["name"] style, explode = get_style_and_explode(param) schema = param / "schema" return style_deserializers_factory.create( - style, explode, schema, name=name + spec, schema, style, explode, name=name ) return create_deserializer diff --git a/tests/unit/extensions/test_factories.py b/tests/unit/extensions/test_factories.py index d50fd551..5af0ffb3 100644 --- a/tests/unit/extensions/test_factories.py +++ b/tests/unit/extensions/test_factories.py @@ -33,7 +33,9 @@ def test_dynamic_model(self): assert is_dataclass(test_model_class) assert test_model_class.__name__ == "TestModel" assert list(test_model_class.__dataclass_fields__.keys()) == ["name"] - assert test_model_class.__dataclass_fields__["name"].type == str(Any) + assert str(test_model_class.__dataclass_fields__["name"].type) == str( + Any + ) def test_model_path(self, loaded_model_class): factory = ModelPathFactory() diff --git a/tests/unit/templating/test_paths_finders.py b/tests/unit/templating/test_paths_finders.py index 63505a48..a864d948 100644 --- a/tests/unit/templating/test_paths_finders.py +++ b/tests/unit/templating/test_paths_finders.py @@ -171,9 +171,7 @@ class BaseTestServerNotFound: @pytest.fixture def servers(self): return [ - SchemaPath.from_dict( - {"url": "http://petstore.swagger.io/resource"} - ) + {"url": "http://petstore.swagger.io/resource"}, ] def test_raises(self, finder): diff --git a/tests/unit/templating/test_templating_util.py b/tests/unit/templating/test_paths_parsers.py similarity index 68% rename from tests/unit/templating/test_templating_util.py rename to tests/unit/templating/test_paths_parsers.py index 815f6cb0..2db80cce 100644 --- a/tests/unit/templating/test_templating_util.py +++ b/tests/unit/templating/test_paths_parsers.py @@ -1,14 +1,15 @@ import pytest -from openapi_core.templating.util import search +from openapi_core.templating.paths.parsers import PathParser class TestSearch: def test_endswith(self): path_pattern = "/{test}/test" + parser = PathParser(path_pattern, post_expression="$") full_url_pattern = "/test1/test/test2/test" - result = search(path_pattern, full_url_pattern) + result = parser.search(full_url_pattern) assert result.named == { "test": "test2", @@ -16,9 +17,10 @@ def test_endswith(self): def test_exact(self): path_pattern = "/{test}/test" + parser = PathParser(path_pattern, post_expression="$") full_url_pattern = "/test/test" - result = search(path_pattern, full_url_pattern) + result = parser.search(full_url_pattern) assert result.named == { "test": "test", @@ -33,19 +35,13 @@ def test_exact(self): ], ) def test_chars_valid(self, path_pattern, expected): + parser = PathParser(path_pattern, post_expression="$") full_url_pattern = "/test/test" - result = search(path_pattern, full_url_pattern) + result = parser.search(full_url_pattern) assert result.named == expected - @pytest.mark.xfail( - reason=( - "Special characters of regex not supported. " - "See https://github.com/python-openapi/openapi-core/issues/672" - ), - strict=True, - ) @pytest.mark.parametrize( "path_pattern,expected", [ @@ -53,8 +49,9 @@ def test_chars_valid(self, path_pattern, expected): ], ) def test_special_chars_valid(self, path_pattern, expected): + parser = PathParser(path_pattern, post_expression="$") full_url_pattern = "/test/test" - result = search(path_pattern, full_url_pattern) + result = parser.search(full_url_pattern) assert result.named == expected diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index a98f7a8b..3ba84ab0 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -1,10 +1,26 @@ from pathlib import Path +from unittest import mock import pytest from openapi_core import Config from openapi_core import OpenAPI +from openapi_core import V3RequestUnmarshaller +from openapi_core import V3RequestValidator +from openapi_core import V3ResponseUnmarshaller +from openapi_core import V3ResponseValidator from openapi_core.exceptions import SpecError +from openapi_core.protocols import Request +from openapi_core.protocols import Response +from openapi_core.protocols import WebhookRequest +from openapi_core.unmarshalling.request import V32RequestUnmarshaller +from openapi_core.unmarshalling.request import V32WebhookRequestUnmarshaller +from openapi_core.unmarshalling.response import V32ResponseUnmarshaller +from openapi_core.unmarshalling.response import V32WebhookResponseUnmarshaller +from openapi_core.validation.request import V32RequestValidator +from openapi_core.validation.request import V32WebhookRequestValidator +from openapi_core.validation.response import V32ResponseValidator +from openapi_core.validation.response import V32WebhookResponseValidator class TestOpenAPIFromPath: @@ -22,7 +38,7 @@ def test_valid(self, create_file): result = OpenAPI.from_path(path) assert type(result) == OpenAPI - assert result.spec.contents() == spec_dict + assert result.spec.read_value() == spec_dict class TestOpenAPIFromFilePath: @@ -39,7 +55,7 @@ def test_valid(self, create_file): result = OpenAPI.from_file_path(file_path) assert type(result) == OpenAPI - assert result.spec.contents() == spec_dict + assert result.spec.read_value() == spec_dict class TestOpenAPIFromFile: @@ -57,7 +73,7 @@ def test_valid(self, create_file): result = OpenAPI.from_file(f) assert type(result) == OpenAPI - assert result.spec.contents() == spec_dict + assert result.spec.read_value() == spec_dict class TestOpenAPIFromDict: @@ -74,4 +90,127 @@ def test_check_skipped(self): result = OpenAPI.from_dict(spec_dict, config=config) assert type(result) == OpenAPI - assert result.spec.contents() == spec_dict + assert result.spec.read_value() == spec_dict + + +class TestOpenAPIVersion32: + def test_v3_aliases_use_v32(self): + assert V3RequestValidator is V32RequestValidator + assert V3ResponseValidator is V32ResponseValidator + assert V3RequestUnmarshaller is V32RequestUnmarshaller + assert V3ResponseUnmarshaller is V32ResponseUnmarshaller + + def test_default_request_validator(self, spec_v32): + result = OpenAPI(spec_v32) + + assert result.request_validator_cls is V32RequestValidator + + def test_default_response_validator(self, spec_v32): + result = OpenAPI(spec_v32) + + assert result.response_validator_cls is V32ResponseValidator + + def test_default_request_unmarshaller(self, spec_v32): + result = OpenAPI(spec_v32) + + assert result.request_unmarshaller_cls is V32RequestUnmarshaller + + def test_default_response_unmarshaller(self, spec_v32): + result = OpenAPI(spec_v32) + + assert result.response_unmarshaller_cls is V32ResponseUnmarshaller + + def test_default_webhook_request_validator(self, spec_v32): + result = OpenAPI(spec_v32) + + assert ( + result.webhook_request_validator_cls is V32WebhookRequestValidator + ) + + def test_default_webhook_response_validator(self, spec_v32): + result = OpenAPI(spec_v32) + + assert ( + result.webhook_response_validator_cls + is V32WebhookResponseValidator + ) + + def test_default_webhook_request_unmarshaller(self, spec_v32): + result = OpenAPI(spec_v32) + + assert ( + result.webhook_request_unmarshaller_cls + is V32WebhookRequestUnmarshaller + ) + + def test_default_webhook_response_unmarshaller(self, spec_v32): + result = OpenAPI(spec_v32) + + assert ( + result.webhook_response_unmarshaller_cls + is V32WebhookResponseUnmarshaller + ) + + +class TestOpenAPIIterErrors: + @mock.patch( + "openapi_core.validation.request.validators.V32RequestValidator." + "iter_errors", + ) + def test_iter_apicall_request_errors(self, mock_iter_errors, spec_v32): + openapi = OpenAPI(spec_v32) + request = mock.Mock(spec=Request) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = openapi.iter_apicall_request_errors(request) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + @mock.patch( + "openapi_core.validation.request.validators.V32WebhookRequestValidator." + "iter_errors", + ) + def test_iter_request_errors_webhook(self, mock_iter_errors, spec_v32): + openapi = OpenAPI(spec_v32) + request = mock.Mock(spec=WebhookRequest) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = openapi.iter_request_errors(request) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + @mock.patch( + "openapi_core.validation.response.validators.V32ResponseValidator." + "iter_errors", + ) + def test_iter_apicall_response_errors(self, mock_iter_errors, spec_v32): + openapi = OpenAPI(spec_v32) + request = mock.Mock(spec=Request) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = openapi.iter_apicall_response_errors(request, response) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) + + @mock.patch( + "openapi_core.validation.response.validators.V32WebhookResponseValidator." + "iter_errors", + ) + def test_iter_response_errors_webhook(self, mock_iter_errors, spec_v32): + openapi = OpenAPI(spec_v32) + request = mock.Mock(spec=WebhookRequest) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = openapi.iter_response_errors(request, response) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) diff --git a/tests/unit/test_paths_spec.py b/tests/unit/test_paths_spec.py deleted file mode 100644 index 8167abf3..00000000 --- a/tests/unit/test_paths_spec.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest - -from openapi_core import Spec - - -class TestSpecFromDict: - def test_deprecated(self): - schema = {} - - with pytest.warns(DeprecationWarning): - Spec.from_dict(schema) diff --git a/tests/unit/test_shortcuts.py b/tests/unit/test_shortcuts.py index 9a3f36c9..2611bef8 100644 --- a/tests/unit/test_shortcuts.py +++ b/tests/unit/test_shortcuts.py @@ -3,6 +3,12 @@ import pytest from openapi_spec_validator import OpenAPIV31SpecValidator +from openapi_core import iter_apicall_request_errors +from openapi_core import iter_apicall_response_errors +from openapi_core import iter_request_errors +from openapi_core import iter_response_errors +from openapi_core import iter_webhook_request_errors +from openapi_core import iter_webhook_response_errors from openapi_core import unmarshal_apicall_request from openapi_core import unmarshal_apicall_response from openapi_core import unmarshal_request @@ -540,6 +546,22 @@ def test_request(self, mock_validate, spec_v31): mock_validate.assert_called_once_with(request) +class TestIterAPICallRequestErrors: + @mock.patch( + "openapi_core.validation.request.validators.APICallRequestValidator." + "iter_errors", + ) + def test_request(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=Request) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_apicall_request_errors(request, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + class TestValidateWebhookRequest: def test_spec_not_detected(self, spec_invalid): request = mock.Mock(spec=WebhookRequest) @@ -591,6 +613,22 @@ def test_request(self, mock_validate, spec_v31): mock_validate.assert_called_once_with(request) +class TestIterWebhookRequestErrors: + @mock.patch( + "openapi_core.validation.request.validators.WebhookRequestValidator." + "iter_errors", + ) + def test_request(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=WebhookRequest) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_webhook_request_errors(request, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + class TestValidateRequest: def test_spec_invalid(self, spec_invalid): request = mock.Mock(spec=Request) @@ -752,6 +790,36 @@ def test_webhook_cls_invalid(self, spec_v31): validate_request(request, spec=spec_v31, cls=Exception) +class TestIterRequestErrors: + @mock.patch( + "openapi_core.validation.request.validators.APICallRequestValidator." + "iter_errors", + ) + def test_request(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=Request) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_request_errors(request, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + @mock.patch( + "openapi_core.validation.request.validators.V31WebhookRequestValidator." + "iter_errors", + ) + def test_webhook_request(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=WebhookRequest) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_request_errors(request, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request) + + class TestValidateAPICallResponse: def test_spec_not_detected(self, spec_invalid): request = mock.Mock(spec=Request) @@ -812,6 +880,23 @@ def test_request_response(self, mock_validate, spec_v31): mock_validate.assert_called_once_with(request, response) +class TestIterAPICallResponseErrors: + @mock.patch( + "openapi_core.validation.response.validators.APICallResponseValidator." + "iter_errors", + ) + def test_request_response(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=Request) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_apicall_response_errors(request, response, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) + + class TestValidateWebhookResponse: def test_spec_not_detected(self, spec_invalid): request = mock.Mock(spec=WebhookRequest) @@ -879,6 +964,23 @@ def test_request_response(self, mock_validate, spec_v31): mock_validate.assert_called_once_with(request, response) +class TestIterWebhookResponseErrors: + @mock.patch( + "openapi_core.validation.response.validators.WebhookResponseValidator." + "iter_errors", + ) + def test_request_response(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=WebhookRequest) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_webhook_response_errors(request, response, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) + + class TestValidateResponse: def test_spec_not_detected(self, spec_invalid): request = mock.Mock(spec=Request) @@ -1012,3 +1114,35 @@ def test_webhook_cls_type_invalid(self, spec_v31): with pytest.raises(TypeError): validate_response(request, response, spec=spec_v31, cls=Exception) + + +class TestIterResponseErrors: + @mock.patch( + "openapi_core.validation.response.validators.APICallResponseValidator." + "iter_errors", + ) + def test_request_response(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=Request) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_response_errors(request, response, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) + + @mock.patch( + "openapi_core.validation.response.validators.V31WebhookResponseValidator." + "iter_errors", + ) + def test_webhook_request(self, mock_iter_errors, spec_v31): + request = mock.Mock(spec=WebhookRequest) + response = mock.Mock(spec=Response) + errors_iter = iter([ValueError("oops")]) + mock_iter_errors.return_value = errors_iter + + result = iter_response_errors(request, response, spec=spec_v31) + + assert result is errors_iter + mock_iter_errors.assert_called_once_with(request, response) diff --git a/tests/unit/unmarshalling/test_path_item_params_validator.py b/tests/unit/unmarshalling/test_path_item_params_validator.py index cf41e6d9..1dc56a3f 100644 --- a/tests/unit/unmarshalling/test_path_item_params_validator.py +++ b/tests/unit/unmarshalling/test_path_item_params_validator.py @@ -9,12 +9,18 @@ from openapi_core.casting.schemas.exceptions import CastError from openapi_core.datatypes import Parameters from openapi_core.testing import MockRequest +from openapi_core.unmarshalling.request.unmarshallers import ( + V30RequestParametersUnmarshaller, +) from openapi_core.validation.request.exceptions import MissingRequiredParameter from openapi_core.validation.request.exceptions import ParameterValidationError +from openapi_core.validation.request.validators import ( + V30RequestParametersValidator, +) class TestPathItemParamsValidator: - @pytest.fixture(scope="session") + @pytest.fixture def spec_dict(self): return { "openapi": "3.0.0", @@ -43,11 +49,11 @@ def spec_dict(self): }, } - @pytest.fixture(scope="session") + @pytest.fixture def spec(self, spec_dict): return SchemaPath.from_dict(spec_dict) - @pytest.fixture(scope="session") + @pytest.fixture def request_unmarshaller(self, spec): return V30RequestUnmarshaller(spec) @@ -169,3 +175,70 @@ def test_request_object_deep_object_params(self, spec, spec_dict): assert is_dataclass(result.parameters.query["paramObj"]) assert result.parameters.query["paramObj"].count == 2 assert result.parameters.query["paramObj"].name == "John" + + def test_request_override_param_uniqueness_parameters_validator( + self, spec, spec_dict + ): + # add parameter on operation with same name as on path but + # different location + spec_dict["paths"]["/resource"]["get"]["parameters"] = [ + { + # full valid parameter object required + "name": "resId", + "in": "header", + "required": False, + "schema": { + "type": "integer", + }, + } + ] + request = MockRequest("http://example.com", "get", "/resource") + with pytest.raises(MissingRequiredParameter): + validate_request( + request, + spec, + base_url="http://example.com", + cls=V30RequestParametersValidator, + ) + + def test_request_object_deep_object_params_parameters_unmarshaller( + self, spec, spec_dict + ): + # override path parameter on operation + spec_dict["paths"]["/resource"]["parameters"] = [ + { + # full valid parameter object required + "name": "paramObj", + "in": "query", + "required": True, + "schema": { + "x-model": "paramObj", + "type": "object", + "properties": { + "count": {"type": "integer"}, + "name": {"type": "string"}, + }, + }, + "explode": True, + "style": "deepObject", + } + ] + + request = MockRequest( + "http://example.com", + "get", + "/resource", + args={"paramObj[count]": 2, "paramObj[name]": "John"}, + ) + result = unmarshal_request( + request, + spec, + base_url="http://example.com", + cls=V30RequestParametersUnmarshaller, + ) + + assert len(result.errors) == 0 + assert len(result.parameters.query) == 1 + assert is_dataclass(result.parameters.query["paramObj"]) + assert result.parameters.query["paramObj"].count == 2 + assert result.parameters.query["paramObj"].name == "John" diff --git a/tests/unit/unmarshalling/test_schema_unmarshallers.py b/tests/unit/unmarshalling/test_schema_unmarshallers.py index 5a8fe12e..e86e06da 100644 --- a/tests/unit/unmarshalling/test_schema_unmarshallers.py +++ b/tests/unit/unmarshalling/test_schema_unmarshallers.py @@ -19,7 +19,13 @@ @pytest.fixture -def schema_unmarshaller_factory(): +def spec(): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + +@pytest.fixture +def schema_unmarshaller_factory(spec): def create_unmarshaller( validators_factory, schema, @@ -31,6 +37,7 @@ def create_unmarshaller( validators_factory, oas30_types_unmarshaller, ).create( + spec, schema, format_validators=format_validators, extra_format_validators=extra_format_validators, diff --git a/tests/unit/validation/schemas/test_schemas_factories.py b/tests/unit/validation/schemas/test_schemas_factories.py new file mode 100644 index 00000000..896236d8 --- /dev/null +++ b/tests/unit/validation/schemas/test_schemas_factories.py @@ -0,0 +1,59 @@ +from typing import cast +from unittest.mock import patch + +from jsonschema._format import FormatChecker +from jsonschema.protocols import Validator + +from openapi_core.validation.schemas.factories import ( + DialectSchemaValidatorsFactory, +) + + +class MockValidator: + FORMAT_CHECKER = FormatChecker() + + +class TestDialectSchemaValidatorsFactoryCaching: + def test_get_validator_class_for_dialect_is_cached(self): + factory = DialectSchemaValidatorsFactory( + schema_validator_cls=cast(type[Validator], MockValidator), + default_jsonschema_dialect_id="http://json-schema.org/draft-04/schema#", + format_checker=FormatChecker(), + ) + + with patch( + "openapi_core.validation.schemas.factories.validator_for" + ) as mock_validator_for: + mock_validator_for.return_value = "MockedClass" + + # Call first time + result1 = factory._get_validator_class_for_dialect( + "http://json-schema.org/draft-04/schema#" + ) + + # Call second time with same dialect + result2 = factory._get_validator_class_for_dialect( + "http://json-schema.org/draft-04/schema#" + ) + + # Assert results are same + assert result1 == "MockedClass" + assert result2 == "MockedClass" + + # Assert `validator_for` was only called once because of cache + mock_validator_for.assert_called_once_with( + {"$schema": "http://json-schema.org/draft-04/schema#"}, + default=None, + ) + + # Let's also check with another dialect + with patch( + "openapi_core.validation.schemas.factories.validator_for" + ) as mock_validator_for2: + mock_validator_for2.return_value = "MockedClass2" + + result3 = factory._get_validator_class_for_dialect( + "https://json-schema.org/draft/2020-12/schema" + ) + assert result3 == "MockedClass2" + mock_validator_for2.assert_called_once() diff --git a/tests/unit/validation/test_schema_validators.py b/tests/unit/validation/test_schema_validators.py index 4732a113..2dea1e10 100644 --- a/tests/unit/validation/test_schema_validators.py +++ b/tests/unit/validation/test_schema_validators.py @@ -9,9 +9,14 @@ class TestSchemaValidate: @pytest.fixture - def validator_factory(self): + def spec(self): + spec_dict = {} + return SchemaPath.from_dict(spec_dict) + + @pytest.fixture + def validator_factory(self, spec): def create_validator(schema): - return oas30_write_schema_validators_factory.create(schema) + return oas30_write_schema_validators_factory.create(spec, schema) return create_validator @@ -213,3 +218,141 @@ def test_number_multiple_of(self, value, validator_factory): result = validator_factory(spec).validate(value) assert result is None + + def test_additional_properties_omitted_default_allows_extra(self, spec): + schema_dict = { + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + schema = SchemaPath.from_dict(schema_dict) + value = { + "name": "openapi-core", + "extra": "allowed by default", + } + + result = oas30_write_schema_validators_factory.create( + spec, schema + ).validate(value) + + assert result is None + + def test_additional_properties_omitted_strict_rejects_extra(self, spec): + schema_dict = { + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + schema = SchemaPath.from_dict(schema_dict) + value = { + "name": "openapi-core", + "extra": "not allowed in strict mode", + } + + with pytest.raises(InvalidSchemaValue): + oas30_write_schema_validators_factory.create( + spec, + schema, + forbid_unspecified_additional_properties=True, + ).validate(value) + + def test_additional_properties_true_strict_allows_extra(self, spec): + schema_dict = { + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + "additionalProperties": True, + } + schema = SchemaPath.from_dict(schema_dict) + value = { + "name": "openapi-core", + "extra": "explicitly allowed", + } + + result = oas30_write_schema_validators_factory.create( + spec, + schema, + forbid_unspecified_additional_properties=True, + ).validate(value) + + assert result is None + + def test_enforce_properties_required_rejects_missing_property(self, spec): + schema_dict = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + "required": ["name"], + } + schema = SchemaPath.from_dict(schema_dict) + + with pytest.raises(InvalidSchemaValue): + oas30_write_schema_validators_factory.create( + spec, + schema, + enforce_properties_required=True, + ).validate({"name": "openapi-core"}) + + def test_enforce_properties_required_ignores_write_only_fields(self, spec): + schema_dict = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "secret": { + "type": "string", + "writeOnly": True, + }, + }, + "required": ["name"], + } + schema = SchemaPath.from_dict(schema_dict) + + result = oas30_write_schema_validators_factory.create( + spec, + schema, + enforce_properties_required=True, + ).validate({"name": "openapi-core"}) + + assert result is None + + def test_enforce_properties_required_applies_to_nested_composed_schemas( + self, + spec, + ): + schema_dict = { + "allOf": [ + { + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "version": {"type": "integer"}, + }, + } + }, + }, + ] + } + schema = SchemaPath.from_dict(schema_dict) + + with pytest.raises(InvalidSchemaValue): + oas30_write_schema_validators_factory.create( + spec, + schema, + enforce_properties_required=True, + ).validate({"name": "openapi-core", "meta": {}})