pax_global_header 0000666 0000000 0000000 00000000064 15163577675 0014540 g ustar 00root root 0000000 0000000 52 comment=21f62cecf53a218e0f3066c55eb7c9bad5373ff5
python-openapi-openapi-core-d6cdb4f/ 0000775 0000000 0000000 00000000000 15163577675 0017647 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/.editorconfig 0000664 0000000 0000000 00000000253 15163577675 0022324 0 ustar 00root root 0000000 0000000 root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
[*.py]
indent_size = 4
python-openapi-openapi-core-d6cdb4f/.github/ 0000775 0000000 0000000 00000000000 15163577675 0021207 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/.github/FUNDING.yml 0000664 0000000 0000000 00000000020 15163577675 0023014 0 ustar 00root root 0000000 0000000 github: [p1c2u]
python-openapi-openapi-core-d6cdb4f/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15163577675 0023372 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/.github/ISSUE_TEMPLATE/00_bug_report.yml 0000664 0000000 0000000 00000004607 15163577675 0026573 0 ustar 00root root 0000000 0000000 name: "Report a Bug"
description: "Report a bug about unexpected error, a crash, or otherwise incorrect behavior while using the library."
title: "[Bug]: "
labels: ["kind/bug"]
body:
- type: markdown
attributes:
value: |
Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What happened?
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: Please list the steps required to reproduce the issue. As minimally and precisely as possible.
validations:
required: true
- type: input
id: openapi_core_version
attributes:
label: OpenAPI Core Version
description: The semantic version of OpenAPI Core used when experiencing the bug. If multiple versions have been tested, a comma separated list.
placeholder: "X.Y.Z"
validations:
required: true
- type: input
id: openapi_core_integration
attributes:
label: OpenAPI Core Integration
description: What integration did you use.
placeholder: "django, flask, etc."
validations:
required: true
- type: textarea
id: affected
attributes:
label: Affected Area(s)
description: Please list the affected area(s).
placeholder: "casting, dependencies, deserializing, documentation, schema, security, unmarshalling, validation"
validations:
required: false
- type: textarea
id: references
attributes:
label: References
description: |
Where possible, please supply links to documentations, other GitHub issues (open or closed) or pull requests that give additional context.
validations:
required: false
- type: textarea
id: other
attributes:
label: Anything else we need to know?
validations:
required: false
- type: dropdown
id: will_contribute
attributes:
label: Would you like to implement a fix?
description: |
If you plan to implement a fix for this.
options:
- "No"
- "Yes"
validations:
required: false
python-openapi-openapi-core-d6cdb4f/.github/ISSUE_TEMPLATE/01_enhancement.yml 0000664 0000000 0000000 00000001702 15163577675 0026702 0 ustar 00root root 0000000 0000000 name: "Request new Feature"
description: "Provide supporting details for an enhancement for the library."
title: "[Feature]: "
labels: ["kind/enhancement"]
body:
- type: textarea
id: feature
attributes:
label: Suggested Behavior
description: What would you like to be added?
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
validations:
required: true
- type: textarea
id: references
attributes:
label: References
description: |
Where possible, please supply links to documentations that give additional context.
validations:
required: false
- type: dropdown
id: will_contribute
attributes:
label: Would you like to implement a feature?
description: |
If you plan to implement a feature for this.
options:
- "No"
- "Yes"
validations:
required: false
python-openapi-openapi-core-d6cdb4f/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000374 15163577675 0025366 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
contact_links:
- name: "Python OpenAPI Contributing: Reporting Bugs"
url: https://openapi-core.readthedocs.io/en/latest/contributing.html#reporting-bugs
about: Read guidance about Reporting Bugs in the repository.
python-openapi-openapi-core-d6cdb4f/.github/dependabot.yml 0000664 0000000 0000000 00000000315 15163577675 0024036 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
python-openapi-openapi-core-d6cdb4f/.github/workflows/ 0000775 0000000 0000000 00000000000 15163577675 0023244 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/.github/workflows/build-docs.yml 0000664 0000000 0000000 00000003023 15163577675 0026012 0 ustar 00root root 0000000 0000000 name: CI / Docs
on:
push:
pull_request:
types: [opened, synchronize]
jobs:
docs_build:
name: "Build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up 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 }}-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 mkdocs build --clean --site-dir ./_build/html --config-file mkdocs.yml
- uses: actions/upload-artifact@v7
name: Upload docs as artifact
with:
name: docs-html
path: './_build/html'
if-no-files-found: error
python-openapi-openapi-core-d6cdb4f/.github/workflows/contrib-tests.yml 0000664 0000000 0000000 00000004641 15163577675 0026574 0 ustar 00root root 0000000 0000000 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 }}
python-openapi-openapi-core-d6cdb4f/.github/workflows/python-publish.yml 0000664 0000000 0000000 00000001547 15163577675 0026763 0 ustar 00root root 0000000 0000000 # 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: CI / Publish
on:
workflow_dispatch:
release:
types:
- published
jobs:
publish_pypi:
name: "PyPI"
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v6
- name: Set up Python
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
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
python-openapi-openapi-core-d6cdb4f/.github/workflows/python-tests.yml 0000664 0000000 0000000 00000007333 15163577675 0026456 0 ustar 00root root 0000000 0000000 # 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
python-openapi-openapi-core-d6cdb4f/.gitignore 0000664 0000000 0000000 00000002373 15163577675 0021644 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
**/__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# asdf versions
.tool-versions
.default-python-packages
# mypy
.mypy_cache/
# Jetbrains project files
.idea/
/reports/ python-openapi-openapi-core-d6cdb4f/.pre-commit-config.yaml 0000664 0000000 0000000 00000001763 15163577675 0024137 0 ustar 00root root 0000000 0000000 ---
default_stages: [commit, push]
default_language_version:
# force all unspecified python hooks to run python3
python: python3
minimum_pre_commit_version: "1.20.0"
repos:
- repo: meta
hooks:
- id: check-hooks-apply
- repo: https://github.com/asottile/pyupgrade
rev: v2.38.4
hooks:
- id: pyupgrade
args: ["--py36-plus"]
- repo: local
hooks:
- id: flynt
name: Convert to f-strings with flynt
entry: flynt
language: python
additional_dependencies: ['flynt==0.64']
- id: black
name: black
entry: black
language: system
require_serial: true
types: [python]
- id: isort
name: isort
entry: isort
args: ['--filter-files']
language: system
require_serial: true
types: [python]
- id: pyflakes
name: pyflakes
entry: pyflakes
language: system
require_serial: true
types: [python]
python-openapi-openapi-core-d6cdb4f/.readthedocs.yaml 0000664 0000000 0000000 00000001106 15163577675 0023074 0 ustar 00root root 0000000 0000000 # Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
# 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-24.04
tools:
python: "3.12"
jobs:
post_system_dependencies:
- asdf plugin-add poetry
- asdf install poetry 2.2.1
- asdf global poetry 2.2.1
post_install:
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --no-interaction --with docs
python-openapi-openapi-core-d6cdb4f/CONTRIBUTING.rst 0000664 0000000 0000000 00000000205 15163577675 0022305 0 ustar 00root root 0000000 0000000 Please read the `Contributing `__ guidelines in the documentation site.
python-openapi-openapi-core-d6cdb4f/LICENSE 0000664 0000000 0000000 00000002735 15163577675 0020663 0 ustar 00root root 0000000 0000000 BSD 3-Clause License
Copyright (c) 2017, A
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
python-openapi-openapi-core-d6cdb4f/Makefile 0000664 0000000 0000000 00000001520 15163577675 0021305 0 ustar 00root root 0000000 0000000 .EXPORT_ALL_VARIABLES:
PROJECT_NAME=openapi-core
PACKAGE_NAME=$(subst -,_,${PROJECT_NAME})
VERSION=`git describe --abbrev=0`
PYTHONDONTWRITEBYTECODE=1
params:
@echo "Project name: ${PROJECT_NAME}"
@echo "Package name: ${PACKAGE_NAME}"
@echo "Version: ${VERSION}"
dist-build:
@poetry build
dist-cleanup:
@rm -rf build dist ${PACKAGE_NAME}.egg-info
dist-upload:
@poetry publish
test-python:
@pytest
test-cache-cleanup:
@rm -rf .pytest_cache
reports-cleanup:
@rm -rf reports
test-cleanup: test-cache-cleanup reports-cleanup
docs-html:
python -m mkdocs build --clean --site-dir docs_build --config-file mkdocs.yml
docs-cleanup:
@rm -rf docs_build
cleanup: dist-cleanup test-cleanup
bench-paths:
@PYTHONHASHSEED=0 python tests/benchmarks/bench_paths.py --paths 500 --templates-ratio 0.7 --lookups 2000 --output bench-paths.json python-openapi-openapi-core-d6cdb4f/README.md 0000664 0000000 0000000 00000013422 15163577675 0021130 0 ustar 00root root 0000000 0000000 # openapi-core
## 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.
python-openapi-openapi-core-d6cdb4f/SECURITY.md 0000664 0000000 0000000 00000001754 15163577675 0021447 0 ustar 00root root 0000000 0000000 # Security Policy
## Reporting a Vulnerability
If you believe you have found a security vulnerability in the repository, please report it to us as described below.
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them directly to the repository maintainer.
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
* This information will help us triage your report more quickly.
python-openapi-openapi-core-d6cdb4f/docs/ 0000775 0000000 0000000 00000000000 15163577675 0020577 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/docs/configuration.md 0000664 0000000 0000000 00000020045 15163577675 0023771 0 ustar 00root root 0000000 0000000 ---
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)
```
python-openapi-openapi-core-d6cdb4f/docs/contributing.md 0000664 0000000 0000000 00000005012 15163577675 0023626 0 ustar 00root root 0000000 0000000 ---
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`
python-openapi-openapi-core-d6cdb4f/docs/extensions.md 0000664 0000000 0000000 00000002731 15163577675 0023323 0 ustar 00root root 0000000 0000000 ---
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.
python-openapi-openapi-core-d6cdb4f/docs/index.md 0000664 0000000 0000000 00000005300 15163577675 0022226 0 ustar 00root root 0000000 0000000 ---
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.
python-openapi-openapi-core-d6cdb4f/docs/integrations/ 0000775 0000000 0000000 00000000000 15163577675 0023305 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/docs/integrations/aiohttp.md 0000664 0000000 0000000 00000002252 15163577675 0025300 0 ustar 00root root 0000000 0000000 # 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
```
python-openapi-openapi-core-d6cdb4f/docs/integrations/bottle.md 0000664 0000000 0000000 00000000166 15163577675 0025123 0 ustar 00root root 0000000 0000000 # Bottle
For more information, see the [bottle-openapi-3](https://github.com/cope-systems/bottle-openapi-3) project.
python-openapi-openapi-core-d6cdb4f/docs/integrations/django.md 0000664 0000000 0000000 00000007202 15163577675 0025072 0 ustar 00root root 0000000 0000000 # 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
```
python-openapi-openapi-core-d6cdb4f/docs/integrations/falcon.md 0000664 0000000 0000000 00000005675 15163577675 0025106 0 ustar 00root root 0000000 0000000 # 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)
```
python-openapi-openapi-core-d6cdb4f/docs/integrations/fastapi.md 0000664 0000000 0000000 00000003654 15163577675 0025266 0 ustar 00root root 0000000 0000000 # 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.
python-openapi-openapi-core-d6cdb4f/docs/integrations/flask.md 0000664 0000000 0000000 00000005365 15163577675 0024740 0 ustar 00root root 0000000 0000000 # 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.
python-openapi-openapi-core-d6cdb4f/docs/integrations/index.md 0000664 0000000 0000000 00000000302 15163577675 0024731 0 ustar 00root root 0000000 0000000 # 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.
python-openapi-openapi-core-d6cdb4f/docs/integrations/pyramid.md 0000664 0000000 0000000 00000000163 15163577675 0025274 0 ustar 00root root 0000000 0000000 # Pyramid
For more information, see the [pyramid_openapi3](https://github.com/niteoweb/pyramid_openapi3) project.
python-openapi-openapi-core-d6cdb4f/docs/integrations/requests.md 0000664 0000000 0000000 00000003002 15163577675 0025475 0 ustar 00root root 0000000 0000000 # 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)
```
python-openapi-openapi-core-d6cdb4f/docs/integrations/starlette.md 0000664 0000000 0000000 00000005070 15163577675 0025640 0 ustar 00root root 0000000 0000000 # 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
```
python-openapi-openapi-core-d6cdb4f/docs/integrations/tornado.md 0000664 0000000 0000000 00000000161 15163577675 0025273 0 ustar 00root root 0000000 0000000 # Tornado
For more information, see the [tornado-openapi3](https://github.com/correl/tornado-openapi3) project.
python-openapi-openapi-core-d6cdb4f/docs/integrations/werkzeug.md 0000664 0000000 0000000 00000002302 15163577675 0025467 0 ustar 00root root 0000000 0000000 # 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)
```
python-openapi-openapi-core-d6cdb4f/docs/reference/ 0000775 0000000 0000000 00000000000 15163577675 0022535 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/docs/reference/configurations.md 0000664 0000000 0000000 00000000052 15163577675 0026106 0 ustar 00root root 0000000 0000000 # `Config` class
::: openapi_core.Config
python-openapi-openapi-core-d6cdb4f/docs/reference/datatypes.md 0000664 0000000 0000000 00000000240 15163577675 0025051 0 ustar 00root root 0000000 0000000 # Datatypes
::: openapi_core.unmarshalling.request.datatypes.RequestUnmarshalResult
::: openapi_core.unmarshalling.response.datatypes.ResponseUnmarshalResult
python-openapi-openapi-core-d6cdb4f/docs/reference/index.md 0000664 0000000 0000000 00000000200 15163577675 0024156 0 ustar 00root root 0000000 0000000 # Reference
Documentation with information on functions, classes, methods, and all other parts of the OpenAPI-core public API.
python-openapi-openapi-core-d6cdb4f/docs/reference/openapi.md 0000664 0000000 0000000 00000000506 15163577675 0024513 0 ustar 00root root 0000000 0000000 # `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
python-openapi-openapi-core-d6cdb4f/docs/reference/protocols.md 0000664 0000000 0000000 00000000123 15163577675 0025077 0 ustar 00root root 0000000 0000000 # `Request`, `WebhookRequest` and `Response` protocols
::: openapi_core.protocols
python-openapi-openapi-core-d6cdb4f/docs/reference/types.md 0000664 0000000 0000000 00000000040 15163577675 0024215 0 ustar 00root root 0000000 0000000 # Types
::: openapi_core.types
python-openapi-openapi-core-d6cdb4f/docs/security.md 0000664 0000000 0000000 00000001431 15163577675 0022767 0 ustar 00root root 0000000 0000000 ---
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']
```
python-openapi-openapi-core-d6cdb4f/docs/unmarshalling.md 0000664 0000000 0000000 00000006111 15163577675 0023764 0 ustar 00root root 0000000 0000000 ---
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)).
python-openapi-openapi-core-d6cdb4f/docs/validation.md 0000664 0000000 0000000 00000007216 15163577675 0023261 0 ustar 00root root 0000000 0000000 ---
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))
```
python-openapi-openapi-core-d6cdb4f/mkdocs.yml 0000664 0000000 0000000 00000005441 15163577675 0021656 0 ustar 00root root 0000000 0000000 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
python-openapi-openapi-core-d6cdb4f/openapi_core/ 0000775 0000000 0000000 00000000000 15163577675 0022312 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/__init__.py 0000664 0000000 0000000 00000011342 15163577675 0024424 0 ustar 00root root 0000000 0000000 """OpenAPI core module"""
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
from openapi_core.shortcuts import unmarshal_response
from openapi_core.shortcuts import unmarshal_webhook_request
from openapi_core.shortcuts import unmarshal_webhook_response
from openapi_core.shortcuts import validate_apicall_request
from openapi_core.shortcuts import validate_apicall_response
from openapi_core.shortcuts import validate_request
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.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.23.1"
__url__ = "https://github.com/python-openapi/openapi-core"
__license__ = "BSD 3-Clause License"
__all__ = [
"OpenAPI",
"Config",
"unmarshal_request",
"unmarshal_response",
"unmarshal_apicall_request",
"unmarshal_webhook_request",
"unmarshal_apicall_response",
"unmarshal_webhook_response",
"validate_apicall_request",
"validate_webhook_request",
"validate_apicall_response",
"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",
"V3WebhookResponseUnmarshaller",
"V30RequestValidator",
"V30ResponseValidator",
"V31RequestValidator",
"V31ResponseValidator",
"V31WebhookRequestValidator",
"V31WebhookResponseValidator",
"V32RequestValidator",
"V32ResponseValidator",
"V32WebhookRequestValidator",
"V32WebhookResponseValidator",
"V3RequestValidator",
"V3ResponseValidator",
"V3WebhookRequestValidator",
"V3WebhookResponseValidator",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/app.py 0000664 0000000 0000000 00000105553 15163577675 0023455 0 ustar 00root root 0000000 0000000 """OpenAPI core app module"""
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
from jsonschema.validators import _UNSET
from jsonschema_path import SchemaPath
from jsonschema_path.handlers.protocols import SupportsRead
from jsonschema_path.typing import Schema
from openapi_spec_validator import validate
from openapi_spec_validator.validation.exceptions import ValidatorDetectError
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
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.protocols import WebhookRequest
from openapi_core.types import AnyRequest
from openapi_core.unmarshalling.request import (
UNMARSHALLERS as REQUEST_UNMARSHALLERS,
)
from openapi_core.unmarshalling.request import (
WEBHOOK_UNMARSHALLERS as WEBHOOK_REQUEST_UNMARSHALLERS,
)
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.request.protocols import RequestUnmarshaller
from openapi_core.unmarshalling.request.protocols import (
WebhookRequestUnmarshaller,
)
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
from openapi_core.unmarshalling.request.types import (
WebhookRequestUnmarshallerType,
)
from openapi_core.unmarshalling.response import (
UNMARSHALLERS as RESPONSE_UNMARSHALLERS,
)
from openapi_core.unmarshalling.response import (
WEBHOOK_UNMARSHALLERS as WEBHOOK_RESPONSE_UNMARSHALLERS,
)
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.response.protocols import ResponseUnmarshaller
from openapi_core.unmarshalling.response.protocols import (
WebhookResponseUnmarshaller,
)
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType
from openapi_core.unmarshalling.response.types import (
WebhookResponseUnmarshallerType,
)
from openapi_core.validation.request import VALIDATORS as REQUEST_VALIDATORS
from openapi_core.validation.request import (
WEBHOOK_VALIDATORS as WEBHOOK_REQUEST_VALIDATORS,
)
from openapi_core.validation.request.protocols import RequestValidator
from openapi_core.validation.request.protocols import WebhookRequestValidator
from openapi_core.validation.request.types import RequestValidatorType
from openapi_core.validation.request.types import WebhookRequestValidatorType
from openapi_core.validation.response import VALIDATORS as RESPONSE_VALIDATORS
from openapi_core.validation.response import (
WEBHOOK_VALIDATORS as WEBHOOK_RESPONSE_VALIDATORS,
)
from openapi_core.validation.response.protocols import ResponseValidator
from openapi_core.validation.response.protocols import WebhookResponseValidator
from openapi_core.validation.response.types import ResponseValidatorType
from openapi_core.validation.response.types import WebhookResponseValidatorType
class OpenAPI:
"""`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: 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")
self.spec = spec
self.config = config or Config()
self.check_spec()
@classmethod
def from_dict(
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":
"""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: 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: 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: 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":
"""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.read_value())
# backward compatibility
except OpenAPIVersionNotFound:
raise SpecError("Spec schema version not detected")
def check_spec(self) -> None:
if self.config.spec_validator_cls is None:
return
cls = None
if self.config.spec_validator_cls is not _UNSET:
cls = self.config.spec_validator_cls
try:
validate(
self.spec.read_value(),
base_uri=self.config.spec_base_uri or self.spec.base_uri,
cls=cls,
)
except ValidatorDetectError:
raise SpecError("spec not detected")
@property
def version(self) -> SpecVersion:
return self._get_version()
@cached_property
def request_validator_cls(self) -> Optional[RequestValidatorType]:
if not isinstance(self.config.request_validator_cls, Unset):
return self.config.request_validator_cls
return REQUEST_VALIDATORS.get(self.version)
@cached_property
def response_validator_cls(self) -> Optional[ResponseValidatorType]:
if not isinstance(self.config.response_validator_cls, Unset):
return self.config.response_validator_cls
return RESPONSE_VALIDATORS.get(self.version)
@cached_property
def webhook_request_validator_cls(
self,
) -> Optional[WebhookRequestValidatorType]:
if not isinstance(self.config.webhook_request_validator_cls, Unset):
return self.config.webhook_request_validator_cls
return WEBHOOK_REQUEST_VALIDATORS.get(self.version)
@cached_property
def webhook_response_validator_cls(
self,
) -> Optional[WebhookResponseValidatorType]:
if not isinstance(self.config.webhook_response_validator_cls, Unset):
return self.config.webhook_response_validator_cls
return WEBHOOK_RESPONSE_VALIDATORS.get(self.version)
@cached_property
def request_unmarshaller_cls(self) -> Optional[RequestUnmarshallerType]:
if not isinstance(self.config.request_unmarshaller_cls, Unset):
return self.config.request_unmarshaller_cls
return REQUEST_UNMARSHALLERS.get(self.version)
@cached_property
def response_unmarshaller_cls(self) -> Optional[ResponseUnmarshallerType]:
if not isinstance(self.config.response_unmarshaller_cls, Unset):
return self.config.response_unmarshaller_cls
return RESPONSE_UNMARSHALLERS.get(self.version)
@cached_property
def webhook_request_unmarshaller_cls(
self,
) -> Optional[WebhookRequestUnmarshallerType]:
if not isinstance(self.config.webhook_request_unmarshaller_cls, Unset):
return self.config.webhook_request_unmarshaller_cls
return WEBHOOK_REQUEST_UNMARSHALLERS.get(self.version)
@cached_property
def webhook_response_unmarshaller_cls(
self,
) -> Optional[WebhookResponseUnmarshallerType]:
if not isinstance(
self.config.webhook_response_unmarshaller_cls, Unset
):
return self.config.webhook_response_unmarshaller_cls
return WEBHOOK_RESPONSE_UNMARSHALLERS.get(self.version)
@cached_property
def request_validator(self) -> RequestValidator:
if self.request_validator_cls is None:
raise SpecError("Validator class not found")
return self.request_validator_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.path_finder_cls,
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,
)
@cached_property
def response_validator(self) -> ResponseValidator:
if self.response_validator_cls is None:
raise SpecError("Validator class not found")
return self.response_validator_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.path_finder_cls,
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
def webhook_request_validator(self) -> WebhookRequestValidator:
if self.webhook_request_validator_cls is None:
raise SpecError("Validator class not found")
return self.webhook_request_validator_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.webhook_path_finder_cls,
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,
)
@cached_property
def webhook_response_validator(self) -> WebhookResponseValidator:
if self.webhook_response_validator_cls is None:
raise SpecError("Validator class not found")
return self.webhook_response_validator_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.webhook_path_finder_cls,
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
def request_unmarshaller(self) -> RequestUnmarshaller:
if self.request_unmarshaller_cls is None:
raise SpecError("Unmarshaller class not found")
return self.request_unmarshaller_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.path_finder_cls,
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,
)
@cached_property
def response_unmarshaller(self) -> ResponseUnmarshaller:
if self.response_unmarshaller_cls is None:
raise SpecError("Unmarshaller class not found")
return self.response_unmarshaller_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.path_finder_cls,
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,
)
@cached_property
def webhook_request_unmarshaller(self) -> WebhookRequestUnmarshaller:
if self.webhook_request_unmarshaller_cls is None:
raise SpecError("Unmarshaller class not found")
return self.webhook_request_unmarshaller_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.webhook_path_finder_cls,
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,
)
@cached_property
def webhook_response_unmarshaller(self) -> WebhookResponseUnmarshaller:
if self.webhook_response_unmarshaller_cls is None:
raise SpecError("Unmarshaller class not found")
return self.webhook_response_unmarshaller_cls(
self.spec,
base_url=self.config.server_base_url,
style_deserializers_factory=self.config.style_deserializers_factory,
media_type_deserializers_factory=self.config.media_type_deserializers_factory,
schema_casters_factory=self.config.schema_casters_factory,
schema_validators_factory=self.config.schema_validators_factory,
path_finder_cls=self.config.webhook_path_finder_cls,
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: 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: 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 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: 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")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
self.response_validator.validate(request, response)
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: 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")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
self.webhook_response_validator.validate(request, response)
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: 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: 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: 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")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
return self.response_unmarshaller.unmarshal(request, response)
def unmarshal_webhook_request(
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: 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")
if not isinstance(response, Response):
raise TypeError("'response' argument is not type of Response")
return self.webhook_response_unmarshaller.unmarshal(request, response)
python-openapi-openapi-core-d6cdb4f/openapi_core/casting/ 0000775 0000000 0000000 00000000000 15163577675 0023742 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/casting/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0026041 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/casting/schemas/ 0000775 0000000 0000000 00000000000 15163577675 0025365 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/casting/schemas/__init__.py 0000664 0000000 0000000 00000004222 15163577675 0027476 0 ustar 00root root 0000000 0000000 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
from openapi_core.casting.schemas.casters import NumberCaster
from openapi_core.casting.schemas.casters import ObjectCaster
from openapi_core.casting.schemas.casters import PrimitiveCaster
from openapi_core.casting.schemas.casters import TypesCaster
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.validation.schemas import (
oas30_read_schema_validators_factory,
)
from openapi_core.validation.schemas import (
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(
[
("object", ObjectCaster),
("array", ArrayCaster),
("boolean", BooleanCaster),
("integer", IntegerCaster),
("number", NumberCaster),
("string", PrimitiveCaster),
]
)
oas31_casters_dict = oas30_casters_dict.copy()
oas31_casters_dict.update(
{
"null": PrimitiveCaster,
}
)
oas30_types_caster = TypesCaster(
oas30_casters_dict,
AnyCaster,
)
oas31_types_caster = TypesCaster(
oas31_casters_dict,
AnyCaster,
multi=PrimitiveCaster,
)
oas32_types_caster = oas31_types_caster
oas30_write_schema_casters_factory = SchemaCastersFactory(
oas30_write_schema_validators_factory,
oas30_types_caster,
)
oas30_read_schema_casters_factory = SchemaCastersFactory(
oas30_read_schema_validators_factory,
oas30_types_caster,
)
oas31_schema_casters_factory = SchemaCastersFactory(
oas31_schema_validators_factory,
oas31_types_caster,
)
oas32_schema_casters_factory = SchemaCastersFactory(
oas32_schema_validators_factory,
oas32_types_caster,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/casting/schemas/casters.py 0000664 0000000 0000000 00000020453 15163577675 0027407 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Generic
from typing import Iterable
from typing import Mapping
from typing import Optional
from typing import Type
from typing import TypeVar
from typing import Union
from jsonschema_path import SchemaPath
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
class PrimitiveCaster:
def __init__(
self,
schema: SchemaPath,
schema_validator: SchemaValidator,
schema_caster: "SchemaCaster",
):
self.schema = schema
self.schema_validator = schema_validator
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
PrimitiveType = TypeVar("PrimitiveType")
class PrimitiveTypeCaster(Generic[PrimitiveType], PrimitiveCaster):
primitive_type: Type[PrimitiveType] = NotImplemented
def cast(self, value: Union[str, bytes]) -> PrimitiveType:
return self.primitive_type(value) # type: ignore [call-arg]
class IntegerCaster(PrimitiveTypeCaster[int]):
primitive_type = int
class NumberCaster(PrimitiveTypeCaster[float]):
primitive_type = float
class BooleanCaster(PrimitiveTypeCaster[bool]):
primitive_type = bool
def validate(self, value: Any) -> None:
super().validate(value)
if isinstance(value, bool):
return
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
def items_caster(self) -> "SchemaCaster":
# sometimes we don't have any schema i.e. free-form objects
items_schema = self.schema.get("items", SchemaPath.from_dict({}))
return self.schema_caster.evolve(items_schema)
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 ValueError("not an array format")
def cast(self, value: list[Any]) -> list[Any]:
return list(map(self.items_caster.cast, value))
class ObjectCaster(PrimitiveCaster):
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":
cls = self.__class__
return cls(
schema,
self.schema_validator.evolve(schema),
self.schema_caster.evolve(schema),
)
def _cast_proparties(
self, value: dict[str, Any], schema_only: bool = False
) -> dict[str, Any]:
if not isinstance(value, dict):
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:
all_of_properties = self.evolve(all_of_schema)._cast_proparties(
value, schema_only=True
)
value.update(all_of_properties)
for prop_name, prop_schema in get_properties(self.schema).items():
try:
prop_value = value[prop_name]
except KeyError:
continue
value[prop_name] = self.schema_caster.evolve(prop_schema).cast(
prop_value
)
if schema_only:
return value
additional_properties = self.schema.get("additionalProperties", True)
if additional_properties is not False:
# free-form object
if additional_properties is True:
additional_prop_schema = SchemaPath.from_dict(
{"nullable": True}
)
# defined schema
else:
additional_prop_schema = self.schema / "additionalProperties"
additional_prop_caster = self.schema_caster.evolve(
additional_prop_schema
)
for prop_name, prop_value in value.items():
if prop_name in value:
continue
value[prop_name] = additional_prop_caster.cast(prop_value)
return value
class TypesCaster:
casters: Mapping[str, Type[PrimitiveCaster]] = {}
multi: Optional[Type[PrimitiveCaster]] = None
def __init__(
self,
casters: Mapping[str, Type[PrimitiveCaster]],
default: Type[PrimitiveCaster],
multi: Optional[Type[PrimitiveCaster]] = None,
):
self.casters = casters
self.default = default
self.multi = multi
def get_caster(
self,
schema_type: Optional[Union[Iterable[str], str]],
) -> Type["PrimitiveCaster"]:
if schema_type is None:
return self.default
if isinstance(schema_type, Iterable) and not isinstance(
schema_type, str
):
if self.multi is None:
raise TypeError("caster does not accept multiple types")
return self.multi
return self.casters[schema_type]
class SchemaCaster:
def __init__(
self,
schema: SchemaPath,
schema_validator: SchemaValidator,
types_caster: TypesCaster,
):
self.schema = schema
self.schema_validator = schema_validator
self.types_caster = types_caster
def cast(self, value: Any) -> Any:
# skip casting for nullable in OpenAPI 3.0
if value is None and (self.schema / "nullable").read_bool(
default=False
):
return value
schema_type = (self.schema / "type").read_str(None)
type_caster = self.get_type_caster(schema_type)
if value is None:
return value
try:
return type_caster(value)
except (ValueError, TypeError) as exc:
raise CastError(value, schema_type) from exc
def get_type_caster(
self,
schema_type: Optional[Union[Iterable[str], str]],
) -> PrimitiveCaster:
caster_cls = self.types_caster.get_caster(schema_type)
return caster_cls(
self.schema,
self.schema_validator,
self,
)
def evolve(self, schema: SchemaPath) -> "SchemaCaster":
cls = self.__class__
return cls(
schema,
self.schema_validator.evolve(schema),
self.types_caster,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/casting/schemas/exceptions.py 0000664 0000000 0000000 00000000540 15163577675 0030117 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Any
from openapi_core.deserializing.exceptions import DeserializeError
@dataclass
class CastError(DeserializeError):
"""Schema cast operation error"""
value: Any
type: str | None
def __str__(self) -> str:
return f"Failed to cast value to {self.type} type: {self.value}"
python-openapi-openapi-core-d6cdb4f/openapi_core/casting/schemas/factories.py 0000664 0000000 0000000 00000002211 15163577675 0027712 0 ustar 00root root 0000000 0000000 from typing import Optional
from jsonschema_path import SchemaPath
from openapi_core.casting.schemas.casters import SchemaCaster
from openapi_core.casting.schemas.casters import TypesCaster
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
class SchemaCastersFactory:
def __init__(
self,
schema_validators_factory: SchemaValidatorsFactory,
types_caster: TypesCaster,
):
self.schema_validators_factory = schema_validators_factory
self.types_caster = types_caster
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,
)
return SchemaCaster(schema, schema_validator, self.types_caster)
python-openapi-openapi-core-d6cdb4f/openapi_core/configurations.py 0000664 0000000 0000000 00000005552 15163577675 0025725 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Union
from jsonschema._utils import Unset
from jsonschema.validators import _UNSET
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.unmarshalling.configurations import UnmarshallerConfig
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
from openapi_core.unmarshalling.request.types import (
WebhookRequestUnmarshallerType,
)
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType
from openapi_core.unmarshalling.response.types import (
WebhookResponseUnmarshallerType,
)
from openapi_core.validation.request.types import RequestValidatorType
from openapi_core.validation.request.types import WebhookRequestValidatorType
from openapi_core.validation.response.types import ResponseValidatorType
from openapi_core.validation.response.types import WebhookResponseValidatorType
@dataclass
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: 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
spec_base_uri: str = ""
request_validator_cls: Union[RequestValidatorType, Unset] = _UNSET
response_validator_cls: Union[ResponseValidatorType, Unset] = _UNSET
webhook_request_validator_cls: Union[
WebhookRequestValidatorType, Unset
] = _UNSET
webhook_response_validator_cls: Union[
WebhookResponseValidatorType, Unset
] = _UNSET
request_unmarshaller_cls: Union[RequestUnmarshallerType, Unset] = _UNSET
response_unmarshaller_cls: Union[ResponseUnmarshallerType, Unset] = _UNSET
webhook_request_unmarshaller_cls: Union[
WebhookRequestUnmarshallerType, Unset
] = _UNSET
webhook_response_unmarshaller_cls: Union[
WebhookResponseUnmarshallerType, Unset
] = _UNSET
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/ 0000775 0000000 0000000 00000000000 15163577675 0023752 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0026051 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/aiohttp/ 0000775 0000000 0000000 00000000000 15163577675 0025422 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/aiohttp/__init__.py 0000664 0000000 0000000 00000000350 15163577675 0027531 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.aiohttp.requests import AIOHTTPOpenAPIWebRequest
from openapi_core.contrib.aiohttp.responses import AIOHTTPOpenAPIWebResponse
__all__ = [
"AIOHTTPOpenAPIWebRequest",
"AIOHTTPOpenAPIWebResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/aiohttp/requests.py 0000664 0000000 0000000 00000002250 15163577675 0027646 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib aiohttp requests module"""
from aiohttp import web
from openapi_core.datatypes import RequestParameters
class Empty: ...
_empty = Empty()
class AIOHTTPOpenAPIWebRequest:
__slots__ = ("request", "parameters", "_get_body", "_body")
def __init__(self, request: web.Request, *, body: bytes | None):
if not isinstance(request, web.Request):
raise TypeError(
f"'request' argument is not type of {web.Request.__qualname__!r}"
)
self.request = request
self.parameters = RequestParameters(
query=self.request.query,
header=self.request.headers,
cookie=self.request.cookies,
)
self._body = body
@property
def host_url(self) -> str:
return f"{self.request.url.scheme}://{self.request.url.host}"
@property
def path(self) -> str:
return self.request.url.path
@property
def method(self) -> str:
return self.request.method.lower()
@property
def body(self) -> bytes | None:
return self._body
@property
def content_type(self) -> str:
return self.request.content_type
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/aiohttp/responses.py 0000664 0000000 0000000 00000001713 15163577675 0030017 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib aiohttp responses module"""
import multidict
from aiohttp import web
class AIOHTTPOpenAPIWebResponse:
def __init__(self, response: web.Response):
if not isinstance(response, web.Response):
raise TypeError(
f"'response' argument is not type of {web.Response.__qualname__!r}"
)
self.response = response
@property
def data(self) -> bytes:
if self.response.body is None:
return b""
if isinstance(self.response.body, bytes):
return self.response.body
assert isinstance(self.response.body, str)
return self.response.body.encode("utf-8")
@property
def status_code(self) -> int:
return self.response.status
@property
def content_type(self) -> str:
return self.response.content_type or ""
@property
def headers(self) -> multidict.CIMultiDict[str]:
return self.response.headers
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/ 0000775 0000000 0000000 00000000000 15163577675 0025214 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/__init__.py 0000664 0000000 0000000 00000000400 15163577675 0027317 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib django module"""
from openapi_core.contrib.django.requests import DjangoOpenAPIRequest
from openapi_core.contrib.django.responses import DjangoOpenAPIResponse
__all__ = [
"DjangoOpenAPIRequest",
"DjangoOpenAPIResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/decorators.py 0000664 0000000 0000000 00000007116 15163577675 0027740 0 ustar 00root root 0000000 0000000 """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,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/handlers.py 0000664 0000000 0000000 00000004201 15163577675 0027363 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib django handlers module"""
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import Type
from django.http import JsonResponse
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
class DjangoOpenAPIErrorsHandler:
OPENAPI_ERROR_STATUS: Dict[Type[BaseException], int] = {
ServerNotFound: 400,
SecurityNotFound: 403,
OperationNotFound: 405,
PathNotFound: 404,
MediaTypeNotFound: 415,
}
def __call__(
self,
errors: Iterable[Exception],
) -> JsonResponse:
data_errors = [self.format_openapi_error(err) for err in errors]
data = {
"errors": data_errors,
}
data_error_max = max(data_errors, key=self.get_error_status)
return JsonResponse(data, status=data_error_max["status"])
@classmethod
def format_openapi_error(cls, error: BaseException) -> Dict[str, Any]:
if error.__cause__ is not None:
error = error.__cause__
return {
"title": str(error),
"status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
"type": str(type(error)),
}
@classmethod
def get_error_status(cls, error: Dict[str, Any]) -> str:
return str(error["status"])
class DjangoOpenAPIValidRequestHandler:
def __init__(self, req: HttpRequest, view: Callable[[Any], HttpResponse]):
self.req = req
self.view = view
def __call__(
self, request_unmarshal_result: RequestUnmarshalResult
) -> HttpResponse:
self.req.openapi = request_unmarshal_result
return self.view(self.req)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/integrations.py 0000664 0000000 0000000 00000002407 15163577675 0030277 0 ustar 00root root 0000000 0000000 from django.http.request import HttpRequest
from django.http.response import HttpResponse
from openapi_core.contrib.django.requests import DjangoOpenAPIRequest
from openapi_core.contrib.django.responses import DjangoOpenAPIResponse
from openapi_core.unmarshalling.processors import UnmarshallingProcessor
from openapi_core.unmarshalling.typing import ErrorsHandlerCallable
class DjangoIntegration(UnmarshallingProcessor[HttpRequest, HttpResponse]):
request_cls = DjangoOpenAPIRequest
response_cls = DjangoOpenAPIResponse
def get_openapi_request(
self, request: HttpRequest
) -> DjangoOpenAPIRequest:
return self.request_cls(request)
def get_openapi_response(
self, response: HttpResponse
) -> DjangoOpenAPIResponse:
assert self.response_cls is not None
return self.response_cls(response)
def should_validate_response(self) -> bool:
return self.response_cls is not None
def handle_response(
self,
request: HttpRequest,
response: HttpResponse,
errors_handler: ErrorsHandlerCallable[HttpResponse],
) -> HttpResponse:
if not self.should_validate_response():
return response
return super().handle_response(request, response, errors_handler)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/middlewares.py 0000664 0000000 0000000 00000002543 15163577675 0030072 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib django middlewares module"""
from typing import Callable
from django.conf import settings
from django.http.request import HttpRequest
from django.http.response import HttpResponse
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):
valid_request_handler_cls = DjangoOpenAPIValidRequestHandler
errors_handler = DjangoOpenAPIErrorsHandler()
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
if hasattr(settings, "OPENAPI_RESPONSE_CLS"):
self.response_cls = settings.OPENAPI_RESPONSE_CLS
openapi = get_default_openapi_instance()
super().__init__(openapi)
def __call__(self, request: HttpRequest) -> HttpResponse:
valid_request_handler = self.valid_request_handler_cls(
request, self.get_response
)
response = self.handle_request(
request, valid_request_handler, self.errors_handler
)
return self.handle_response(request, response, self.errors_handler)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/providers.py 0000664 0000000 0000000 00000001720 15163577675 0027603 0 ustar 00root root 0000000 0000000 """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."
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/requests.py 0000664 0000000 0000000 00000005564 15163577675 0027453 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib django requests module"""
import re
from typing import Optional
from django.http.request import HttpRequest
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.datatypes import RequestParameters
# https://docs.djangoproject.com/en/stable/topics/http/urls/
#
# Currently unsupported are :
# - nested arguments, e.g.: ^comments/(?:page-(?P\d+)/)?$
# - unnamed regex groups, e.g.: ^articles/([0-9]{4})/$
# - multiple named parameters between a single pair of slashes
# e.g.: -/edit/
#
# The regex matches everything, except a "/" until "<". Then only the name
# is exported, after which it matches ">" and everything until a "/".
# A check is made to ensure that "/" is not in an excluded character set such
# as may be found with Django REST Framwork's default value pattern, "[^/.]+".
PATH_PARAMETER_PATTERN = (
r"(?:[^/]*?)<(?:(?:.*?:))*?(\w+)>(?:(?:[^/]*?\[\^[^/]*/)?[^/]*)"
)
class DjangoOpenAPIRequest:
path_regex = re.compile(PATH_PARAMETER_PATTERN)
def __init__(self, request: HttpRequest):
if not isinstance(request, HttpRequest):
raise TypeError(f"'request' argument is not type of {HttpRequest}")
self.request = request
path = (
self.request.resolver_match
and self.request.resolver_match.kwargs
or {}
)
self.parameters = RequestParameters(
path=path,
query=ImmutableMultiDict(self.request.GET),
header=Headers(self.request.headers.items()),
cookie=ImmutableMultiDict(dict(self.request.COOKIES)),
)
@property
def host_url(self) -> str:
assert isinstance(self.request._current_scheme_host, str)
return self.request._current_scheme_host
@property
def path(self) -> str:
assert isinstance(self.request.path, str)
return self.request.path
@property
def path_pattern(self) -> Optional[str]:
if self.request.resolver_match is None:
return None
route = self.path_regex.sub(r"{\1}", self.request.resolver_match.route)
# Delete start and end marker to allow concatenation.
if route[:1] == "^":
route = route[1:]
if route[-1:] == "$":
route = route[:-1]
return "/" + route
@property
def method(self) -> str:
if self.request.method is None:
return ""
assert isinstance(self.request.method, str)
return self.request.method.lower()
@property
def body(self) -> bytes:
assert isinstance(self.request.body, bytes)
return self.request.body
@property
def content_type(self) -> str:
content_type = self.request.META.get("CONTENT_TYPE", "")
assert isinstance(content_type, str)
return content_type
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/django/responses.py 0000664 0000000 0000000 00000002600 15163577675 0027605 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib django responses module"""
from itertools import tee
from django.http.response import HttpResponse
from django.http.response import StreamingHttpResponse
from werkzeug.datastructures import Headers
class DjangoOpenAPIResponse:
def __init__(self, response: HttpResponse):
if not isinstance(response, (HttpResponse, StreamingHttpResponse)):
raise TypeError(
f"'response' argument is not type of {HttpResponse} or {StreamingHttpResponse}"
)
self.response = response
@property
def data(self) -> bytes:
if isinstance(self.response, StreamingHttpResponse):
resp_iter1, resp_iter2 = tee(self.response._iterator)
self.response.streaming_content = resp_iter1
content = b"".join(map(self.response.make_bytes, resp_iter2))
return content
assert isinstance(self.response.content, bytes)
return self.response.content
@property
def status_code(self) -> int:
assert isinstance(self.response.status_code, int)
return self.response.status_code
@property
def headers(self) -> Headers:
return Headers(self.response.headers.items())
@property
def content_type(self) -> str:
content_type = self.response.get("Content-Type", "")
assert isinstance(content_type, str)
return content_type
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/ 0000775 0000000 0000000 00000000000 15163577675 0025214 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/__init__.py 0000664 0000000 0000000 00000001047 15163577675 0027327 0 ustar 00root root 0000000 0000000 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",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/handlers.py 0000664 0000000 0000000 00000004672 15163577675 0027377 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib falcon handlers module"""
from json import dumps
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Type
from falcon import status_codes
from falcon.constants import MEDIA_JSON
from falcon.request import Request
from falcon.response import Response
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
class FalconOpenAPIErrorsHandler:
OPENAPI_ERROR_STATUS: Dict[Type[BaseException], int] = {
ServerNotFound: 400,
SecurityNotFound: 403,
OperationNotFound: 405,
PathNotFound: 404,
MediaTypeNotFound: 415,
}
def __init__(self, req: Request, resp: Response):
self.req = req
self.resp = resp
def __call__(self, errors: Iterable[Exception]) -> Response:
data_errors = [self.format_openapi_error(err) for err in errors]
data = {
"errors": data_errors,
}
data_str = dumps(data)
data_error_max = max(data_errors, key=self.get_error_status)
self.resp.content_type = MEDIA_JSON
self.resp.status = getattr(
status_codes,
f"HTTP_{data_error_max['status']}",
status_codes.HTTP_400,
)
self.resp.text = data_str
self.resp.complete = True
return self.resp
@classmethod
def format_openapi_error(cls, error: BaseException) -> Dict[str, Any]:
if error.__cause__ is not None:
error = error.__cause__
return {
"title": str(error),
"status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
"type": str(type(error)),
}
@classmethod
def get_error_status(cls, error: Dict[str, Any]) -> int:
return int(error["status"])
class FalconOpenAPIValidRequestHandler:
def __init__(self, req: Request, resp: Response):
self.req = req
self.resp = resp
def __call__(
self, request_unmarshal_result: RequestUnmarshalResult
) -> Response:
self.req.context.openapi = request_unmarshal_result
return self.resp
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/integrations.py 0000664 0000000 0000000 00000005663 15163577675 0030306 0 ustar 00root root 0000000 0000000 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
class FalconIntegration(UnmarshallingProcessor[Request, Response]):
request_cls = FalconOpenAPIRequest
response_cls = FalconOpenAPIResponse
def get_openapi_request(self, request: Request) -> FalconOpenAPIRequest:
return self.request_cls(request)
def get_openapi_response(
self, response: Response
) -> FalconOpenAPIResponse:
assert self.response_cls is not None
return self.response_cls(response)
def should_validate_response(self) -> bool:
return self.response_cls is not None
def handle_response(
self,
request: Request,
response: Response,
errors_handler: ErrorsHandlerCallable[Response],
) -> 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)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/middlewares.py 0000664 0000000 0000000 00000023635 15163577675 0030077 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib falcon middlewares module"""
from typing import Any
from typing import Optional
from typing import Type
from typing import cast
from falcon.request import Request
from falcon.response import Response
from jsonschema_path import SchemaPath
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.
"""
valid_request_handler_cls = FalconOpenAPIValidRequestHandler
errors_handler_cls: Type[FalconOpenAPIErrorsHandler] = (
FalconOpenAPIErrorsHandler
)
def __init__(
self,
openapi: OpenAPI,
request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler_cls: Type[
FalconOpenAPIErrorsHandler
] = FalconOpenAPIErrorsHandler,
**kwargs: Any,
):
super().__init__(openapi)
self.request_cls = request_cls or self.request_cls
self.response_cls = response_cls or self.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[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler_cls: Type[
FalconOpenAPIErrorsHandler
] = FalconOpenAPIErrorsHandler,
**kwargs: Any,
) -> "FalconWSGIOpenAPIMiddleware":
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,
)
def process_request(self, req: Request, resp: Response) -> None:
valid_handler = self.valid_request_handler_cls(req, resp)
errors_handler = self.errors_handler_cls(req, resp)
self.handle_request(req, valid_handler, errors_handler)
def process_response(
self, req: Request, resp: Response, resource: Any, req_succeeded: bool
) -> 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,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/requests.py 0000664 0000000 0000000 00000006500 15163577675 0027442 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib falcon responses module"""
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__(
self,
request: Request,
default_when_empty: Optional[Dict[Any, Any]] = None,
):
if not isinstance(request, Request):
raise TypeError(f"'request' argument is not type of {Request}")
self.request = request
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(
query=ImmutableMultiDict(unpack_params(self.request.params)),
header=Headers(self.request.headers),
cookie=self.request.cookies,
)
@property
def host_url(self) -> str:
assert isinstance(self.request.prefix, str)
return self.request.prefix
@property
def path(self) -> str:
assert isinstance(self.request.path, str)
return self.request.path
@property
def method(self) -> str:
assert isinstance(self.request.method, str)
return self.request.method.lower()
@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.
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,
)
self._body = serialize_body(self.request, media, self.content_type)
return cast(Optional[bytes], self._body)
@property
def content_type(self) -> str:
if self.request.content_type:
assert isinstance(self.request.content_type, str)
return self.request.content_type
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
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/responses.py 0000664 0000000 0000000 00000010157 15163577675 0027613 0 ustar 00root root 0000000 0000000 """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
class FalconOpenAPIResponse:
def __init__(self, response: Response):
if not isinstance(response, Response):
raise TypeError(f"'response' argument is not type of {Response}")
self.response = response
@property
def data(self) -> bytes:
if self.response.text is None:
if self.response.stream is None:
return b""
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 self.response.status_code
@property
def content_type(self) -> str:
content_type = ""
if self.response.content_type:
content_type = self.response.content_type
else:
content_type = self.response.options.default_media_type
return content_type
@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
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/util.py 0000664 0000000 0000000 00000002021 15163577675 0026536 0 ustar 00root root 0000000 0000000 import warnings
from typing import Any
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: Mapping[str, Any],
) -> Generator[Tuple[str, Any], None, None]:
for k, v in params.items():
if isinstance(v, list):
for v2 in v:
yield (k, v2)
else:
yield (k, v)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/falcon/views.py 0000664 0000000 0000000 00000000000 15163577675 0026711 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/fastapi/ 0000775 0000000 0000000 00000000000 15163577675 0025401 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/fastapi/__init__.py 0000664 0000000 0000000 00000000512 15163577675 0027510 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware
from openapi_core.contrib.fastapi.requests import FastAPIOpenAPIRequest
from openapi_core.contrib.fastapi.responses import FastAPIOpenAPIResponse
__all__ = [
"FastAPIOpenAPIMiddleware",
"FastAPIOpenAPIRequest",
"FastAPIOpenAPIResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/fastapi/middlewares.py 0000664 0000000 0000000 00000000237 15163577675 0030255 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.starlette.middlewares import (
StarletteOpenAPIMiddleware as FastAPIOpenAPIMiddleware,
)
__all__ = ["FastAPIOpenAPIMiddleware"]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/fastapi/requests.py 0000664 0000000 0000000 00000000355 15163577675 0027631 0 ustar 00root root 0000000 0000000 from fastapi import Request
from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest
class FastAPIOpenAPIRequest(StarletteOpenAPIRequest):
def __init__(self, request: Request):
super().__init__(request)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/fastapi/responses.py 0000664 0000000 0000000 00000000473 15163577675 0030000 0 ustar 00root root 0000000 0000000 from typing import Optional
from fastapi import Response
from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse
class FastAPIOpenAPIResponse(StarletteOpenAPIResponse):
def __init__(self, response: Response, data: Optional[bytes] = None):
super().__init__(response, data=data)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/ 0000775 0000000 0000000 00000000000 15163577675 0025052 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/__init__.py 0000664 0000000 0000000 00000000475 15163577675 0027171 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse
__all__ = [
"FlaskOpenAPIViewDecorator",
"FlaskOpenAPIRequest",
"FlaskOpenAPIResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/decorators.py 0000664 0000000 0000000 00000005622 15163577675 0027576 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib flask decorators module"""
from functools import wraps
from typing import Any
from typing import Callable
from typing import Type
from flask.globals import request
from flask.wrappers import Request
from flask.wrappers import Response
from jsonschema_path import SchemaPath
from openapi_core import OpenAPI
from openapi_core.contrib.flask.handlers import FlaskOpenAPIErrorsHandler
from openapi_core.contrib.flask.handlers import FlaskOpenAPIValidRequestHandler
from openapi_core.contrib.flask.integrations import FlaskIntegration
from openapi_core.contrib.flask.providers import FlaskRequestProvider
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse
class FlaskOpenAPIViewDecorator(FlaskIntegration):
valid_request_handler_cls = FlaskOpenAPIValidRequestHandler
errors_handler_cls: Type[FlaskOpenAPIErrorsHandler] = (
FlaskOpenAPIErrorsHandler
)
def __init__(
self,
openapi: OpenAPI,
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_cls: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
errors_handler_cls: Type[
FlaskOpenAPIErrorsHandler
] = FlaskOpenAPIErrorsHandler,
):
super().__init__(openapi)
self.request_cls = request_cls
self.response_cls = response_cls
self.request_provider = request_provider
self.errors_handler_cls = errors_handler_cls
def __call__(self, view: Callable[..., Any]) -> Callable[..., Any]:
@wraps(view)
def decorated(*args: Any, **kwargs: Any) -> Response:
request = self.get_request()
valid_request_handler = self.valid_request_handler_cls(
request, view, *args, **kwargs
)
errors_handler = self.errors_handler_cls()
response = self.handle_request(
request, valid_request_handler, errors_handler
)
return self.handle_response(request, response, errors_handler)
return decorated
def get_request(self) -> Request:
return request
@classmethod
def from_spec(
cls,
spec: SchemaPath,
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_cls: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
errors_handler_cls: Type[
FlaskOpenAPIErrorsHandler
] = FlaskOpenAPIErrorsHandler,
) -> "FlaskOpenAPIViewDecorator":
openapi = OpenAPI(spec)
return cls(
openapi,
request_cls=request_cls,
response_cls=response_cls,
request_provider=request_provider,
errors_handler_cls=errors_handler_cls,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/handlers.py 0000664 0000000 0000000 00000004723 15163577675 0027232 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib flask handlers module"""
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import Type
from flask.globals import current_app
from flask.helpers import make_response
from flask.json import dumps
from flask.wrappers import Request
from flask.wrappers import Response
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
class FlaskOpenAPIErrorsHandler:
OPENAPI_ERROR_STATUS: Dict[Type[BaseException], int] = {
ServerNotFound: 400,
SecurityNotFound: 403,
OperationNotFound: 405,
PathNotFound: 404,
MediaTypeNotFound: 415,
}
def __call__(self, errors: Iterable[Exception]) -> Response:
data_errors = [self.format_openapi_error(err) for err in errors]
data = {
"errors": data_errors,
}
data_error_max = max(data_errors, key=self.get_error_status)
status = data_error_max["status"]
return current_app.response_class(
dumps(data), status=status, mimetype="application/json"
)
@classmethod
def format_openapi_error(cls, error: BaseException) -> Dict[str, Any]:
if error.__cause__ is not None:
error = error.__cause__
return {
"title": str(error),
"status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
"class": str(type(error)),
}
@classmethod
def get_error_status(cls, error: Dict[str, Any]) -> int:
return int(error["status"])
class FlaskOpenAPIValidRequestHandler:
def __init__(
self,
req: Request,
view: Callable[[Any], Response],
*view_args: Any,
**view_kwargs: Any,
):
self.req = req
self.view = view
self.view_args = view_args
self.view_kwargs = view_kwargs
def __call__(
self, request_unmarshal_result: RequestUnmarshalResult
) -> Response:
self.req.openapi = request_unmarshal_result # type: ignore
rv = self.view(*self.view_args, **self.view_kwargs)
return make_response(rv)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/integrations.py 0000664 0000000 0000000 00000002257 15163577675 0030140 0 ustar 00root root 0000000 0000000 from flask.wrappers import Request
from flask.wrappers import Response
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse
from openapi_core.unmarshalling.processors import UnmarshallingProcessor
from openapi_core.unmarshalling.typing import ErrorsHandlerCallable
class FlaskIntegration(UnmarshallingProcessor[Request, Response]):
request_cls = FlaskOpenAPIRequest
response_cls = FlaskOpenAPIResponse
def get_openapi_request(self, request: Request) -> FlaskOpenAPIRequest:
return self.request_cls(request)
def get_openapi_response(self, response: Response) -> FlaskOpenAPIResponse:
assert self.response_cls is not None
return self.response_cls(response)
def should_validate_response(self) -> bool:
return self.response_cls is not None
def handle_response(
self,
request: Request,
response: Response,
errors_handler: ErrorsHandlerCallable[Response],
) -> Response:
if not self.should_validate_response():
return response
return super().handle_response(request, response, errors_handler)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/providers.py 0000664 0000000 0000000 00000000423 15163577675 0027440 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib flask providers module"""
from typing import Any
from flask.globals import request
from flask.wrappers import Request
class FlaskRequestProvider:
@classmethod
def provide(self, *args: Any, **kwargs: Any) -> Request:
return request
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/requests.py 0000664 0000000 0000000 00000002030 15163577675 0027272 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib flask requests module"""
from flask.wrappers import Request
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest
from openapi_core.datatypes import RequestParameters
class FlaskOpenAPIRequest(WerkzeugOpenAPIRequest):
def __init__(self, request: Request):
if not isinstance(request, Request):
raise TypeError(f"'request' argument is not type of {Request}")
self.request: Request = request
self.parameters = RequestParameters(
path=self.request.view_args or {},
query=ImmutableMultiDict(self.request.args),
header=Headers(self.request.headers),
cookie=self.request.cookies,
)
@property
def path_pattern(self) -> str:
if self.request.url_rule is None:
return self.path
path = self.get_path(self.request.url_rule.rule)
return self.path_regex.sub(r"{\1}", path)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/responses.py 0000664 0000000 0000000 00000000221 15163577675 0027440 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.werkzeug.responses import (
WerkzeugOpenAPIResponse as FlaskOpenAPIResponse,
)
__all__ = ["FlaskOpenAPIResponse"]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/flask/views.py 0000664 0000000 0000000 00000001516 15163577675 0026564 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib flask views module"""
from typing import Any
from flask.views import MethodView
from openapi_core import OpenAPI
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core.contrib.flask.handlers import FlaskOpenAPIErrorsHandler
class FlaskOpenAPIView(MethodView):
"""Brings OpenAPI specification validation and unmarshalling for views."""
openapi_errors_handler = FlaskOpenAPIErrorsHandler
def __init__(self, openapi: OpenAPI):
super().__init__()
self.decorator = FlaskOpenAPIViewDecorator(
openapi,
errors_handler_cls=self.openapi_errors_handler,
)
def dispatch_request(self, *args: Any, **kwargs: Any) -> Any:
response = self.decorator(super().dispatch_request)(*args, **kwargs)
return response
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/requests/ 0000775 0000000 0000000 00000000000 15163577675 0025625 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/requests/__init__.py 0000664 0000000 0000000 00000000541 15163577675 0027736 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.requests.requests import RequestsOpenAPIRequest
from openapi_core.contrib.requests.requests import (
RequestsOpenAPIWebhookRequest,
)
from openapi_core.contrib.requests.responses import RequestsOpenAPIResponse
__all__ = [
"RequestsOpenAPIRequest",
"RequestsOpenAPIResponse",
"RequestsOpenAPIWebhookRequest",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/requests/protocols.py 0000664 0000000 0000000 00000000311 15163577675 0030216 0 ustar 00root root 0000000 0000000 from typing import Protocol
from typing import runtime_checkable
from requests.cookies import RequestsCookieJar
@runtime_checkable
class SupportsCookieJar(Protocol):
_cookies: RequestsCookieJar
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/requests/requests.py 0000664 0000000 0000000 00000006213 15163577675 0030054 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib requests requests module"""
from typing import Optional
from typing import Union
from urllib.parse import parse_qs
from urllib.parse import urlparse
from requests import PreparedRequest
from requests import Request
from requests.cookies import RequestsCookieJar
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.contrib.requests.protocols import SupportsCookieJar
from openapi_core.datatypes import RequestParameters
class RequestsOpenAPIRequest:
"""
Converts a requests request to an OpenAPI request
Internally converts to a `PreparedRequest` first to parse the exact
payload being sent
"""
def __init__(self, request: Union[Request, PreparedRequest]):
if not isinstance(request, (Request, PreparedRequest)):
raise TypeError(
"'request' argument is not type of "
f"{Request} or {PreparedRequest}"
)
if isinstance(request, Request):
request = request.prepare()
self.request = request
if request.url is None:
raise RuntimeError("Request URL is missing")
self._url_parsed = urlparse(request.url, allow_fragments=False)
cookie = {}
if isinstance(self.request, SupportsCookieJar) and isinstance(
self.request._cookies, RequestsCookieJar
):
# cookies are stored in a cookiejar object
cookie = self.request._cookies.get_dict()
self.parameters = RequestParameters(
query=ImmutableMultiDict(parse_qs(self._url_parsed.query)),
header=Headers(dict(self.request.headers)),
cookie=ImmutableMultiDict(cookie),
)
@property
def host_url(self) -> str:
return f"{self._url_parsed.scheme}://{self._url_parsed.netloc}"
@property
def path(self) -> str:
assert isinstance(self._url_parsed.path, str)
return self._url_parsed.path
@property
def method(self) -> str:
method = self.request.method
return method and method.lower() or ""
@property
def body(self) -> Optional[bytes]:
if self.request.body is None:
return None
if isinstance(self.request.body, bytes):
return self.request.body
assert isinstance(self.request.body, str)
# TODO: figure out if request._body_position is relevant
return self.request.body.encode("utf-8")
@property
def content_type(self) -> str:
# Order matters because all python requests issued from a session
# include Accept */* which does not necessarily match the content type
return str(
self.request.headers.get("Content-Type")
or self.request.headers.get("Accept")
)
class RequestsOpenAPIWebhookRequest(RequestsOpenAPIRequest):
"""
Converts a requests request to an OpenAPI Webhook request
Internally converts to a `PreparedRequest` first to parse the exact
payload being sent
"""
def __init__(self, request: Union[Request, PreparedRequest], name: str):
super().__init__(request)
self.name = name
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/requests/responses.py 0000664 0000000 0000000 00000001454 15163577675 0030224 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib requests responses module"""
from requests import Response
from werkzeug.datastructures import Headers
class RequestsOpenAPIResponse:
def __init__(self, response: Response):
if not isinstance(response, Response):
raise TypeError(f"'response' argument is not type of {Response}")
self.response = response
@property
def data(self) -> bytes:
assert isinstance(self.response.content, bytes)
return self.response.content
@property
def status_code(self) -> int:
return int(self.response.status_code)
@property
def content_type(self) -> str:
return str(self.response.headers.get("Content-Type", ""))
@property
def headers(self) -> Headers:
return Headers(dict(self.response.headers))
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/ 0000775 0000000 0000000 00000000000 15163577675 0025761 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/__init__.py 0000664 0000000 0000000 00000000350 15163577675 0030070 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest
from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse
__all__ = [
"StarletteOpenAPIRequest",
"StarletteOpenAPIResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/handlers.py 0000664 0000000 0000000 00000004336 15163577675 0030141 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib starlette handlers module"""
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Type
from starlette.middleware.base import RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.responses import Response
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
class StarletteOpenAPIErrorsHandler:
OPENAPI_ERROR_STATUS: Dict[Type[BaseException], int] = {
ServerNotFound: 400,
SecurityNotFound: 403,
OperationNotFound: 405,
PathNotFound: 404,
MediaTypeNotFound: 415,
}
def __call__(
self,
errors: Iterable[Exception],
) -> JSONResponse:
data_errors = [self.format_openapi_error(err) for err in errors]
data = {
"errors": data_errors,
}
data_error_max = max(data_errors, key=self.get_error_status)
return JSONResponse(data, status_code=data_error_max["status"])
@classmethod
def format_openapi_error(cls, error: BaseException) -> Dict[str, Any]:
if error.__cause__ is not None:
error = error.__cause__
return {
"title": str(error),
"status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
"type": str(type(error)),
}
@classmethod
def get_error_status(cls, error: Dict[str, Any]) -> str:
return str(error["status"])
class StarletteOpenAPIValidRequestHandler:
def __init__(self, request: Request, call_next: RequestResponseEndpoint):
self.request = request
self.call_next = call_next
async def __call__(
self, request_unmarshal_result: RequestUnmarshalResult
) -> Response:
self.request.scope["openapi"] = request_unmarshal_result
return await self.call_next(self.request)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/integrations.py 0000664 0000000 0000000 00000003562 15163577675 0031047 0 ustar 00root root 0000000 0000000 from aioitertools.itertools import tee as atee
from starlette.requests import Request
from starlette.responses import Response
from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest
from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse
from openapi_core.unmarshalling.processors import AsyncUnmarshallingProcessor
from openapi_core.unmarshalling.typing import ErrorsHandlerCallable
class StarletteIntegration(AsyncUnmarshallingProcessor[Request, Response]):
request_cls = StarletteOpenAPIRequest
response_cls = StarletteOpenAPIResponse
async def get_openapi_request(
self, request: Request
) -> StarletteOpenAPIRequest:
body = await request.body()
return self.request_cls(request, body)
async def get_openapi_response(
self, response: Response
) -> StarletteOpenAPIResponse:
assert self.response_cls is not None
data = None
if hasattr(response, "body_iterator"):
body_iter1, body_iter2 = atee(response.body_iterator)
response.body_iterator = body_iter2
data = b"".join(
[
(
chunk.encode(response.charset)
if not isinstance(chunk, bytes)
else chunk
)
async for chunk in body_iter1
]
)
return self.response_cls(response, data=data)
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)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/middlewares.py 0000664 0000000 0000000 00000003457 15163577675 0030644 0 ustar 00root root 0000000 0000000 """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
from starlette.responses import Response
from starlette.types import ASGIApp
from openapi_core import OpenAPI
from openapi_core.contrib.starlette.handlers import (
StarletteOpenAPIErrorsHandler,
)
from openapi_core.contrib.starlette.handlers import (
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,
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(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
valid_request_handler = self.valid_request_handler_cls(
request, call_next
)
response = await self.handle_request(
request, valid_request_handler, self.errors_handler
)
return await self.handle_response(
request, response, self.errors_handler
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/requests.py 0000664 0000000 0000000 00000002253 15163577675 0030210 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib starlette requests module"""
from typing import Optional
from starlette.requests import Request
from openapi_core.datatypes import RequestParameters
class StarletteOpenAPIRequest:
def __init__(self, request: Request, body: Optional[bytes] = None):
if not isinstance(request, Request):
raise TypeError(f"'request' argument is not type of {Request}")
self.request = request
self.parameters = RequestParameters(
query=self.request.query_params,
header=self.request.headers,
cookie=self.request.cookies,
)
self._body = body
@property
def host_url(self) -> str:
return self.request.base_url._url
@property
def path(self) -> str:
return self.request.url.path
@property
def method(self) -> str:
return self.request.method.lower()
@property
def body(self) -> Optional[bytes]:
return self._body
@property
def content_type(self) -> str:
# default value according to RFC 2616
return (
self.request.headers.get("Content-Type")
or "application/octet-stream"
)
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/starlette/responses.py 0000664 0000000 0000000 00000002406 15163577675 0030356 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib starlette responses module"""
from typing import Optional
from starlette.datastructures import Headers
from starlette.responses import Response
from starlette.responses import StreamingResponse
class StarletteOpenAPIResponse:
def __init__(self, response: Response, data: Optional[bytes] = None):
if not isinstance(response, Response):
raise TypeError(f"'response' argument is not type of {Response}")
self.response = response
if data is None and isinstance(response, StreamingResponse):
raise RuntimeError(
f"'data' argument is required for {StreamingResponse}"
)
self._data = data
@property
def data(self) -> bytes:
if self._data is not None:
return self._data
if isinstance(self.response.body, bytes):
return self.response.body
assert isinstance(self.response.body, str)
return self.response.body.encode("utf-8")
@property
def status_code(self) -> int:
return self.response.status_code
@property
def content_type(self) -> str:
return self.response.headers.get("Content-Type") or ""
@property
def headers(self) -> Headers:
return self.response.headers
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/werkzeug/ 0000775 0000000 0000000 00000000000 15163577675 0025615 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/werkzeug/__init__.py 0000664 0000000 0000000 00000000342 15163577675 0027725 0 ustar 00root root 0000000 0000000 from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest
from openapi_core.contrib.werkzeug.responses import WerkzeugOpenAPIResponse
__all__ = [
"WerkzeugOpenAPIRequest",
"WerkzeugOpenAPIResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/werkzeug/requests.py 0000664 0000000 0000000 00000002773 15163577675 0030053 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib werkzeug requests module"""
import re
from typing import Optional
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.wrappers import Request
from openapi_core.datatypes import RequestParameters
# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
PATH_PARAMETER_PATTERN = r"<(?:(?:string|int|float|path|uuid):)?(\w+)>"
class WerkzeugOpenAPIRequest:
path_regex = re.compile(PATH_PARAMETER_PATTERN)
def __init__(self, request: Request):
if not isinstance(request, Request):
raise TypeError(f"'request' argument is not type of {Request}")
self.request = request
self.parameters = RequestParameters(
query=ImmutableMultiDict(self.request.args),
header=Headers(self.request.headers),
cookie=self.request.cookies,
)
@property
def host_url(self) -> str:
return self.request.host_url
@property
def path(self) -> str:
return self.get_path(self.request.path)
@property
def method(self) -> str:
return self.request.method.lower()
@property
def body(self) -> Optional[bytes]:
return self.request.get_data(as_text=False)
@property
def content_type(self) -> str:
# default value according to RFC 2616
return self.request.content_type or "application/octet-stream"
def get_path(self, path: str) -> str:
return "".join([self.request.root_path, path])
python-openapi-openapi-core-d6cdb4f/openapi_core/contrib/werkzeug/responses.py 0000664 0000000 0000000 00000001720 15163577675 0030210 0 ustar 00root root 0000000 0000000 """OpenAPI core contrib werkzeug responses module"""
from itertools import tee
from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response
class WerkzeugOpenAPIResponse:
def __init__(self, response: Response):
if not isinstance(response, Response):
raise TypeError(f"'response' argument is not type of {Response}")
self.response = response
@property
def data(self) -> bytes:
if not self.response.is_sequence:
resp_iter1, resp_iter2 = tee(self.response.iter_encoded())
self.response.response = resp_iter1
return b"".join(resp_iter2)
return self.response.get_data(as_text=False)
@property
def status_code(self) -> int:
return self.response._status_code
@property
def content_type(self) -> str:
return str(self.response.mimetype)
@property
def headers(self) -> Headers:
return Headers(self.response.headers)
python-openapi-openapi-core-d6cdb4f/openapi_core/datatypes.py 0000664 0000000 0000000 00000002634 15163577675 0024667 0 ustar 00root root 0000000 0000000 """OpenAPI core validation request datatypes module"""
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:
"""OpenAPI request parameters dataclass.
Attributes:
query
Query string parameters as MultiDict. Must support getlist method.
header
Request headers as Headers.
cookie
Request cookies as MultiDict.
path
Path parameters as dict. Gets resolved against spec if empty.
"""
query: Mapping[str, Any] = field(default_factory=ImmutableMultiDict)
header: HeadersType = field(default_factory=Headers)
cookie: Mapping[str, Any] = field(default_factory=ImmutableMultiDict)
path: Mapping[str, Any] = field(default_factory=dict)
def __getitem__(self, location: str) -> Any:
return getattr(self, location)
@dataclass
class Parameters:
query: Mapping[str, Any] = field(default_factory=dict)
header: Mapping[str, Any] = field(default_factory=dict)
cookie: Mapping[str, Any] = field(default_factory=dict)
path: Mapping[str, Any] = field(default_factory=dict)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/ 0000775 0000000 0000000 00000000000 15163577675 0025143 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0027242 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/exceptions.py 0000664 0000000 0000000 00000000177 15163577675 0027703 0 ustar 00root root 0000000 0000000 from openapi_core.exceptions import OpenAPIError
class DeserializeError(OpenAPIError):
"""Deserialize operation error"""
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/ 0000775 0000000 0000000 00000000000 15163577675 0027446 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/__init__.py 0000664 0000000 0000000 00000002333 15163577675 0031560 0 ustar 00root root 0000000 0000000 from collections import defaultdict
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.media_types.util import binary_loads
from openapi_core.deserializing.media_types.util import data_form_loads
from openapi_core.deserializing.media_types.util import json_loads
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
__all__ = ["media_type_deserializers", "MediaTypeDeserializersFactory"]
media_type_deserializers: MediaTypeDeserializersDict = defaultdict(
lambda: binary_loads,
**{
"text/html": plain_loads,
"text/plain": plain_loads,
"application/octet-stream": binary_loads,
"application/json": json_loads,
"application/vnd.api+json": json_loads,
"application/xml": xml_loads,
"application/xhtml+xml": xml_loads,
"application/x-www-form-urlencoded": urlencoded_form_loads,
"multipart/form-data": data_form_loads,
}
)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/datatypes.py 0000664 0000000 0000000 00000000267 15163577675 0032023 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Callable
from typing import Dict
DeserializerCallable = Callable[[bytes], Any]
MediaTypeDeserializersDict = Dict[str, DeserializerCallable]
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/deserializers.py 0000664 0000000 0000000 00000021623 15163577675 0032671 0 ustar 00root root 0000000 0000000 from typing import TYPE_CHECKING
from typing import Any
from typing import Mapping
from typing import Optional
from xml.etree.ElementTree import ParseError
from jsonschema_path import SchemaPath
from openapi_core.deserializing.media_types.datatypes import (
DeserializerCallable,
)
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.exceptions import (
MediaTypeDeserializeError,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.schema.encodings import get_content_type
from openapi_core.schema.parameters import get_style_and_explode
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:
def __init__(
self,
media_type_deserializers: Optional[MediaTypeDeserializersDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
):
if media_type_deserializers is None:
media_type_deserializers = {}
self.media_type_deserializers = media_type_deserializers
if extra_media_type_deserializers is None:
extra_media_type_deserializers = {}
self.extra_media_type_deserializers = extra_media_type_deserializers
def deserialize(
self, mimetype: str, value: bytes, **parameters: str
) -> Any:
deserializer_callable = self.get_deserializer_callable(mimetype)
try:
return deserializer_callable(value, **parameters)
except (ParseError, ValueError, TypeError, AttributeError):
raise MediaTypeDeserializeError(mimetype, value)
def get_deserializer_callable(
self,
mimetype: str,
) -> DeserializerCallable:
if mimetype in self.extra_media_type_deserializers:
return self.extra_media_type_deserializers[mimetype]
return self.media_type_deserializers[mimetype]
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
def deserialize(self, value: bytes) -> Any:
deserialized = self.media_types_deserializer.deserialize(
self.mimetype, value, **self.parameters
)
if (
self.mimetype != "application/x-www-form-urlencoded"
and not self.mimetype.startswith("multipart")
):
return deserialized
# decode multipart request bodies if schema provided
if self.schema is not None:
return self.decode(deserialized)
return deserialized
def evolve(
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 or self.mimetype,
schema=schema,
schema_validator=schema_validator,
schema_caster=schema_caster,
)
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: 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(
prop_name, prop_schema, location
)
except KeyError:
if "default" not in prop_schema:
continue
properties[prop_name] = (prop_schema / "default").read_value()
if schema_only:
return properties
return properties
def decode_property(
self,
prop_name: str,
prop_schema: SchemaPath,
location: Mapping[str, Any],
) -> Any:
if self.encoding is None or prop_name not in self.encoding:
if self.mimetype == "application/x-www-form-urlencoded":
# default serialization strategy for complex objects
# in the application/x-www-form-urlencoded
return self.decode_property_style(
prop_name,
prop_schema,
location,
SchemaPath.from_dict({"style": "form"}),
)
return self.decode_property_content_type(
prop_name, prop_schema, location
)
prep_encoding = self.encoding / prop_name
if (
"style" not in prep_encoding
and "explode" not in prep_encoding
and "allowReserved" not in prep_encoding
):
return self.decode_property_content_type(
prop_name, prop_schema, location, prep_encoding
)
return self.decode_property_style(
prop_name, prop_schema, location, prep_encoding
)
def decode_property_style(
self,
prop_name: str,
prop_schema: SchemaPath,
location: Mapping[str, Any],
prep_encoding: SchemaPath,
) -> Any:
prop_style, prop_explode = get_style_and_explode(
prep_encoding, default_location="query"
)
prop_deserializer = self.style_deserializers_factory.create(
self.spec, prop_schema, prop_style, prop_explode, name=prop_name
)
return prop_deserializer.deserialize(location)
def decode_property_content_type(
self,
prop_name: str,
prop_schema: SchemaPath,
location: Mapping[str, Any],
prop_encoding: Optional[SchemaPath] = None,
) -> Any:
prop_content_type = get_content_type(prop_schema, prop_encoding)
prop_deserializer = self.evolve(
prop_schema,
mimetype=prop_content_type,
)
prop_schema_type = (prop_schema / "type").read_str("")
if (
self.mimetype.startswith("multipart")
and prop_schema_type == "array"
):
if isinstance(location, SuportsGetAll):
value = location.getall(prop_name)
return list(map(prop_deserializer.deserialize, value))
if isinstance(location, SuportsGetList):
value = location.getlist(prop_name)
return list(map(prop_deserializer.deserialize, value))
return prop_deserializer.deserialize(location[prop_name])
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/exceptions.py 0000664 0000000 0000000 00000000702 15163577675 0032200 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from openapi_core.deserializing.exceptions import DeserializeError
@dataclass
class MediaTypeDeserializeError(DeserializeError):
"""Media type deserialize operation error"""
mimetype: str
value: bytes
def __str__(self) -> str:
return (
"Failed to deserialize value with {mimetype} mimetype: {value}"
).format(value=self.value.decode("utf-8"), mimetype=self.mimetype)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/factories.py 0000664 0000000 0000000 00000007331 15163577675 0032003 0 ustar 00root root 0000000 0000000 from typing import Mapping
from typing import Optional
from jsonschema_path import SchemaPath
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.deserializers import (
MediaTypeDeserializer,
)
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:
def __init__(
self,
style_deserializers_factory: StyleDeserializersFactory,
media_type_deserializers: Optional[MediaTypeDeserializersDict] = None,
):
self.style_deserializers_factory = style_deserializers_factory
if media_type_deserializers is None:
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[
MediaTypeDeserializersDict
] = None,
) -> MediaTypeDeserializer:
if parameters is None:
parameters = {}
if extra_media_type_deserializers is None:
extra_media_type_deserializers = {}
media_types_deserializer = MediaTypesDeserializer(
self.media_type_deserializers,
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,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/media_types/util.py 0000664 0000000 0000000 00000004412 15163577675 0030776 0 ustar 00root root 0000000 0000000 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
from werkzeug.datastructures import ImmutableMultiDict
def binary_loads(value: bytes, **parameters: str) -> bytes:
return value
def plain_loads(value: bytes, **parameters: str) -> str:
charset = "utf-8"
if "charset" in parameters:
charset = parameters["charset"]
if isinstance(value, bytes):
try:
return value.decode(charset)
# fallback safe decode
except UnicodeDecodeError:
return value.decode("ASCII", errors="surrogateescape")
return value
def json_loads(value: bytes, **parameters: str) -> Any:
return loads(value)
def xml_loads(value: bytes, **parameters: str) -> Element:
charset = "utf-8"
if "charset" in parameters:
charset = parameters["charset"]
return fromstring(value.decode(charset))
def urlencoded_form_loads(
value: bytes, **parameters: str
) -> Mapping[str, Any]:
# only UTF-8 is conforming
return ImmutableMultiDict(
parse_qsl(value.decode("utf-8"), keep_blank_values=True)
)
def data_form_loads(value: bytes, **parameters: str) -> Mapping[str, Any]:
charset = "ASCII"
if "charset" in parameters:
charset = parameters["charset"]
decoded = value.decode(charset, errors="surrogateescape")
boundary = ""
if "boundary" in parameters:
boundary = parameters["boundary"]
parser = Parser()
mimetype = "multipart/form-data"
header = f'Content-Type: {mimetype}; boundary="{boundary}"'
text = "\n\n".join([header, decoded])
parts = parser.parsestr(text, headersonly=False)
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
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/ 0000775 0000000 0000000 00000000000 15163577675 0026466 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/__init__.py 0000664 0000000 0000000 00000001727 15163577675 0030606 0 ustar 00root root 0000000 0000000 from openapi_core.deserializing.styles.datatypes import StyleDeserializersDict
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.deserializing.styles.util import deep_object_loads
from openapi_core.deserializing.styles.util import form_loads
from openapi_core.deserializing.styles.util import label_loads
from openapi_core.deserializing.styles.util import matrix_loads
from openapi_core.deserializing.styles.util import pipe_delimited_loads
from openapi_core.deserializing.styles.util import simple_loads
from openapi_core.deserializing.styles.util import space_delimited_loads
__all__ = ["style_deserializers", "StyleDeserializersFactory"]
style_deserializers: StyleDeserializersDict = {
"matrix": matrix_loads,
"label": label_loads,
"form": form_loads,
"simple": simple_loads,
"spaceDelimited": space_delimited_loads,
"pipeDelimited": pipe_delimited_loads,
"deepObject": deep_object_loads,
}
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/casters.py 0000664 0000000 0000000 00000003177 15163577675 0030514 0 ustar 00root root 0000000 0000000 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)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/datatypes.py 0000664 0000000 0000000 00000000352 15163577675 0031036 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Callable
from typing import Dict
from typing import Mapping
DeserializerCallable = Callable[[bool, str, str, Mapping[str, Any]], Any]
StyleDeserializersDict = Dict[str, DeserializerCallable]
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/deserializers.py 0000664 0000000 0000000 00000002726 15163577675 0031714 0 ustar 00root root 0000000 0000000 import warnings
from typing import Any
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
class StyleDeserializer:
def __init__(
self,
style: str,
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:
if self.deserializer_callable is None:
warnings.warn(f"Unsupported {self.style} style")
return location[self.name]
try:
value = self.deserializer_callable(
self.explode, self.name, self.schema_type, location
)
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
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/exceptions.py 0000664 0000000 0000000 00000001613 15163577675 0031222 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from openapi_core.deserializing.exceptions import DeserializeError
@dataclass
class BaseStyleDeserializeError(DeserializeError):
"""Base style deserialize operation error"""
location: str
@dataclass
class ParameterDeserializeError(BaseStyleDeserializeError):
"""Parameter deserialize operation error"""
style: str
value: str
def __str__(self) -> str:
return (
"Failed to deserialize value of "
f"{self.location} parameter with style {self.style}: {self.value}"
)
@dataclass(init=False)
class EmptyQueryParameterValue(BaseStyleDeserializeError):
name: str
def __init__(self, name: str):
super().__init__(location="query")
self.name = name
def __str__(self) -> str:
return (
f"Value of {self.name} {self.location} parameter cannot be empty"
)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/factories.py 0000664 0000000 0000000 00000002222 15163577675 0031015 0 ustar 00root root 0000000 0000000 from typing import Optional
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
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,
name: str,
) -> StyleDeserializer:
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, caster, deserialize_callable
)
python-openapi-openapi-core-d6cdb4f/openapi_core/deserializing/styles/util.py 0000664 0000000 0000000 00000013336 15163577675 0030023 0 ustar 00root root 0000000 0000000 import re
from functools import partial
from typing import Any
from typing import List
from typing import Mapping
from openapi_core.schema.protocols import SuportsGetAll
from openapi_core.schema.protocols import SuportsGetList
def split(value: str, separator: str = ",", step: int = 1) -> List[str]:
parts = value.split(separator)
if step == 1:
return parts
result = []
for i in range(len(parts)):
if i % step == 0:
if i + 1 < len(parts):
result.append(parts[i] + separator + parts[i + 1])
return result
def delimited_loads(
explode: bool,
name: str,
schema_type: str,
location: Mapping[str, Any],
delimiter: str,
) -> Any:
value = location[name]
explode_type = (explode, schema_type)
if explode_type == (False, "array"):
return split(value, separator=delimiter)
if explode_type == (False, "object"):
return dict(
map(
partial(split, separator=delimiter),
split(value, separator=delimiter, step=2),
)
)
raise ValueError("not available")
def matrix_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
if explode == False:
m = re.match(rf"^;{name}=(.*)$", location[f";{name}"])
if m is None:
raise KeyError(name)
value = m.group(1)
# ;color=blue,black,brown
if schema_type == "array":
return split(value)
# ;color=R,100,G,200,B,150
if schema_type == "object":
return dict(map(split, split(value, step=2)))
# .;color=blue
return value
else:
# ;color=blue;color=black;color=brown
if schema_type == "array":
return re.findall(rf";{name}=([^;]*)", location[f";{name}*"])
# ;R=100;G=200;B=150
if schema_type == "object":
value = location[f";{name}*"]
return dict(
map(
partial(split, separator="="),
split(value[1:], separator=";"),
)
)
# ;color=blue
m = re.match(rf"^;{name}=(.*)$", location[f";{name}*"])
if m is None:
raise KeyError(name)
value = m.group(1)
return value
def label_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
if explode == False:
value = location[f".{name}"]
# .blue,black,brown
if schema_type == "array":
return split(value[1:])
# .R,100,G,200,B,150
if schema_type == "object":
return dict(map(split, split(value[1:], separator=",", step=2)))
# .blue
return value[1:]
else:
value = location[f".{name}*"]
# .blue.black.brown
if schema_type == "array":
return split(value[1:], separator=".")
# .R=100.G=200.B=150
if schema_type == "object":
return dict(
map(
partial(split, separator="="),
split(value[1:], separator="."),
)
)
# .blue
return value[1:]
def form_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
explode_type = (explode, schema_type)
# color=blue,black,brown
if explode_type == (False, "array"):
return split(location[name], separator=",")
# color=blue&color=black&color=brown
elif explode_type == (True, "array"):
if name not in location:
raise KeyError(name)
if isinstance(location, SuportsGetAll):
return location.getall(name)
if isinstance(location, SuportsGetList):
return location.getlist(name)
return location[name]
value = location[name]
# color=R,100,G,200,B,150
if explode_type == (False, "object"):
return dict(map(split, split(value, separator=",", step=2)))
# R=100&G=200&B=150
elif explode_type == (True, "object"):
return dict(
map(partial(split, separator="="), split(value, separator="&"))
)
# color=blue
return value
def simple_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
value = location[name]
# blue,black,brown
if schema_type == "array":
return split(value, separator=",")
explode_type = (explode, schema_type)
# R,100,G,200,B,150
if explode_type == (False, "object"):
return dict(map(split, split(value, separator=",", step=2)))
# R=100,G=200,B=150
elif explode_type == (True, "object"):
return dict(
map(partial(split, separator="="), split(value, separator=","))
)
# blue
return value
def space_delimited_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
return delimited_loads(
explode, name, schema_type, location, delimiter="%20"
)
def pipe_delimited_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
return delimited_loads(explode, name, schema_type, location, delimiter="|")
def deep_object_loads(
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
) -> Any:
explode_type = (explode, schema_type)
if explode_type != (True, "object"):
raise ValueError("not available")
keys_str = " ".join(location.keys())
if not re.search(rf"{name}\[\w+\]", keys_str):
raise KeyError(name)
values = {}
for key, value in location.items():
# Split the key from the brackets.
key_split = re.split(pattern=r"\[|\]", string=key)
if key_split[0] == name:
values[key_split[1]] = value
return values
python-openapi-openapi-core-d6cdb4f/openapi_core/exceptions.py 0000664 0000000 0000000 00000000171 15163577675 0025044 0 ustar 00root root 0000000 0000000 """OpenAPI core exceptions module"""
class OpenAPIError(Exception):
pass
class SpecError(OpenAPIError):
pass
python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/ 0000775 0000000 0000000 00000000000 15163577675 0024511 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/__init__.py 0000664 0000000 0000000 00000000041 15163577675 0026615 0 ustar 00root root 0000000 0000000 """OpenAPI extensions package"""
python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/models/ 0000775 0000000 0000000 00000000000 15163577675 0025774 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/models/__init__.py 0000664 0000000 0000000 00000000050 15163577675 0030100 0 ustar 00root root 0000000 0000000 """OpenAPI X-Model extension package"""
python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/models/factories.py 0000664 0000000 0000000 00000002210 15163577675 0030320 0 ustar 00root root 0000000 0000000 """OpenAPI X-Model extension factories module"""
from dataclasses import make_dataclass
from pydoc import locate
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Type
from jsonschema_path import SchemaPath
from openapi_core.extensions.models.types import Field
class DictFactory:
base_class = dict
def create(
self, schema: SchemaPath, fields: Iterable[Field]
) -> Type[Dict[Any, Any]]:
return self.base_class
class ModelFactory(DictFactory):
def create(
self,
schema: SchemaPath,
fields: Iterable[Field],
) -> Type[Any]:
name = (schema / "x-model").read_str(None)
if name is None:
return super().create(schema, fields)
return make_dataclass(name, fields, frozen=True)
class ModelPathFactory(ModelFactory):
def create(
self,
schema: SchemaPath,
fields: Iterable[Field],
) -> Any:
model_class_path = (schema / "x-model-path").read_str(None)
if model_class_path is None:
return super().create(schema, fields)
return locate(model_class_path)
python-openapi-openapi-core-d6cdb4f/openapi_core/extensions/models/types.py 0000664 0000000 0000000 00000000156 15163577675 0027514 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Tuple
from typing import Union
Field = Union[str, Tuple[str, Any]]
python-openapi-openapi-core-d6cdb4f/openapi_core/protocols.py 0000664 0000000 0000000 00000007076 15163577675 0024722 0 ustar 00root root 0000000 0000000 """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:
"""The request method, as lowercase string."""
@property
def body(self) -> Optional[bytes]:
"""The request body, as bytes (None if not provided)."""
@property
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 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 support path attribute setter
to write resolved path parameters.
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).
"""
@property
def host_url(self) -> str:
"""Url with scheme and host. For example: https://localhost:8000"""
@property
def path(self) -> str:
"""Request path."""
@runtime_checkable
class WebhookRequest(BaseRequest, Protocol):
"""Webhook request protocol.
Attributes:
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 (e.g., charset, boundary, etc.)
and always lowercase.
body: The request body, as bytes (None if not provided).
"""
@property
def name(self) -> str:
"""Webhook name."""
@runtime_checkable
class SupportsPathPattern(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}
"""
@property
def path_pattern(self) -> str:
"""The matched path pattern. For example: /api/v1/pets/{pet_id}"""
@runtime_checkable
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).
"""
@property
def status_code(self) -> int:
"""The status code as integer."""
@property
def content_type(self) -> str:
"""The content type with parameters and always lowercase."""
@property
def headers(self) -> HeadersType:
"""Response headers as Headers."""
@property
def data(self) -> Optional[bytes]:
"""The response body, as bytes (None if not provided)."""
python-openapi-openapi-core-d6cdb4f/openapi_core/py.typed 0000664 0000000 0000000 00000000000 15163577675 0023777 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/schema/ 0000775 0000000 0000000 00000000000 15163577675 0023552 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/schema/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0025651 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/schema/encodings.py 0000664 0000000 0000000 00000002200 15163577675 0026067 0 ustar 00root root 0000000 0000000 from typing import Optional
from typing import cast
from jsonschema_path import SchemaPath
def get_content_type(
prop_schema: SchemaPath, encoding: Optional[SchemaPath]
) -> str:
if encoding is None:
return get_default_content_type(prop_schema, encoding=False)
if "contentType" not in encoding:
return get_default_content_type(prop_schema, encoding=True)
return cast(str, encoding["contentType"])
def get_default_content_type(
prop_schema: Optional[SchemaPath], encoding: bool = False
) -> str:
if prop_schema is None:
return "text/plain"
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 / "format").read_str(None)
if prop_type == "string" and prop_format in ["binary", "base64"]:
return "application/octet-stream"
if prop_type == "object":
return "application/json"
if prop_type == "array":
prop_items = prop_schema / "items"
return get_default_content_type(prop_items, encoding=encoding)
return "text/plain"
python-openapi-openapi-core-d6cdb4f/openapi_core/schema/parameters.py 0000664 0000000 0000000 00000002432 15163577675 0026270 0 ustar 00root root 0000000 0000000 from typing import Tuple
from jsonschema_path import SchemaPath
def get_style(
param_or_header: SchemaPath, default_location: str = "header"
) -> str:
"""Checks parameter/header style for simpler scenarios"""
if "style" in param_or_header:
assert isinstance(param_or_header["style"], str)
return param_or_header["style"]
location = (param_or_header / "in").read_str(default=default_location)
# determine default
return "simple" if location in ["path", "header"] else "form"
def get_explode(param_or_header: SchemaPath) -> bool:
"""Checks parameter/header explode for simpler scenarios"""
if "explode" in param_or_header:
assert isinstance(param_or_header["explode"], bool)
return param_or_header["explode"]
# determine default
style = get_style(param_or_header)
return style == "form"
def get_style_and_explode(
param_or_header: SchemaPath, default_location: str = "header"
) -> Tuple[str, bool]:
"""Checks parameter/header explode for simpler scenarios"""
style = get_style(param_or_header, default_location=default_location)
if "explode" in param_or_header:
assert isinstance(param_or_header["explode"], bool)
return style, param_or_header["explode"]
return style, style == "form"
python-openapi-openapi-core-d6cdb4f/openapi_core/schema/protocols.py 0000664 0000000 0000000 00000000476 15163577675 0026157 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import List
from typing import Protocol
from typing import runtime_checkable
@runtime_checkable
class SuportsGetAll(Protocol):
def getall(self, name: str) -> List[Any]: ...
@runtime_checkable
class SuportsGetList(Protocol):
def getlist(self, name: str) -> List[Any]: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/schema/schemas.py 0000664 0000000 0000000 00000000447 15163577675 0025554 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Dict
from jsonschema_path import SchemaPath
def get_properties(schema: SchemaPath) -> Dict[str, Any]:
properties = schema.get("properties", SchemaPath.from_dict({}))
properties_dict = dict(list(properties.items()))
return properties_dict
python-openapi-openapi-core-d6cdb4f/openapi_core/schema/servers.py 0000664 0000000 0000000 00000001347 15163577675 0025622 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Dict
from jsonschema_path import SchemaPath
def is_absolute(url: str) -> bool:
return url.startswith("//") or "://" in url
def get_server_default_variables(server: SchemaPath) -> Dict[str, Any]:
if "variables" not in server:
return {}
defaults = {}
variables = server / "variables"
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)
url = (server / "url").read_value()
assert isinstance(url, str)
return url.format(**variables)
python-openapi-openapi-core-d6cdb4f/openapi_core/schema/specs.py 0000664 0000000 0000000 00000000342 15163577675 0025240 0 ustar 00root root 0000000 0000000 from jsonschema_path import SchemaPath
from openapi_core.schema.servers import get_server_url
def get_spec_url(spec: SchemaPath, index: int = 0) -> str:
servers = spec / "servers"
return get_server_url(servers / 0)
python-openapi-openapi-core-d6cdb4f/openapi_core/security/ 0000775 0000000 0000000 00000000000 15163577675 0024161 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/security/__init__.py 0000664 0000000 0000000 00000000244 15163577675 0026272 0 ustar 00root root 0000000 0000000 from openapi_core.security.factories import SecurityProviderFactory
__all__ = ["security_provider_factory"]
security_provider_factory = SecurityProviderFactory()
python-openapi-openapi-core-d6cdb4f/openapi_core/security/exceptions.py 0000664 0000000 0000000 00000000147 15163577675 0026716 0 ustar 00root root 0000000 0000000 from openapi_core.exceptions import OpenAPIError
class SecurityProviderError(OpenAPIError):
pass
python-openapi-openapi-core-d6cdb4f/openapi_core/security/factories.py 0000664 0000000 0000000 00000001412 15163577675 0026510 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Dict
from typing import Type
from jsonschema_path import SchemaPath
from openapi_core.security.providers import ApiKeyProvider
from openapi_core.security.providers import BaseProvider
from openapi_core.security.providers import HttpProvider
from openapi_core.security.providers import UnsupportedProvider
class SecurityProviderFactory:
PROVIDERS: Dict[str, Type[BaseProvider]] = {
"apiKey": ApiKeyProvider,
"http": HttpProvider,
"oauth2": UnsupportedProvider,
"openIdConnect": UnsupportedProvider,
}
def create(self, scheme: SchemaPath) -> Any:
scheme_type = (scheme / "type").read_str()
provider_class = self.PROVIDERS[scheme_type]
return provider_class(scheme)
python-openapi-openapi-core-d6cdb4f/openapi_core/security/providers.py 0000664 0000000 0000000 00000003170 15163577675 0026551 0 ustar 00root root 0000000 0000000 import warnings
from typing import Any
from jsonschema_path import SchemaPath
from openapi_core.datatypes import RequestParameters
from openapi_core.security.exceptions import SecurityProviderError
class BaseProvider:
def __init__(self, scheme: SchemaPath):
self.scheme = scheme
def __call__(self, parameters: RequestParameters) -> Any:
raise NotImplementedError
class UnsupportedProvider(BaseProvider):
def __call__(self, parameters: RequestParameters) -> Any:
warnings.warn("Unsupported scheme type")
class ApiKeyProvider(BaseProvider):
def __call__(self, parameters: RequestParameters) -> Any:
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.")
return source[name]
class HttpProvider(BaseProvider):
def __call__(self, parameters: RequestParameters) -> Any:
if "Authorization" not in parameters.header:
raise SecurityProviderError("Missing authorization header.")
auth_header = parameters.header["Authorization"]
try:
auth_type, encoded_credentials = auth_header.split(" ", 1)
except ValueError:
raise SecurityProviderError(
"Could not parse authorization header."
)
scheme = self.scheme["scheme"]
if auth_type.lower() != scheme:
raise SecurityProviderError(
f"Unknown authorization method {auth_type}"
)
return encoded_credentials
python-openapi-openapi-core-d6cdb4f/openapi_core/shortcuts.py 0000664 0000000 0000000 00000024326 15163577675 0024731 0 ustar 00root root 0000000 0000000 """OpenAPI core shortcuts module"""
from typing import Any
from typing import Iterator
from typing import Optional
from typing import Union
from jsonschema.validators import _UNSET
from jsonschema_path import SchemaPath
from openapi_core.app import OpenAPI
from openapi_core.configurations import Config
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.protocols import WebhookRequest
from openapi_core.types import AnyRequest
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.request.types import AnyRequestUnmarshallerType
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
from openapi_core.unmarshalling.request.types import (
WebhookRequestUnmarshallerType,
)
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.response.types import (
AnyResponseUnmarshallerType,
)
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType
from openapi_core.unmarshalling.response.types import (
WebhookResponseUnmarshallerType,
)
from openapi_core.validation.request.types import AnyRequestValidatorType
from openapi_core.validation.request.types import RequestValidatorType
from openapi_core.validation.request.types import WebhookRequestValidatorType
from openapi_core.validation.response.types import AnyResponseValidatorType
from openapi_core.validation.response.types import ResponseValidatorType
from openapi_core.validation.response.types import WebhookResponseValidatorType
def unmarshal_apicall_request(
request: Request,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[RequestUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> RequestUnmarshalResult:
config = Config(
server_base_url=base_url,
request_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_apicall_request(request)
result.raise_for_errors()
return result
def unmarshal_webhook_request(
request: WebhookRequest,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[WebhookRequestUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> RequestUnmarshalResult:
config = Config(
server_base_url=base_url,
webhook_request_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_webhook_request(request)
result.raise_for_errors()
return result
def unmarshal_request(
request: AnyRequest,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[AnyRequestUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> RequestUnmarshalResult:
config = Config(
server_base_url=base_url,
request_unmarshaller_cls=cls or _UNSET,
webhook_request_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_request(request)
result.raise_for_errors()
return result
def unmarshal_apicall_response(
request: Request,
response: Response,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[ResponseUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> ResponseUnmarshalResult:
config = Config(
server_base_url=base_url,
response_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_apicall_response(
request, response
)
result.raise_for_errors()
return result
def unmarshal_webhook_response(
request: WebhookRequest,
response: Response,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[WebhookResponseUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> ResponseUnmarshalResult:
config = Config(
server_base_url=base_url,
webhook_response_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_webhook_response(
request, response
)
result.raise_for_errors()
return result
def unmarshal_response(
request: AnyRequest,
response: Response,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[AnyResponseUnmarshallerType] = None,
**unmarshaller_kwargs: Any,
) -> ResponseUnmarshalResult:
config = Config(
server_base_url=base_url,
response_unmarshaller_cls=cls or _UNSET,
webhook_response_unmarshaller_cls=cls or _UNSET,
**unmarshaller_kwargs,
)
result = OpenAPI(spec, config=config).unmarshal_response(request, response)
result.raise_for_errors()
return result
def validate_request(
request: AnyRequest,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[AnyRequestValidatorType] = None,
**validator_kwargs: Any,
) -> None:
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).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,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[AnyResponseValidatorType] = None,
**validator_kwargs: Any,
) -> None:
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).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,
base_url: Optional[str] = None,
cls: Optional[RequestValidatorType] = None,
**validator_kwargs: Any,
) -> None:
config = Config(
server_base_url=base_url,
request_validator_cls=cls or _UNSET,
**validator_kwargs,
)
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,
base_url: Optional[str] = None,
cls: Optional[WebhookRequestValidatorType] = None,
**validator_kwargs: Any,
) -> None:
config = Config(
server_base_url=base_url,
webhook_request_validator_cls=cls or _UNSET,
**validator_kwargs,
)
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,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[ResponseValidatorType] = None,
**validator_kwargs: Any,
) -> None:
config = Config(
server_base_url=base_url,
response_validator_cls=cls or _UNSET,
**validator_kwargs,
)
return OpenAPI(spec, config=config).validate_apicall_response(
request, 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,
spec: SchemaPath,
base_url: Optional[str] = None,
cls: Optional[WebhookResponseValidatorType] = None,
**validator_kwargs: Any,
) -> None:
config = Config(
server_base_url=base_url,
webhook_response_validator_cls=cls or _UNSET,
**validator_kwargs,
)
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
)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/ 0000775 0000000 0000000 00000000000 15163577675 0024456 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0026555 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/datatypes.py 0000664 0000000 0000000 00000000533 15163577675 0027027 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Dict
from typing import Optional
@dataclass
class TemplateResult:
pattern: str
variables: Optional[Dict[str, str]] = None
@property
def resolved(self) -> str:
if not self.variables:
return self.pattern
return self.pattern.format(**self.variables)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/media_types/ 0000775 0000000 0000000 00000000000 15163577675 0026761 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/media_types/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0031060 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/media_types/datatypes.py 0000664 0000000 0000000 00000000163 15163577675 0031331 0 ustar 00root root 0000000 0000000 from collections import namedtuple
MediaType = namedtuple("MediaType", ["mime_type", "parameters", "media_type"])
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/media_types/exceptions.py 0000664 0000000 0000000 00000000755 15163577675 0031523 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import List
from openapi_core.exceptions import OpenAPIError
class MediaTypeFinderError(OpenAPIError):
"""Media type finder error"""
@dataclass
class MediaTypeNotFound(MediaTypeFinderError):
mimetype: str
availableMimetypes: List[str]
def __str__(self) -> str:
return (
f"Content for the following mimetype not found: {self.mimetype}. "
f"Valid mimetypes: {self.availableMimetypes}"
)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/media_types/finders.py 0000664 0000000 0000000 00000004646 15163577675 0030777 0 ustar 00root root 0000000 0000000 """OpenAPI core templating media types finders module"""
import fnmatch
import re
from typing import Mapping
from typing import Tuple
from jsonschema_path import SchemaPath
from openapi_core.templating.media_types.datatypes import MediaType
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
class MediaTypeFinder:
def __init__(self, content: SchemaPath):
self.content = content
def get_first(self) -> MediaType:
mimetype, media_type = next(self.content.items())
return MediaType(mimetype, {}, media_type)
def find(self, mimetype: str) -> MediaType:
if mimetype is None:
raise MediaTypeNotFound(mimetype, list(self.content.keys()))
mime_type, parameters = self._parse_mimetype(mimetype)
# simple mime type
for m in [mimetype, mime_type]:
if m in self.content:
return MediaType(mime_type, parameters, self.content / m)
# range mime type
if mime_type:
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.str_keys()))
def _parse_mimetype(self, mimetype: str) -> Tuple[str, Mapping[str, str]]:
mimetype_parts = mimetype.split(";")
mime_type = mimetype_parts[0].lower().rstrip()
parameters = {}
if len(mimetype_parts) > 1:
parameters_list = (
self._parse_parameter(param_str)
for param_str in mimetype_parts[1:]
)
parameters = dict(parameters_list)
return mime_type, parameters
def _parse_parameter(self, parameter: str) -> Tuple[str, str]:
"""Parse a parameter according to RFC 9110.
See https://www.rfc-editor.org/rfc/rfc9110.html#name-parameters
Important points:
* parameter names are case-insensitive
* parameter values are case-sensitive
except "charset" which is case-insensitive
https://www.rfc-editor.org/rfc/rfc2046#section-4.1.2
"""
name, value = parameter.split("=")
name = name.lower().lstrip()
# remove surrounding quotes from value
value = re.sub('^"(.*)"$', r"\1", value, count=1)
if name == "charset":
value = value.lower()
return name, value.rstrip()
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/ 0000775 0000000 0000000 00000000000 15163577675 0025575 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/__init__.py 0000664 0000000 0000000 00000000311 15163577675 0027701 0 ustar 00root root 0000000 0000000 from openapi_core.templating.paths.finders import APICallPathFinder
from openapi_core.templating.paths.finders import WebhookPathFinder
__all__ = [
"APICallPathFinder",
"WebhookPathFinder",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/datatypes.py 0000664 0000000 0000000 00000000551 15163577675 0030146 0 ustar 00root root 0000000 0000000 """OpenAPI core templating paths datatypes module"""
from collections import namedtuple
Path = namedtuple("Path", ["path", "path_result"])
PathOperation = namedtuple(
"PathOperation", ["path", "operation", "path_result"]
)
PathOperationServer = namedtuple(
"PathOperationServer",
["path", "operation", "server", "path_result", "server_result"],
)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/exceptions.py 0000664 0000000 0000000 00000001511 15163577675 0030326 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from openapi_core.exceptions import OpenAPIError
class PathError(OpenAPIError):
"""Path error"""
@dataclass
class PathNotFound(PathError):
"""Path not found"""
url: str
def __str__(self) -> str:
return f"Path not found for {self.url}"
@dataclass
class PathsNotFound(PathNotFound):
"""Paths not found"""
def __str__(self) -> str:
return f"Paths not found in spec: {self.url}"
@dataclass
class OperationNotFound(PathError):
"""Find path operation error"""
url: str
method: str
def __str__(self) -> str:
return f"Operation {self.method} not found for {self.url}"
@dataclass
class ServerNotFound(PathError):
"""Find server error"""
url: str
def __str__(self) -> str:
return f"Server not found for {self.url}"
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/finders.py 0000664 0000000 0000000 00000005065 15163577675 0027607 0 ustar 00root root 0000000 0000000 """OpenAPI core templating paths finders module"""
from typing import Optional
from jsonschema_path import SchemaPath
from more_itertools import peekable
from openapi_core.templating.paths.datatypes import PathOperationServer
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.paths.iterators import SimpleOperationsIterator
from openapi_core.templating.paths.iterators import SimplePathsIterator
from openapi_core.templating.paths.iterators import SimpleServersIterator
from openapi_core.templating.paths.iterators import TemplatePathsIterator
from openapi_core.templating.paths.iterators import TemplateServersIterator
from openapi_core.templating.paths.protocols import OperationsIterator
from openapi_core.templating.paths.protocols import PathsIterator
from openapi_core.templating.paths.protocols import ServersIterator
class BasePathFinder:
paths_iterator: PathsIterator = NotImplemented
operations_iterator: OperationsIterator = NotImplemented
servers_iterator: ServersIterator = NotImplemented
def __init__(self, spec: SchemaPath, base_url: Optional[str] = None):
self.spec = spec
self.base_url = base_url
def find(self, method: str, name: str) -> PathOperationServer:
paths_iter = self.paths_iterator(
name,
self.spec,
base_url=self.base_url,
)
paths_iter_peek = peekable(paths_iter)
if not paths_iter_peek:
raise PathNotFound(name)
operations_iter = self.operations_iterator(
method,
paths_iter_peek,
self.spec,
base_url=self.base_url,
)
operations_iter_peek = peekable(operations_iter)
if not operations_iter_peek:
raise OperationNotFound(name, method)
servers_iter = self.servers_iterator(
name, operations_iter_peek, self.spec, base_url=self.base_url
)
try:
return next(servers_iter)
except StopIteration:
raise ServerNotFound(name)
class APICallPathFinder(BasePathFinder):
paths_iterator: PathsIterator = TemplatePathsIterator("paths")
operations_iterator: OperationsIterator = SimpleOperationsIterator()
servers_iterator: ServersIterator = TemplateServersIterator()
class WebhookPathFinder(APICallPathFinder):
paths_iterator = SimplePathsIterator("webhooks")
servers_iterator = SimpleServersIterator()
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/iterators.py 0000664 0000000 0000000 00000016171 15163577675 0030171 0 ustar 00root root 0000000 0000000 from functools import lru_cache
from typing import Iterator
from typing import List
from typing import Optional
from urllib.parse import urljoin
from urllib.parse import urlparse
from jsonschema_path import SchemaPath
from openapi_core.schema.servers import is_absolute
from openapi_core.templating.datatypes import TemplateResult
from openapi_core.templating.paths.datatypes import Path
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
class SimplePathsIterator:
def __init__(self, paths_part: str):
self.paths_part = paths_part
def __call__(
self, name: str, spec: SchemaPath, base_url: Optional[str] = None
) -> Iterator[Path]:
paths = spec / self.paths_part
if not paths.exists():
raise PathsNotFound(paths.as_uri())
for path_name, path in list(paths.str_items()):
if name == path_name:
path_result = TemplateResult(path_name, {})
yield Path(path, path_result)
class TemplatePathsIterator:
def __init__(self, paths_part: str):
self.paths_part = paths_part
def __call__(
self, name: str, spec: SchemaPath, base_url: Optional[str] = None
) -> Iterator[Path]:
paths = spec / self.paths_part
if not paths.exists():
raise PathsNotFound(paths.as_uri())
template_paths: List[Path] = []
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):
path_result = TemplateResult(path_pattern, {})
yield Path(path, path_result)
# template path
else:
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))
# 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__(
self,
method: str,
paths_iter: Iterator[Path],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperation]:
for path, path_result in paths_iter:
if method not in path:
continue
operation = path / method
yield PathOperation(path, operation, path_result)
class CatchAllMethodOperationsIterator(SimpleOperationsIterator):
def __init__(self, ca_method_name: str, ca_operation_name: str):
self.ca_method_name = ca_method_name
self.ca_operation_name = ca_operation_name
def __call__(
self,
method: str,
paths_iter: Iterator[Path],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperation]:
if method == self.ca_method_name:
yield from super().__call__(
self.ca_operation_name, paths_iter, spec, base_url=base_url
)
else:
yield from super().__call__(
method, paths_iter, spec, base_url=base_url
)
class SimpleServersIterator:
def __call__(
self,
name: str,
operations_iter: Iterator[PathOperation],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperationServer]:
for path, operation, path_result in operations_iter:
yield PathOperationServer(
path,
operation,
None,
path_result,
{},
)
class TemplateServersIterator:
def __call__(
self,
name: str,
operations_iter: Iterator[PathOperation],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperationServer]:
for path, operation, path_result in operations_iter:
servers = (
path.get("servers", None)
or operation.get("servers", None)
or spec.get("servers", None)
)
if not servers:
servers = [SchemaPath.from_dict({"url": "/"})]
for server in servers:
server_url_pattern = name.rsplit(path_result.resolved, 1)[0]
server_url = server["url"]
if not is_absolute(server_url):
# relative to absolute url
if base_url is not None:
server_url = urljoin(base_url, server["url"])
# if no base url check only path part
else:
server_url_pattern = urlparse(server_url_pattern).path
if server_url.endswith("/"):
server_url = server_url[:-1]
# simple path
if server_url_pattern == server_url:
server_result = TemplateResult(server["url"], {})
yield PathOperationServer(
path,
operation,
server,
path_result,
server_result,
)
# template path
else:
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
)
yield PathOperationServer(
path,
operation,
server,
path_result,
server_result,
)
# servers should'n end with tailing slash
# but let's search for this too
server_url_pattern += "/"
result = server_url_parser.parse(server_url_pattern)
if result:
server_result = TemplateResult(
server["url"], result.named
)
yield PathOperationServer(
path,
operation,
server,
path_result,
server_result,
)
@lru_cache(maxsize=1024)
def _get_server_url_parser(self, server_url: str) -> PathParser:
return PathParser(server_url, pre_expression="^")
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/parsers.py 0000664 0000000 0000000 00000004277 15163577675 0027640 0 ustar 00root root 0000000 0000000 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()
},
)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/protocols.py 0000664 0000000 0000000 00000002025 15163577675 0030172 0 ustar 00root root 0000000 0000000 from typing import Iterator
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from jsonschema_path import SchemaPath
from openapi_core.templating.paths.datatypes import Path
from openapi_core.templating.paths.datatypes import PathOperation
from openapi_core.templating.paths.datatypes import PathOperationServer
@runtime_checkable
class PathsIterator(Protocol):
def __call__(
self, name: str, spec: SchemaPath, base_url: Optional[str] = None
) -> Iterator[Path]: ...
@runtime_checkable
class OperationsIterator(Protocol):
def __call__(
self,
method: str,
paths_iter: Iterator[Path],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperation]: ...
@runtime_checkable
class ServersIterator(Protocol):
def __call__(
self,
name: str,
operations_iter: Iterator[PathOperation],
spec: SchemaPath,
base_url: Optional[str] = None,
) -> Iterator[PathOperationServer]: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/types.py 0000664 0000000 0000000 00000000201 15163577675 0027304 0 ustar 00root root 0000000 0000000 from typing import Type
from openapi_core.templating.paths.finders import BasePathFinder
PathFinderType = Type[BasePathFinder]
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/paths/util.py 0000664 0000000 0000000 00000000231 15163577675 0027120 0 ustar 00root root 0000000 0000000 from openapi_core.templating.paths.datatypes import Path
def template_path_len(template_path: Path) -> int:
return len(template_path[1].variables)
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/responses/ 0000775 0000000 0000000 00000000000 15163577675 0026477 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/responses/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0030576 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/responses/exceptions.py 0000664 0000000 0000000 00000000656 15163577675 0031241 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import List
from openapi_core.exceptions import OpenAPIError
class ResponseFinderError(OpenAPIError):
"""Response finder error"""
@dataclass
class ResponseNotFound(ResponseFinderError):
"""Find response error"""
http_status: str
availableresponses: List[str]
def __str__(self) -> str:
return f"Unknown response http status: {str(self.http_status)}"
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/responses/finders.py 0000664 0000000 0000000 00000001353 15163577675 0030505 0 ustar 00root root 0000000 0000000 from jsonschema_path import SchemaPath
from openapi_core.templating.responses.exceptions import ResponseNotFound
class ResponseFinder:
def __init__(self, responses: SchemaPath):
self.responses = responses
def find(self, http_status: str = "default") -> SchemaPath:
if http_status in self.responses:
return self.responses / http_status
# try range
http_status_range = f"{http_status[0]}XX"
if http_status_range in self.responses:
return self.responses / http_status_range
if "default" not in self.responses:
raise ResponseNotFound(
http_status, list(self.responses.str_keys())
)
return self.responses / "default"
python-openapi-openapi-core-d6cdb4f/openapi_core/templating/security/ 0000775 0000000 0000000 00000000000 15163577675 0026325 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/security/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0030424 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/templating/security/exceptions.py 0000664 0000000 0000000 00000000655 15163577675 0031066 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import List
from openapi_core.exceptions import OpenAPIError
class SecurityFinderError(OpenAPIError):
"""Security finder error"""
@dataclass
class SecurityNotFound(SecurityFinderError):
"""Find security error"""
schemes: List[List[str]]
def __str__(self) -> str:
return f"Security not found. Schemes not valid for any requirement: {str(self.schemes)}"
python-openapi-openapi-core-d6cdb4f/openapi_core/testing/ 0000775 0000000 0000000 00000000000 15163577675 0023767 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/testing/__init__.py 0000664 0000000 0000000 00000000307 15163577675 0026100 0 ustar 00root root 0000000 0000000 """OpenAPI core testing module"""
from openapi_core.testing.requests import MockRequest
from openapi_core.testing.responses import MockResponse
__all__ = [
"MockRequest",
"MockResponse",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/testing/datatypes.py 0000664 0000000 0000000 00000001062 15163577675 0026336 0 ustar 00root root 0000000 0000000 from typing import Optional
from openapi_core.datatypes import Parameters
class ResultMock:
def __init__(
self,
body: Optional[str] = None,
parameters: Optional[Parameters] = None,
data: Optional[str] = None,
error_to_raise: Optional[Exception] = None,
):
self.body = body
self.parameters = parameters
self.data = data
self.error_to_raise = error_to_raise
def raise_for_errors(self) -> None:
if self.error_to_raise is not None:
raise self.error_to_raise
python-openapi-openapi-core-d6cdb4f/openapi_core/testing/requests.py 0000664 0000000 0000000 00000002441 15163577675 0026215 0 ustar 00root root 0000000 0000000 """OpenAPI core testing requests module"""
from typing import Any
from typing import Dict
from typing import Optional
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.datatypes import RequestParameters
class MockRequest:
def __init__(
self,
host_url: str,
method: str,
path: str,
path_pattern: Optional[str] = None,
args: Optional[Dict[str, Any]] = None,
view_args: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, Any]] = None,
cookies: Optional[Dict[str, Any]] = None,
data: Optional[bytes] = None,
content_type: str = "application/json",
):
self.host_url = host_url
self.method = method.lower()
self.path = path
self.path_pattern = path_pattern
self.args = args
self.view_args = view_args
self.headers = headers
self.cookies = cookies
self.body = data or b""
self.content_type = content_type
self.parameters = RequestParameters(
path=self.view_args or {},
query=ImmutableMultiDict(self.args or {}),
header=Headers(self.headers or {}),
cookie=ImmutableMultiDict(self.cookies or {}),
)
python-openapi-openapi-core-d6cdb4f/openapi_core/testing/responses.py 0000664 0000000 0000000 00000001020 15163577675 0026353 0 ustar 00root root 0000000 0000000 """OpenAPI core testing responses module"""
from typing import Any
from typing import Dict
from typing import Optional
from werkzeug.datastructures import Headers
class MockResponse:
def __init__(
self,
data: bytes,
status_code: int = 200,
headers: Optional[Dict[str, Any]] = None,
content_type: str = "application/json",
):
self.data = data
self.status_code = status_code
self.headers = Headers(headers or {})
self.content_type = content_type
python-openapi-openapi-core-d6cdb4f/openapi_core/types.py 0000664 0000000 0000000 00000000276 15163577675 0024035 0 ustar 00root root 0000000 0000000 """OpenAPI core types"""
from typing import Union
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
AnyRequest = Union[Request, WebhookRequest]
python-openapi-openapi-core-d6cdb4f/openapi_core/typing.py 0000664 0000000 0000000 00000000305 15163577675 0024174 0 ustar 00root root 0000000 0000000 from typing import TypeVar
#: The type of request within an integration.
RequestType = TypeVar("RequestType")
#: The type of response within an integration.
ResponseType = TypeVar("ResponseType")
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/ 0000775 0000000 0000000 00000000000 15163577675 0025156 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/__init__.py 0000664 0000000 0000000 00000000000 15163577675 0027255 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/configurations.py 0000664 0000000 0000000 00000001351 15163577675 0030562 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Optional
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.validation.configurations import ValidatorConfig
@dataclass
class UnmarshallerConfig(ValidatorConfig):
"""Unmarshaller configuration dataclass.
Attributes:
schema_unmarshallers_factory
Schema unmarshallers factory.
extra_format_unmarshallers
Extra format unmarshallers.
"""
schema_unmarshallers_factory: Optional[SchemaUnmarshallersFactory] = None
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/datatypes.py 0000664 0000000 0000000 00000000516 15163577675 0027530 0 ustar 00root root 0000000 0000000 """OpenAPI core validation datatypes module"""
from dataclasses import dataclass
from typing import Iterable
from openapi_core.exceptions import OpenAPIError
@dataclass
class BaseUnmarshalResult:
errors: Iterable[OpenAPIError]
def raise_for_errors(self) -> None:
for error in self.errors:
raise error
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/integrations.py 0000664 0000000 0000000 00000004311 15163577675 0030235 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling processors module"""
from typing import Generic
from openapi_core.app import OpenAPI
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.typing import RequestType
from openapi_core.typing import ResponseType
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.validation.integrations import ValidationIntegration
class UnmarshallingIntegration(
ValidationIntegration[RequestType, ResponseType]
):
def unmarshal_request(
self, request: RequestType
) -> RequestUnmarshalResult:
openapi_request = self.get_openapi_request(request)
return self.openapi.unmarshal_request(
openapi_request,
)
def unmarshal_response(
self,
request: RequestType,
response: ResponseType,
) -> ResponseUnmarshalResult:
openapi_request = self.get_openapi_request(request)
openapi_response = self.get_openapi_response(response)
return self.openapi.unmarshal_response(
openapi_request, openapi_response
)
class AsyncUnmarshallingIntegration(Generic[RequestType, ResponseType]):
def __init__(
self,
openapi: OpenAPI,
):
self.openapi = openapi
async def get_openapi_request(self, request: RequestType) -> Request:
raise NotImplementedError
async def get_openapi_response(self, response: ResponseType) -> Response:
raise NotImplementedError
async def unmarshal_request(
self,
request: RequestType,
) -> RequestUnmarshalResult:
openapi_request = await self.get_openapi_request(request)
return self.openapi.unmarshal_request(openapi_request)
async def unmarshal_response(
self,
request: RequestType,
response: ResponseType,
) -> ResponseUnmarshalResult:
openapi_request = await self.get_openapi_request(request)
openapi_response = await self.get_openapi_response(response)
return self.openapi.unmarshal_response(
openapi_request, openapi_response
)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/processors.py 0000664 0000000 0000000 00000004744 15163577675 0027743 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling processors module"""
from openapi_core.typing import RequestType
from openapi_core.typing import ResponseType
from openapi_core.unmarshalling.integrations import (
AsyncUnmarshallingIntegration,
)
from openapi_core.unmarshalling.integrations import UnmarshallingIntegration
from openapi_core.unmarshalling.typing import AsyncValidRequestHandlerCallable
from openapi_core.unmarshalling.typing import ErrorsHandlerCallable
from openapi_core.unmarshalling.typing import ValidRequestHandlerCallable
class UnmarshallingProcessor(
UnmarshallingIntegration[RequestType, ResponseType]
):
def handle_request(
self,
request: RequestType,
valid_handler: ValidRequestHandlerCallable[ResponseType],
errors_handler: ErrorsHandlerCallable[ResponseType],
) -> ResponseType:
request_unmarshal_result = self.unmarshal_request(
request,
)
if request_unmarshal_result.errors:
return errors_handler(request_unmarshal_result.errors)
return valid_handler(request_unmarshal_result)
def handle_response(
self,
request: RequestType,
response: ResponseType,
errors_handler: ErrorsHandlerCallable[ResponseType],
) -> ResponseType:
response_unmarshal_result = self.unmarshal_response(request, response)
if response_unmarshal_result.errors:
return errors_handler(response_unmarshal_result.errors)
return response
class AsyncUnmarshallingProcessor(
AsyncUnmarshallingIntegration[RequestType, ResponseType]
):
async def handle_request(
self,
request: RequestType,
valid_handler: AsyncValidRequestHandlerCallable[ResponseType],
errors_handler: ErrorsHandlerCallable[ResponseType],
) -> ResponseType:
request_unmarshal_result = await self.unmarshal_request(request)
if request_unmarshal_result.errors:
return errors_handler(request_unmarshal_result.errors)
result = await valid_handler(request_unmarshal_result)
return result
async def handle_response(
self,
request: RequestType,
response: ResponseType,
errors_handler: ErrorsHandlerCallable[ResponseType],
) -> ResponseType:
response_unmarshal_result = await self.unmarshal_response(
request, response
)
if response_unmarshal_result.errors:
return errors_handler(response_unmarshal_result.errors)
return response
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/ 0000775 0000000 0000000 00000000000 15163577675 0026646 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/__init__.py 0000664 0000000 0000000 00000003270 15163577675 0030761 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling request module"""
from typing import Mapping
from openapi_spec_validator.versions import consts as versions
from openapi_spec_validator.versions.datatypes import SpecVersion
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
from openapi_core.unmarshalling.request.types import (
WebhookRequestUnmarshallerType,
)
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestUnmarshaller,
)
from openapi_core.unmarshalling.request.unmarshallers import (
V31RequestUnmarshaller,
)
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",
"WEBHOOK_UNMARSHALLERS",
"V3RequestUnmarshaller",
"V3WebhookRequestUnmarshaller",
"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 = V32RequestUnmarshaller
V3WebhookRequestUnmarshaller = V32WebhookRequestUnmarshaller
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/datatypes.py 0000664 0000000 0000000 00000000714 15163577675 0031220 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling request datatypes module"""
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
@dataclass
class RequestUnmarshalResult(BaseUnmarshalResult):
body: Any | None = None
parameters: Parameters = field(default_factory=Parameters)
security: dict[str, str] | None = None
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/processors.py 0000664 0000000 0000000 00000002345 15163577675 0031426 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Optional
from jsonschema_path import SchemaPath
from openapi_core.protocols import Request
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.request.protocols import RequestUnmarshaller
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
class RequestUnmarshallingProcessor:
def __init__(
self,
spec: SchemaPath,
request_unmarshaller_cls: RequestUnmarshallerType,
**unmarshaller_kwargs: Any
) -> None:
self.spec = spec
self.request_unmarshaller_cls = request_unmarshaller_cls
self.unmarshaller_kwargs = unmarshaller_kwargs
self._request_unmarshaller_cached: Optional[RequestUnmarshaller] = None
@property
def request_unmarshaller(self) -> RequestUnmarshaller:
if self._request_unmarshaller_cached is None:
self._request_unmarshaller_cached = self.request_unmarshaller_cls(
self.spec, **self.unmarshaller_kwargs
)
return self._request_unmarshaller_cached
def process(self, request: Request) -> RequestUnmarshalResult:
return self.request_unmarshaller.unmarshal(request)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/protocols.py 0000664 0000000 0000000 00000010050 15163577675 0031240 0 ustar 00root root 0000000 0000000 """OpenAPI core validation request protocols module"""
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
from openapi_core.security import security_provider_factory
from openapi_core.security.factories import SecurityProviderFactory
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@runtime_checkable
class RequestUnmarshaller(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
): ...
def unmarshal(
self,
request: Request,
) -> RequestUnmarshalResult: ...
@runtime_checkable
class WebhookRequestUnmarshaller(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
): ...
def unmarshal(
self,
request: WebhookRequest,
) -> RequestUnmarshalResult: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/types.py 0000664 0000000 0000000 00000000665 15163577675 0030373 0 ustar 00root root 0000000 0000000 from typing import Type
from typing import Union
from openapi_core.unmarshalling.request.protocols import RequestUnmarshaller
from openapi_core.unmarshalling.request.protocols import (
WebhookRequestUnmarshaller,
)
RequestUnmarshallerType = Type[RequestUnmarshaller]
WebhookRequestUnmarshallerType = Type[WebhookRequestUnmarshaller]
AnyRequestUnmarshallerType = Union[
RequestUnmarshallerType, WebhookRequestUnmarshallerType
]
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/request/unmarshallers.py 0000664 0000000 0000000 00000043273 15163577675 0032111 0 ustar 00root root 0000000 0000000 from typing import Optional
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import BaseRequest
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
from openapi_core.security import security_provider_factory
from openapi_core.security.factories import SecurityProviderFactory
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.schemas import (
oas30_write_schema_unmarshallers_factory,
)
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,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.unmarshalling.unmarshallers import BaseUnmarshaller
from openapi_core.util import chainiters
from openapi_core.validation.request.exceptions import MissingRequestBody
from openapi_core.validation.request.exceptions import ParametersError
from openapi_core.validation.request.exceptions import (
RequestBodyValidationError,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
from openapi_core.validation.request.validators import APICallRequestValidator
from openapi_core.validation.request.validators import BaseRequestValidator
from openapi_core.validation.request.validators import V30RequestBodyValidator
from openapi_core.validation.request.validators import (
V30RequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V30RequestSecurityValidator,
)
from openapi_core.validation.request.validators import V30RequestValidator
from openapi_core.validation.request.validators import V31RequestBodyValidator
from openapi_core.validation.request.validators import (
V31RequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V31RequestSecurityValidator,
)
from openapi_core.validation.request.validators import V31RequestValidator
from openapi_core.validation.request.validators import (
V31WebhookRequestBodyValidator,
)
from openapi_core.validation.request.validators import (
V31WebhookRequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V31WebhookRequestSecurityValidator,
)
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
class BaseRequestUnmarshaller(BaseRequestValidator, BaseUnmarshaller):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
):
BaseUnmarshaller.__init__(
self,
spec,
base_url=base_url,
style_deserializers_factory=style_deserializers_factory,
media_type_deserializers_factory=media_type_deserializers_factory,
schema_casters_factory=schema_casters_factory,
schema_validators_factory=schema_validators_factory,
path_finder_cls=path_finder_cls,
spec_validator_cls=spec_validator_cls,
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,
)
BaseRequestValidator.__init__(
self,
spec,
base_url=base_url,
style_deserializers_factory=style_deserializers_factory,
media_type_deserializers_factory=media_type_deserializers_factory,
schema_casters_factory=schema_casters_factory,
schema_validators_factory=schema_validators_factory,
path_finder_cls=path_finder_cls,
spec_validator_cls=spec_validator_cls,
format_validators=format_validators,
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(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> RequestUnmarshalResult:
try:
security = self._get_security(request.parameters, operation)
except SecurityValidationError as exc:
return RequestUnmarshalResult(errors=[exc])
try:
params = self._get_parameters(request.parameters, operation, path)
except ParametersError as exc:
params = exc.parameters
params_errors = exc.errors
else:
params_errors = []
try:
body = self._get_body(
request.body, request.content_type, operation
)
except MissingRequestBody:
body = None
body_errors = []
except RequestBodyValidationError as exc:
body = None
body_errors = [exc]
else:
body_errors = []
errors = list(chainiters(params_errors, body_errors))
return RequestUnmarshalResult(
errors=errors,
body=body,
parameters=params,
security=security,
)
def _unmarshal_body(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> RequestUnmarshalResult:
try:
body = self._get_body(
request.body, request.content_type, operation
)
except MissingRequestBody:
body = None
errors = []
except RequestBodyValidationError as exc:
body = None
errors = [exc]
else:
errors = []
return RequestUnmarshalResult(
errors=errors,
body=body,
)
def _unmarshal_parameters(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> RequestUnmarshalResult:
try:
params = self._get_parameters(request.parameters, operation, path)
except ParametersError as exc:
params = exc.parameters
params_errors = exc.errors
else:
params_errors = []
return RequestUnmarshalResult(
errors=params_errors,
parameters=params,
)
def _unmarshal_security(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> RequestUnmarshalResult:
try:
security = self._get_security(request.parameters, operation)
except SecurityValidationError as exc:
return RequestUnmarshalResult(errors=[exc])
return RequestUnmarshalResult(
errors=[],
security=security,
)
class BaseAPICallRequestUnmarshaller(BaseRequestUnmarshaller):
pass
class BaseWebhookRequestUnmarshaller(BaseRequestUnmarshaller):
pass
class APICallRequestUnmarshaller(
APICallRequestValidator, BaseAPICallRequestUnmarshaller
):
def unmarshal(self, request: Request) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal(request, operation, path)
class APICallRequestBodyUnmarshaller(
APICallRequestValidator, BaseAPICallRequestUnmarshaller
):
def unmarshal(self, request: Request) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_body(request, operation, path)
class APICallRequestParametersUnmarshaller(
APICallRequestValidator, BaseAPICallRequestUnmarshaller
):
def unmarshal(self, request: Request) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_parameters(request, operation, path)
class APICallRequestSecurityUnmarshaller(
APICallRequestValidator, BaseAPICallRequestUnmarshaller
):
def unmarshal(self, request: Request) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_security(request, operation, path)
class WebhookRequestUnmarshaller(
WebhookRequestValidator, BaseWebhookRequestUnmarshaller
):
def unmarshal(self, request: WebhookRequest) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal(request, operation, path)
class WebhookRequestBodyUnmarshaller(
WebhookRequestValidator, BaseWebhookRequestUnmarshaller
):
def unmarshal(self, request: WebhookRequest) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_body(request, operation, path)
class WebhookRequestParametersUnmarshaller(
WebhookRequestValidator, BaseWebhookRequestUnmarshaller
):
def unmarshal(self, request: WebhookRequest) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_parameters(request, operation, path)
class WebhookRequestSecuritysUnmarshaller(
WebhookRequestValidator, BaseWebhookRequestUnmarshaller
):
def unmarshal(self, request: WebhookRequest) -> RequestUnmarshalResult:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestUnmarshalResult(errors=[exc])
request.parameters.path = (
request.parameters.path or path_result.variables
)
return self._unmarshal_security(request, operation, path)
class V30RequestBodyUnmarshaller(
V30RequestBodyValidator, APICallRequestBodyUnmarshaller
):
schema_unmarshallers_factory = oas30_write_schema_unmarshallers_factory
class V30RequestParametersUnmarshaller(
V30RequestParametersValidator, APICallRequestParametersUnmarshaller
):
schema_unmarshallers_factory = oas30_write_schema_unmarshallers_factory
class V30RequestSecurityUnmarshaller(
V30RequestSecurityValidator, APICallRequestSecurityUnmarshaller
):
schema_unmarshallers_factory = oas30_write_schema_unmarshallers_factory
class V30RequestUnmarshaller(V30RequestValidator, APICallRequestUnmarshaller):
schema_unmarshallers_factory = oas30_write_schema_unmarshallers_factory
class V31RequestBodyUnmarshaller(
V31RequestBodyValidator, APICallRequestBodyUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31RequestParametersUnmarshaller(
V31RequestParametersValidator, APICallRequestParametersUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31RequestSecurityUnmarshaller(
V31RequestSecurityValidator, APICallRequestSecurityUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31RequestUnmarshaller(V31RequestValidator, APICallRequestUnmarshaller):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31WebhookRequestBodyUnmarshaller(
V31WebhookRequestBodyValidator, WebhookRequestBodyUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31WebhookRequestParametersUnmarshaller(
V31WebhookRequestParametersValidator, WebhookRequestParametersUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31WebhookRequestSecurityUnmarshaller(
V31WebhookRequestSecurityValidator, WebhookRequestSecuritysUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
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
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/ 0000775 0000000 0000000 00000000000 15163577675 0027014 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/__init__.py 0000664 0000000 0000000 00000003337 15163577675 0031133 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling response module"""
from typing import Mapping
from openapi_spec_validator.versions import consts as versions
from openapi_spec_validator.versions.datatypes import SpecVersion
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType
from openapi_core.unmarshalling.response.types import (
WebhookResponseUnmarshallerType,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V31ResponseUnmarshaller,
)
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",
"WEBHOOK_UNMARSHALLERS",
"V3ResponseUnmarshaller",
"V3WebhookResponseUnmarshaller",
"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 = V32ResponseUnmarshaller
V3WebhookResponseUnmarshaller = V32WebhookResponseUnmarshaller
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/datatypes.py 0000664 0000000 0000000 00000000647 15163577675 0031373 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling response datatypes module"""
from dataclasses import dataclass
from dataclasses import field
from typing import Any
from typing import Dict
from typing import Optional
from openapi_core.unmarshalling.datatypes import BaseUnmarshalResult
@dataclass
class ResponseUnmarshalResult(BaseUnmarshalResult):
data: Optional[str] = None
headers: Dict[str, Any] = field(default_factory=dict)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/processors.py 0000664 0000000 0000000 00000002633 15163577675 0031574 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Optional
from jsonschema_path import SchemaPath
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.response.protocols import ResponseUnmarshaller
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType
class ResponseUnmarshallingProcessor:
def __init__(
self,
spec: SchemaPath,
response_unmarshaller_cls: ResponseUnmarshallerType,
**unmarshaller_kwargs: Any
) -> None:
self.spec = spec
self.response_unmarshaller_cls = response_unmarshaller_cls
self.unmarshaller_kwargs = unmarshaller_kwargs
self._response_unmarshaller_cached: Optional[ResponseUnmarshaller] = (
None
)
@property
def response_unmarshaller(self) -> ResponseUnmarshaller:
if self._response_unmarshaller_cached is None:
self._response_unmarshaller_cached = (
self.response_unmarshaller_cls(
self.spec, **self.unmarshaller_kwargs
)
)
return self._response_unmarshaller_cached
def process(
self, request: Request, response: Response
) -> ResponseUnmarshalResult:
return self.response_unmarshaller.unmarshal(request, response)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/protocols.py 0000664 0000000 0000000 00000007722 15163577675 0031422 0 ustar 00root root 0000000 0000000 """OpenAPI core validation response protocols module"""
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.protocols import WebhookRequest
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@runtime_checkable
class ResponseUnmarshaller(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
forbid_unspecified_additional_properties: bool = False,
enforce_properties_required: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
): ...
def unmarshal(
self,
request: Request,
response: Response,
) -> ResponseUnmarshalResult: ...
@runtime_checkable
class WebhookResponseUnmarshaller(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
forbid_unspecified_additional_properties: bool = False,
enforce_properties_required: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
): ...
def unmarshal(
self,
request: WebhookRequest,
response: Response,
) -> ResponseUnmarshalResult: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/types.py 0000664 0000000 0000000 00000000700 15163577675 0030527 0 ustar 00root root 0000000 0000000 from typing import Type
from typing import Union
from openapi_core.unmarshalling.response.protocols import ResponseUnmarshaller
from openapi_core.unmarshalling.response.protocols import (
WebhookResponseUnmarshaller,
)
ResponseUnmarshallerType = Type[ResponseUnmarshaller]
WebhookResponseUnmarshallerType = Type[WebhookResponseUnmarshaller]
AnyResponseUnmarshallerType = Union[
ResponseUnmarshallerType, WebhookResponseUnmarshallerType
]
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/response/unmarshallers.py 0000664 0000000 0000000 00000025456 15163577675 0032262 0 ustar 00root root 0000000 0000000 from jsonschema_path import SchemaPath
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.protocols import WebhookRequest
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.templating.responses.exceptions import ResponseFinderError
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.schemas import (
oas30_read_schema_unmarshallers_factory,
)
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
from openapi_core.validation.response.exceptions import HeadersError
from openapi_core.validation.response.validators import (
APICallResponseValidator,
)
from openapi_core.validation.response.validators import BaseResponseValidator
from openapi_core.validation.response.validators import (
V30ResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V30ResponseHeadersValidator,
)
from openapi_core.validation.response.validators import V30ResponseValidator
from openapi_core.validation.response.validators import (
V31ResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V31ResponseHeadersValidator,
)
from openapi_core.validation.response.validators import V31ResponseValidator
from openapi_core.validation.response.validators import (
V31WebhookResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V31WebhookResponseHeadersValidator,
)
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,
)
class BaseResponseUnmarshaller(BaseResponseValidator, BaseUnmarshaller):
def _unmarshal(
self,
response: Response,
operation: SchemaPath,
) -> ResponseUnmarshalResult:
try:
operation_response = self._find_operation_response(
response.status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
return ResponseUnmarshalResult(errors=[exc])
try:
validated_data = self._get_data(
response.data, response.content_type, operation_response
)
except DataValidationError as exc:
validated_data = None
data_errors = [exc]
else:
data_errors = []
try:
validated_headers = self._get_headers(
response.headers, operation_response
)
except HeadersError as exc:
validated_headers = exc.headers
headers_errors = exc.context
else:
headers_errors = []
errors = list(chainiters(data_errors, headers_errors))
return ResponseUnmarshalResult(
errors=errors,
data=validated_data,
headers=validated_headers,
)
def _unmarshal_data(
self,
response: Response,
operation: SchemaPath,
) -> ResponseUnmarshalResult:
try:
operation_response = self._find_operation_response(
response.status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
return ResponseUnmarshalResult(errors=[exc])
try:
validated = self._get_data(
response.data, response.content_type, operation_response
)
except DataValidationError as exc:
validated = None
data_errors = [exc]
else:
data_errors = []
return ResponseUnmarshalResult(
errors=data_errors,
data=validated,
)
def _unmarshal_headers(
self,
response: Response,
operation: SchemaPath,
) -> ResponseUnmarshalResult:
try:
operation_response = self._find_operation_response(
response.status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
return ResponseUnmarshalResult(errors=[exc])
try:
validated = self._get_headers(response.headers, operation_response)
except HeadersError as exc:
validated = exc.headers
headers_errors = exc.context
else:
headers_errors = []
return ResponseUnmarshalResult(
errors=headers_errors,
headers=validated,
)
class APICallResponseUnmarshaller(
APICallResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: Request,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal(response, operation)
class APICallResponseDataUnmarshaller(
APICallResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: Request,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal_data(response, operation)
class APICallResponseHeadersUnmarshaller(
APICallResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: Request,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal_headers(response, operation)
class WebhookResponseUnmarshaller(
WebhookResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: WebhookRequest,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal(response, operation)
class WebhookResponseDataUnmarshaller(
WebhookResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: WebhookRequest,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal_data(response, operation)
class WebhookResponseHeadersUnmarshaller(
WebhookResponseValidator, BaseResponseUnmarshaller
):
def unmarshal(
self,
request: WebhookRequest,
response: Response,
) -> ResponseUnmarshalResult:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseUnmarshalResult(errors=[exc])
return self._unmarshal_headers(response, operation)
class V30ResponseDataUnmarshaller(
V30ResponseDataValidator, APICallResponseDataUnmarshaller
):
schema_unmarshallers_factory = oas30_read_schema_unmarshallers_factory
class V30ResponseHeadersUnmarshaller(
V30ResponseHeadersValidator, APICallResponseHeadersUnmarshaller
):
schema_unmarshallers_factory = oas30_read_schema_unmarshallers_factory
class V30ResponseUnmarshaller(
V30ResponseValidator, APICallResponseUnmarshaller
):
schema_unmarshallers_factory = oas30_read_schema_unmarshallers_factory
class V31ResponseDataUnmarshaller(
V31ResponseDataValidator, APICallResponseDataUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31ResponseHeadersUnmarshaller(
V31ResponseHeadersValidator, APICallResponseHeadersUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31ResponseUnmarshaller(
V31ResponseValidator, APICallResponseUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31WebhookResponseDataUnmarshaller(
V31WebhookResponseDataValidator, WebhookResponseDataUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
class V31WebhookResponseHeadersUnmarshaller(
V31WebhookResponseHeadersValidator, WebhookResponseHeadersUnmarshaller
):
schema_unmarshallers_factory = oas31_schema_unmarshallers_factory
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
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/ 0000775 0000000 0000000 00000000000 15163577675 0026601 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/__init__.py 0000664 0000000 0000000 00000010172 15163577675 0030713 0 ustar 00root root 0000000 0000000 from collections import OrderedDict
from isodate.isodatetime import parse_datetime
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.unmarshalling.schemas.unmarshallers import AnyUnmarshaller
from openapi_core.unmarshalling.schemas.unmarshallers import ArrayUnmarshaller
from openapi_core.unmarshalling.schemas.unmarshallers import (
MultiTypeUnmarshaller,
)
from openapi_core.unmarshalling.schemas.unmarshallers import ObjectUnmarshaller
from openapi_core.unmarshalling.schemas.unmarshallers import (
PrimitiveUnmarshaller,
)
from openapi_core.unmarshalling.schemas.unmarshallers import TypesUnmarshaller
from openapi_core.unmarshalling.schemas.util import format_byte
from openapi_core.unmarshalling.schemas.util import format_date
from openapi_core.unmarshalling.schemas.util import format_uuid
from openapi_core.validation.schemas import (
oas30_read_schema_validators_factory,
)
from openapi_core.validation.schemas import (
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(
[
("object", ObjectUnmarshaller),
("array", ArrayUnmarshaller),
("boolean", PrimitiveUnmarshaller),
("integer", PrimitiveUnmarshaller),
("number", PrimitiveUnmarshaller),
("string", PrimitiveUnmarshaller),
]
)
oas31_unmarshallers_dict = OrderedDict(
[
("object", ObjectUnmarshaller),
("array", ArrayUnmarshaller),
("boolean", PrimitiveUnmarshaller),
("integer", PrimitiveUnmarshaller),
("number", PrimitiveUnmarshaller),
("string", PrimitiveUnmarshaller),
("null", PrimitiveUnmarshaller),
]
)
oas30_types_unmarshaller = TypesUnmarshaller(
dict(oas30_unmarshallers_dict),
AnyUnmarshaller,
)
oas31_types_unmarshaller = TypesUnmarshaller(
dict(oas31_unmarshallers_dict),
AnyUnmarshaller,
multi=MultiTypeUnmarshaller,
)
oas32_types_unmarshaller = oas31_types_unmarshaller
oas30_format_unmarshallers = {
# string compatible
"date": format_date,
"date-time": parse_datetime,
"binary": bytes,
"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,
oas30_types_unmarshaller,
format_unmarshallers=oas30_format_unmarshallers,
)
oas30_read_schema_unmarshallers_factory = SchemaUnmarshallersFactory(
oas30_read_schema_validators_factory,
oas30_types_unmarshaller,
format_unmarshallers=oas30_format_unmarshallers,
)
oas31_schema_unmarshallers_factory = SchemaUnmarshallersFactory(
oas31_schema_validators_factory,
oas31_types_unmarshaller,
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
)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/datatypes.py 0000664 0000000 0000000 00000000256 15163577675 0031154 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Callable
from typing import Dict
FormatUnmarshaller = Callable[[Any], Any]
FormatUnmarshallersDict = Dict[str, FormatUnmarshaller]
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/exceptions.py 0000664 0000000 0000000 00000000717 15163577675 0031341 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from openapi_core.exceptions import OpenAPIError
class UnmarshalError(OpenAPIError):
"""Schema unmarshal operation error"""
class UnmarshallerError(UnmarshalError):
"""Unmarshaller error"""
@dataclass
class FormatterNotFoundError(UnmarshallerError):
"""Formatter not found to unmarshal"""
type_format: str
def __str__(self) -> str:
return f"Formatter not found for {self.type_format} format"
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/factories.py 0000664 0000000 0000000 00000006141 15163577675 0031134 0 ustar 00root root 0000000 0000000 import warnings
from typing import Optional
from jsonschema_path import SchemaPath
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.unmarshalling.schemas.exceptions import (
FormatterNotFoundError,
)
from openapi_core.unmarshalling.schemas.unmarshallers import (
FormatsUnmarshaller,
)
from openapi_core.unmarshalling.schemas.unmarshallers import SchemaUnmarshaller
from openapi_core.unmarshalling.schemas.unmarshallers import TypesUnmarshaller
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
class SchemaUnmarshallersFactory:
def __init__(
self,
schema_validators_factory: SchemaValidatorsFactory,
types_unmarshaller: TypesUnmarshaller,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
):
self.schema_validators_factory = schema_validators_factory
self.types_unmarshaller = types_unmarshaller
if format_unmarshallers is None:
format_unmarshallers = {}
self.format_unmarshallers = format_unmarshallers
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 / "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 / "format").read_str(None)
formats_unmarshaller = FormatsUnmarshaller(
format_unmarshallers or self.format_unmarshallers,
extra_format_unmarshallers,
)
# FIXME: don;t raise exception on unknown format
# See https://github.com/python-openapi/openapi-core/issues/515
if (
schema_format
and schema_format not in schema_validator
and schema_format not in formats_unmarshaller
):
raise FormatterNotFoundError(schema_format)
return SchemaUnmarshaller(
schema,
schema_validator,
self.types_unmarshaller,
formats_unmarshaller,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/unmarshallers.py 0000664 0000000 0000000 00000024430 15163577675 0032036 0 ustar 00root root 0000000 0000000 import logging
from typing import Any
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
from typing import Type
from typing import Union
from jsonschema_path import SchemaPath
from openapi_core.extensions.models.factories import ModelPathFactory
from openapi_core.schema.schemas import get_properties
from openapi_core.unmarshalling.schemas.datatypes import FormatUnmarshaller
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.validation.schemas.validators import SchemaValidator
log = logging.getLogger(__name__)
class PrimitiveUnmarshaller:
def __init__(
self,
schema: SchemaPath,
schema_validator: SchemaValidator,
schema_unmarshaller: "SchemaUnmarshaller",
) -> None:
self.schema = schema
self.schema_validator = schema_validator
self.schema_unmarshaller = schema_unmarshaller
def __call__(self, value: Any) -> Any:
return value
class ArrayUnmarshaller(PrimitiveUnmarshaller):
def __call__(self, value: Any) -> Optional[List[Any]]:
return list(map(self.items_unmarshaller.unmarshal, value))
@property
def items_unmarshaller(self) -> "SchemaUnmarshaller":
# sometimes we don't have any schema i.e. free-form objects
items_schema = self.schema.get("items", SchemaPath.from_dict({}))
return self.schema_unmarshaller.evolve(items_schema)
class ObjectUnmarshaller(PrimitiveUnmarshaller):
def __call__(self, value: Any) -> Any:
properties = self._unmarshal_properties(value)
fields: Iterable[str] = properties and properties.keys() or []
object_class = self.object_class_factory.create(self.schema, fields)
return object_class(**properties)
@property
def object_class_factory(self) -> ModelPathFactory:
return ModelPathFactory()
def evolve(self, schema: SchemaPath) -> "ObjectUnmarshaller":
cls = self.__class__
return cls(
schema,
self.schema_validator.evolve(schema),
self.schema_unmarshaller,
)
def _unmarshal_properties(
self, value: Any, schema_only: bool = False
) -> Any:
properties = {}
one_of_schema = self.schema_validator.get_one_of_schema(value)
if one_of_schema is not None:
one_of_properties = self.evolve(
one_of_schema
)._unmarshal_properties(value, schema_only=True)
properties.update(one_of_properties)
any_of_schemas = self.schema_validator.iter_any_of_schemas(value)
for any_of_schema in any_of_schemas:
any_of_properties = self.evolve(
any_of_schema
)._unmarshal_properties(value, schema_only=True)
properties.update(any_of_properties)
all_of_schemas = self.schema_validator.iter_all_of_schemas(value)
for all_of_schema in all_of_schemas:
all_of_properties = self.evolve(
all_of_schema
)._unmarshal_properties(value, schema_only=True)
properties.update(all_of_properties)
for prop_name, prop_schema in get_properties(self.schema).items():
try:
prop_value = value[prop_name]
except KeyError:
if "default" not in prop_schema:
continue
prop_value = (prop_schema / "default").read_value()
properties[prop_name] = self.schema_unmarshaller.evolve(
prop_schema
).unmarshal(prop_value)
if schema_only:
return properties
additional_properties = self.schema.get("additionalProperties", True)
if additional_properties is not False:
# free-form object
if additional_properties is True:
additional_prop_schema = SchemaPath.from_dict(
{"nullable": True}
)
# defined schema
else:
additional_prop_schema = self.schema / "additionalProperties"
additional_prop_unmarshaler = self.schema_unmarshaller.evolve(
additional_prop_schema
)
for prop_name, prop_value in value.items():
if prop_name in properties:
continue
properties[prop_name] = additional_prop_unmarshaler.unmarshal(
prop_value
)
return 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
)
return unmarshaller(value)
class AnyUnmarshaller(MultiTypeUnmarshaller):
pass
class TypesUnmarshaller:
unmarshallers: Mapping[str, Type[PrimitiveUnmarshaller]] = {}
multi: Optional[Type[PrimitiveUnmarshaller]] = None
def __init__(
self,
unmarshallers: Mapping[str, Type[PrimitiveUnmarshaller]],
default: Type[PrimitiveUnmarshaller],
multi: Optional[Type[PrimitiveUnmarshaller]] = None,
):
self.unmarshallers = unmarshallers
self.default = default
self.multi = multi
def get_types(self) -> List[str]:
return list(self.unmarshallers.keys())
def get_unmarshaller_cls(
self,
schema_type: Optional[Union[Iterable[str], str]],
) -> Type["PrimitiveUnmarshaller"]:
if schema_type is None:
return self.default
if isinstance(schema_type, Iterable) and not isinstance(
schema_type, str
):
if self.multi is None:
raise TypeError("Unmarshaller does not accept multiple types")
return self.multi
return self.unmarshallers[schema_type]
class FormatsUnmarshaller:
def __init__(
self,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
):
if format_unmarshallers is None:
format_unmarshallers = {}
self.format_unmarshallers = format_unmarshallers
if extra_format_unmarshallers is None:
extra_format_unmarshallers = {}
self.extra_format_unmarshallers = extra_format_unmarshallers
def unmarshal(self, schema_format: str, value: Any) -> Any:
format_unmarshaller = self.get_unmarshaller(schema_format)
if format_unmarshaller is None:
return value
try:
return format_unmarshaller(value)
except (AttributeError, ValueError, TypeError):
return value
def get_unmarshaller(
self, schema_format: str
) -> Optional[FormatUnmarshaller]:
if schema_format in self.extra_format_unmarshallers:
return self.extra_format_unmarshallers[schema_format]
if schema_format in self.format_unmarshallers:
return self.format_unmarshallers[schema_format]
return None
def __contains__(self, schema_format: str) -> bool:
format_unmarshallers_dicts: List[Mapping[str, Any]] = [
self.extra_format_unmarshallers,
self.format_unmarshallers,
]
for content in format_unmarshallers_dicts:
if schema_format in content:
return True
return False
class SchemaUnmarshaller:
def __init__(
self,
schema: SchemaPath,
schema_validator: SchemaValidator,
types_unmarshaller: TypesUnmarshaller,
formats_unmarshaller: FormatsUnmarshaller,
):
self.schema = schema
self.schema_validator = schema_validator
self.types_unmarshaller = types_unmarshaller
self.formats_unmarshaller = formats_unmarshaller
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 / "nullable").read_bool(
default=False
):
return value
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
# ignore incompatible formats
if not (
isinstance(value, str)
or
# Workaround allows bytes for binary and byte formats
(isinstance(value, bytes) and schema_format in ["binary", "byte"])
):
return typed
format_unmarshaller = self.get_format_unmarshaller(schema_format)
if format_unmarshaller is None:
return typed
try:
return format_unmarshaller(typed)
except (AttributeError, ValueError, TypeError):
return typed
def get_type_unmarshaller(
self,
schema_type: Optional[Union[Iterable[str], str]],
) -> PrimitiveUnmarshaller:
klass = self.types_unmarshaller.get_unmarshaller_cls(schema_type)
return klass(
self.schema,
self.schema_validator,
self,
)
def get_format_unmarshaller(
self,
schema_format: str,
) -> Optional[FormatUnmarshaller]:
return self.formats_unmarshaller.get_unmarshaller(schema_format)
def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller":
cls = self.__class__
return cls(
schema,
self.schema_validator.evolve(schema),
self.types_unmarshaller,
self.formats_unmarshaller,
)
def find_format(self, value: Any) -> Optional[str]:
for schema in self.schema_validator.iter_valid_schemas(value):
schema_validator = self.schema_validator.evolve(schema)
primitive_type = schema_validator.get_primitive_type(value)
if primitive_type != "string":
continue
if "format" in schema:
return (schema / "format").read_str()
return None
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/schemas/util.py 0000664 0000000 0000000 00000001211 15163577675 0030123 0 ustar 00root root 0000000 0000000 """OpenAPI core schemas util module"""
from base64 import b64decode
from datetime import date
from datetime import datetime
from typing import Any
from typing import Union
from uuid import UUID
def format_date(value: str) -> date:
return datetime.strptime(value, "%Y-%m-%d").date()
def format_uuid(value: Any) -> UUID:
if isinstance(value, UUID):
return value
return UUID(value)
def format_byte(value: str, encoding: str = "utf8") -> str:
return str(b64decode(value), encoding)
def format_number(value: str) -> Union[int, float]:
if isinstance(value, (int, float)):
return value
return float(value)
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/typing.py 0000664 0000000 0000000 00000000716 15163577675 0027046 0 ustar 00root root 0000000 0000000 from typing import Awaitable
from typing import Callable
from typing import Iterable
from openapi_core.typing import ResponseType
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
ErrorsHandlerCallable = Callable[[Iterable[Exception]], ResponseType]
ValidRequestHandlerCallable = Callable[[RequestUnmarshalResult], ResponseType]
AsyncValidRequestHandlerCallable = Callable[
[RequestUnmarshalResult], Awaitable[ResponseType]
]
python-openapi-openapi-core-d6cdb4f/openapi_core/unmarshalling/unmarshallers.py 0000664 0000000 0000000 00000012315 15163577675 0030412 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Mapping
from typing import Optional
from typing import Tuple
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.unmarshalling.schemas.datatypes import (
FormatUnmarshallersDict,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
from openapi_core.validation.validators import BaseValidator
class BaseUnmarshaller(BaseValidator):
schema_unmarshallers_factory: SchemaUnmarshallersFactory = NotImplemented
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
forbid_unspecified_additional_properties: bool = False,
enforce_properties_required: bool = False,
schema_unmarshallers_factory: Optional[
SchemaUnmarshallersFactory
] = None,
format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None,
):
if schema_validators_factory is None and schema_unmarshallers_factory:
schema_validators_factory = (
schema_unmarshallers_factory.schema_validators_factory
)
BaseValidator.__init__(
self,
spec,
base_url=base_url,
style_deserializers_factory=style_deserializers_factory,
media_type_deserializers_factory=media_type_deserializers_factory,
schema_casters_factory=schema_casters_factory,
schema_validators_factory=schema_validators_factory,
path_finder_cls=path_finder_cls,
spec_validator_cls=spec_validator_cls,
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
)
if self.schema_unmarshallers_factory is NotImplemented:
raise NotImplementedError(
"schema_unmarshallers_factory is not assigned"
)
self.format_unmarshallers = format_unmarshallers
self.extra_format_unmarshallers = extra_format_unmarshallers
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,
)
return unmarshaller.unmarshal(value)
def _get_param_or_header_and_schema(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Tuple[Any, Optional[SchemaPath]]:
casted, schema = super()._get_param_or_header_and_schema(
param_or_header, location, name=name
)
if schema is None:
return casted, None
return self._unmarshal_schema(schema, casted), schema
def _get_content_and_schema(
self, raw: Any, content: SchemaPath, mimetype: Optional[str] = None
) -> Tuple[Any, Optional[SchemaPath]]:
casted, schema = super()._get_content_and_schema(
raw, content, mimetype
)
if schema is None:
return casted, None
return self._unmarshal_schema(schema, casted), schema
python-openapi-openapi-core-d6cdb4f/openapi_core/util.py 0000664 0000000 0000000 00000001256 15163577675 0023645 0 ustar 00root root 0000000 0000000 """OpenAPI core util module"""
from itertools import chain
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 BOOLEAN_TRUE_VALUES:
return True
elif val in BOOLEAN_FALSE_VALUES:
return False
else:
raise ValueError(f"invalid truth value {val!r}")
return bool(val)
def chainiters(*lists: Iterable[Any]) -> Iterable[Any]:
iters = map(lambda l: l and iter(l) or [], lists)
return chain(*iters)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/ 0000775 0000000 0000000 00000000000 15163577675 0024444 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/validation/__init__.py 0000664 0000000 0000000 00000000045 15163577675 0026554 0 ustar 00root root 0000000 0000000 """OpenAPI core validation module"""
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/configurations.py 0000664 0000000 0000000 00000005505 15163577675 0030055 0 ustar 00root root 0000000 0000000 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.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.security import security_provider_factory
from openapi_core.security.factories import SecurityProviderFactory
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@dataclass
class ValidatorConfig:
"""Validator configuration dataclass.
Attributes:
server_base_url
Server base URI.
path_finder_cls
Path finder class.
webhook_path_finder_cls
Webhook path finder class.
style_deserializers_factory
Style deserializers factory.
media_type_deserializers_factory
Media type deserializers factory.
schema_casters_factory
Schema casters factory.
schema_validators_factory
Schema validators factory.
extra_format_validators
Extra format validators.
extra_media_type_deserializers
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: Optional[StyleDeserializersFactory] = None
media_type_deserializers_factory: Optional[
MediaTypeDeserializersFactory
] = None
schema_casters_factory: Optional[SchemaCastersFactory] = None
schema_validators_factory: Optional[SchemaValidatorsFactory] = None
extra_format_validators: Optional[FormatValidatorsDict] = None
extra_media_type_deserializers: Optional[MediaTypeDeserializersDict] = None
security_provider_factory: SecurityProviderFactory = (
security_provider_factory
)
additional_properties_default_policy: Literal["allow", "forbid"] = "allow"
response_properties_default_policy: Literal["optional", "required"] = (
"optional"
)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/decorators.py 0000664 0000000 0000000 00000003330 15163577675 0027162 0 ustar 00root root 0000000 0000000 from functools import wraps
from inspect import signature
from typing import Any
from typing import Callable
from typing import Optional
from typing import Type
from openapi_core.exceptions import OpenAPIError
from openapi_core.validation.schemas.exceptions import ValidateError
OpenAPIErrorType = Type[OpenAPIError]
class ValidationErrorWrapper:
def __init__(
self,
err_cls: OpenAPIErrorType,
err_validate_cls: Optional[OpenAPIErrorType] = None,
err_cls_init: Optional[str] = None,
**err_cls_kw: Any
):
self.err_cls = err_cls
self.err_validate_cls = err_validate_cls or err_cls
self.err_cls_init = err_cls_init
self.err_cls_kw = err_cls_kw
def __call__(self, f: Callable[..., Any]) -> Callable[..., Any]:
@wraps(f)
def wrapper(*args: Any, **kwds: Any) -> Any:
try:
return f(*args, **kwds)
except ValidateError as exc:
self._raise_error(exc, self.err_validate_cls, f, *args, **kwds)
except OpenAPIError as exc:
self._raise_error(exc, self.err_cls, f, *args, **kwds)
return wrapper
def _raise_error(
self,
exc: OpenAPIError,
cls: OpenAPIErrorType,
f: Callable[..., Any],
*args: Any,
**kwds: Any
) -> None:
if isinstance(exc, self.err_cls):
raise
sig = signature(f)
ba = sig.bind(*args, **kwds)
kw = {
name: ba.arguments[func_kw]
for name, func_kw in self.err_cls_kw.items()
}
init = cls
if self.err_cls_init is not None:
init = getattr(cls, self.err_cls_init)
raise init(**kw) from exc
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/exceptions.py 0000664 0000000 0000000 00000002517 15163577675 0027204 0 ustar 00root root 0000000 0000000 """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__}"
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/integrations.py 0000664 0000000 0000000 00000002237 15163577675 0027530 0 ustar 00root root 0000000 0000000 """OpenAPI core unmarshalling processors module"""
from typing import Generic
from openapi_core.app import OpenAPI
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.typing import RequestType
from openapi_core.typing import ResponseType
class ValidationIntegration(Generic[RequestType, ResponseType]):
def __init__(
self,
openapi: OpenAPI,
):
self.openapi = openapi
def get_openapi_request(self, request: RequestType) -> Request:
raise NotImplementedError
def get_openapi_response(self, response: ResponseType) -> Response:
raise NotImplementedError
def validate_request(self, request: RequestType) -> None:
openapi_request = self.get_openapi_request(request)
self.openapi.validate_request(
openapi_request,
)
def validate_response(
self,
request: RequestType,
response: ResponseType,
) -> None:
openapi_request = self.get_openapi_request(request)
openapi_response = self.get_openapi_response(response)
self.openapi.validate_response(openapi_request, openapi_response)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/processors.py 0000664 0000000 0000000 00000001031 15163577675 0027213 0 ustar 00root root 0000000 0000000 """OpenAPI core validation processors module"""
from openapi_core.typing import RequestType
from openapi_core.typing import ResponseType
from openapi_core.validation.integrations import ValidationIntegration
class ValidationProcessor(ValidationIntegration[RequestType, ResponseType]):
def handle_request(self, request: RequestType) -> None:
self.validate_request(request)
def handle_response(
self, request: RequestType, response: ResponseType
) -> None:
self.validate_response(request, response)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/ 0000775 0000000 0000000 00000000000 15163577675 0026134 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/__init__.py 0000664 0000000 0000000 00000006654 15163577675 0030260 0 ustar 00root root 0000000 0000000 """OpenAPI core validation request module"""
from typing import Mapping
from openapi_spec_validator.versions import consts as versions
from openapi_spec_validator.versions.datatypes import SpecVersion
from openapi_core.validation.request.types import RequestValidatorType
from openapi_core.validation.request.types import WebhookRequestValidatorType
from openapi_core.validation.request.validators import V30RequestBodyValidator
from openapi_core.validation.request.validators import (
V30RequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V30RequestSecurityValidator,
)
from openapi_core.validation.request.validators import V30RequestValidator
from openapi_core.validation.request.validators import V31RequestBodyValidator
from openapi_core.validation.request.validators import (
V31RequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V31RequestSecurityValidator,
)
from openapi_core.validation.request.validators import V31RequestValidator
from openapi_core.validation.request.validators import (
V31WebhookRequestBodyValidator,
)
from openapi_core.validation.request.validators import (
V31WebhookRequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V31WebhookRequestSecurityValidator,
)
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",
"WEBHOOK_VALIDATORS",
"V30RequestBodyValidator",
"V30RequestParametersValidator",
"V30RequestSecurityValidator",
"V30RequestValidator",
"V31RequestBodyValidator",
"V31RequestParametersValidator",
"V31RequestSecurityValidator",
"V31RequestValidator",
"V31WebhookRequestBodyValidator",
"V31WebhookRequestParametersValidator",
"V31WebhookRequestSecurityValidator",
"V31WebhookRequestValidator",
"V32RequestBodyValidator",
"V32RequestParametersValidator",
"V32RequestSecurityValidator",
"V32RequestValidator",
"V32WebhookRequestBodyValidator",
"V32WebhookRequestParametersValidator",
"V32WebhookRequestSecurityValidator",
"V32WebhookRequestValidator",
"V3RequestValidator",
"V3WebhookRequestValidator",
]
# versions mapping
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 = V32RequestValidator
V3WebhookRequestValidator = V32WebhookRequestValidator
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/datatypes.py 0000664 0000000 0000000 00000000266 15163577675 0030510 0 ustar 00root root 0000000 0000000 from openapi_core.datatypes import Parameters
from openapi_core.datatypes import RequestParameters
# Backward compatibility
__all__ = [
"Parameters",
"RequestParameters",
]
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/exceptions.py 0000664 0000000 0000000 00000004466 15163577675 0030701 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Iterable
from jsonschema_path import SchemaPath
from openapi_core.datatypes import Parameters
from openapi_core.exceptions import OpenAPIError
from openapi_core.validation.exceptions import ValidationError
from openapi_core.validation.schemas.exceptions import ValidateError
@dataclass
class ParametersError(Exception):
parameters: Parameters
errors: Iterable[OpenAPIError]
class RequestValidationError(ValidationError):
"""Request validation error"""
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"
class InvalidRequestBody(RequestBodyValidationError, ValidateError):
"""Invalid request body"""
class MissingRequestBodyError(RequestBodyValidationError):
"""Missing request body error"""
class MissingRequestBody(MissingRequestBodyError):
def __str__(self) -> str:
return "Missing request body"
class MissingRequiredRequestBody(MissingRequestBodyError):
def __str__(self) -> str:
return "Missing required request body"
@dataclass
class ParameterValidationError(RequestValidationError):
name: str
location: str
@classmethod
def from_spec(cls, spec: SchemaPath) -> "ParameterValidationError":
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}"
class InvalidParameter(ParameterValidationError, ValidateError):
def __str__(self) -> str:
return f"Invalid {self.location} parameter: {self.name}"
class MissingParameterError(ParameterValidationError):
"""Missing parameter error"""
class MissingParameter(MissingParameterError):
def __str__(self) -> str:
return f"Missing {self.location} parameter: {self.name}"
class MissingRequiredParameter(MissingParameterError):
def __str__(self) -> str:
return f"Missing required {self.location} parameter: {self.name}"
class SecurityValidationError(RequestValidationError):
pass
class InvalidSecurity(SecurityValidationError, ValidateError):
"""Invalid security"""
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/protocols.py 0000664 0000000 0000000 00000006732 15163577675 0030542 0 ustar 00root root 0000000 0000000 """OpenAPI core validation request protocols module"""
from typing import Iterator
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
from openapi_core.security import security_provider_factory
from openapi_core.security.factories import SecurityProviderFactory
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@runtime_checkable
class RequestValidator(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
): ...
def iter_errors(
self,
request: Request,
) -> Iterator[Exception]: ...
def validate(
self,
request: Request,
) -> None: ...
@runtime_checkable
class WebhookRequestValidator(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
): ...
def iter_errors(
self,
request: WebhookRequest,
) -> Iterator[Exception]: ...
def validate(
self,
request: WebhookRequest,
) -> None: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/types.py 0000664 0000000 0000000 00000000613 15163577675 0027652 0 ustar 00root root 0000000 0000000 from typing import Type
from typing import Union
from openapi_core.validation.request.protocols import RequestValidator
from openapi_core.validation.request.protocols import WebhookRequestValidator
RequestValidatorType = Type[RequestValidator]
WebhookRequestValidatorType = Type[WebhookRequestValidator]
AnyRequestValidatorType = Union[
RequestValidatorType, WebhookRequestValidatorType
]
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/request/validators.py 0000664 0000000 0000000 00000047131 15163577675 0030664 0 ustar 00root root 0000000 0000000 """OpenAPI core validation request validators module"""
import warnings
from typing import Any
from typing import Dict
from typing import Iterator
from typing import Optional
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.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import BaseRequest
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
from openapi_core.security import security_provider_factory
from openapi_core.security.exceptions import SecurityProviderError
from openapi_core.security.factories import SecurityProviderFactory
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.util import chainiters
from openapi_core.validation.decorators import ValidationErrorWrapper
from openapi_core.validation.request.exceptions import InvalidParameter
from openapi_core.validation.request.exceptions import InvalidRequestBody
from openapi_core.validation.request.exceptions import InvalidSecurity
from openapi_core.validation.request.exceptions import MissingParameter
from openapi_core.validation.request.exceptions import MissingRequestBody
from openapi_core.validation.request.exceptions import MissingRequiredParameter
from openapi_core.validation.request.exceptions import (
MissingRequiredRequestBody,
)
from openapi_core.validation.request.exceptions import ParametersError
from openapi_core.validation.request.exceptions import ParameterValidationError
from openapi_core.validation.request.exceptions import (
RequestBodyValidationError,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
from openapi_core.validation.schemas import (
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
from openapi_core.validation.validators import BaseValidator
from openapi_core.validation.validators import BaseWebhookValidator
class BaseRequestValidator(BaseValidator):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
security_provider_factory: SecurityProviderFactory = security_provider_factory,
forbid_unspecified_additional_properties: bool = False,
):
BaseValidator.__init__(
self,
spec,
base_url=base_url,
style_deserializers_factory=style_deserializers_factory,
media_type_deserializers_factory=media_type_deserializers_factory,
schema_casters_factory=schema_casters_factory,
schema_validators_factory=schema_validators_factory,
path_finder_cls=path_finder_cls,
spec_validator_cls=spec_validator_cls,
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
def _iter_errors(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> Iterator[Exception]:
try:
self._get_security(request.parameters, operation)
# don't process if security errors
except SecurityValidationError as exc:
yield exc
return
try:
self._get_parameters(request.parameters, operation, path)
except ParametersError as exc:
yield from exc.errors
try:
self._get_body(request.body, request.content_type, operation)
except MissingRequestBody:
pass
except RequestBodyValidationError as exc:
yield exc
def _iter_body_errors(
self, request: BaseRequest, operation: SchemaPath
) -> Iterator[Exception]:
try:
self._get_body(request.body, request.content_type, operation)
except RequestBodyValidationError as exc:
yield exc
def _iter_parameters_errors(
self, request: BaseRequest, operation: SchemaPath, path: SchemaPath
) -> Iterator[Exception]:
try:
self._get_parameters(request.parameters, operation, path)
except ParametersError as exc:
yield from exc.errors
def _iter_security_errors(
self, request: BaseRequest, operation: SchemaPath
) -> Iterator[Exception]:
try:
self._get_security(request.parameters, operation)
except SecurityValidationError as exc:
yield exc
def _get_parameters(
self,
parameters: RequestParameters,
operation: SchemaPath,
path: SchemaPath,
) -> 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").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
continue
seen.add((param_name, param_location))
try:
value = self._get_parameter(parameters, param)
except MissingParameter:
continue
except ParameterValidationError as exc:
errors.append(exc)
continue
else:
location = getattr(validated, param_location)
location[param_name] = value
if errors:
raise ParametersError(errors=errors, parameters=validated)
return validated
@ValidationErrorWrapper(
ParameterValidationError,
InvalidParameter,
"from_spec",
spec="param",
)
def _get_parameter(
self, parameters: RequestParameters, param: SchemaPath
) -> Any:
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,
)
try:
value, _ = self._get_param_or_header_and_schema(param, location)
except KeyError:
if (param / "required").read_bool(default=False):
raise MissingRequiredParameter(name, param_location)
raise MissingParameter(name, param_location)
else:
return value
@ValidationErrorWrapper(SecurityValidationError, InvalidSecurity)
def _get_security(
self, parameters: RequestParameters, operation: SchemaPath
) -> Optional[Dict[str, str]]:
security = None
if "security" in self.spec:
security = self.spec / "security"
if "security" in operation:
security = operation / "security"
if not security:
return {}
schemes = []
for security_requirement in security:
try:
scheme_names = list(security_requirement.str_keys())
schemes.append(scheme_names)
return {
scheme_name: self._get_security_value(
parameters, scheme_name
)
for scheme_name in scheme_names
}
except SecurityProviderError:
continue
raise SecurityNotFound(schemes)
def _get_security_value(
self, parameters: RequestParameters, scheme_name: str
) -> Any:
security_schemes = self.spec / "components#securitySchemes"
if scheme_name not in security_schemes:
return
scheme = security_schemes[scheme_name]
security_provider = self.security_provider_factory.create(scheme)
return security_provider(parameters)
@ValidationErrorWrapper(RequestBodyValidationError, InvalidRequestBody)
def _get_body(
self, body: Optional[bytes], mimetype: str, operation: SchemaPath
) -> Any:
if "requestBody" not in operation:
return None
# TODO: implement required flag checking
request_body = operation / "requestBody"
content = request_body / "content"
raw_body = self._get_body_value(body, request_body)
value, _ = self._get_content_and_schema(raw_body, content, mimetype)
return value
def _get_body_value(
self, body: Optional[bytes], request_body: SchemaPath
) -> bytes:
if not body:
if (request_body / "required").read_bool(default=False):
raise MissingRequiredRequestBody
raise MissingRequestBody
return body
class BaseAPICallRequestValidator(BaseRequestValidator, BaseAPICallValidator):
def iter_errors(self, request: Request) -> Iterator[Exception]:
raise NotImplementedError
def validate(self, request: Request) -> None:
for err in self.iter_errors(request):
raise err
class BaseWebhookRequestValidator(BaseRequestValidator, BaseWebhookValidator):
def iter_errors(self, request: WebhookRequest) -> Iterator[Exception]:
raise NotImplementedError
def validate(self, request: WebhookRequest) -> None:
for err in self.iter_errors(request):
raise err
class APICallRequestBodyValidator(BaseAPICallRequestValidator):
def iter_errors(self, request: Request) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
except PathError as exc:
yield exc
return
yield from self._iter_body_errors(request, operation)
class APICallRequestParametersValidator(BaseAPICallRequestValidator):
def iter_errors(self, request: Request) -> Iterator[Exception]:
try:
path, operation, _, path_result, _ = self._find_path(request)
except PathError as exc:
yield exc
return
request.parameters.path = (
request.parameters.path or path_result.variables
)
yield from self._iter_parameters_errors(request, operation, path)
class APICallRequestSecurityValidator(BaseAPICallRequestValidator):
def iter_errors(self, request: Request) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
except PathError as exc:
yield exc
return
yield from self._iter_security_errors(request, operation)
class APICallRequestValidator(BaseAPICallRequestValidator):
def iter_errors(self, request: Request) -> Iterator[Exception]:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
request.parameters.path = (
request.parameters.path or path_result.variables
)
yield from self._iter_errors(request, operation, path)
class WebhookRequestValidator(BaseWebhookRequestValidator):
def iter_errors(self, request: WebhookRequest) -> Iterator[Exception]:
try:
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
request.parameters.path = (
request.parameters.path or path_result.variables
)
yield from self._iter_errors(request, operation, path)
class WebhookRequestBodyValidator(BaseWebhookRequestValidator):
def iter_errors(self, request: WebhookRequest) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
except PathError as exc:
yield exc
return
yield from self._iter_body_errors(request, operation)
class WebhookRequestParametersValidator(BaseWebhookRequestValidator):
def iter_errors(self, request: WebhookRequest) -> Iterator[Exception]:
try:
path, operation, _, path_result, _ = self._find_path(request)
except PathError as exc:
yield exc
return
request.parameters.path = (
request.parameters.path or path_result.variables
)
yield from self._iter_parameters_errors(request, operation, path)
class WebhookRequestSecurityValidator(BaseWebhookRequestValidator):
def iter_errors(self, request: WebhookRequest) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
except PathError as exc:
yield exc
return
yield from self._iter_security_errors(request, operation)
class V30RequestBodyValidator(APICallRequestBodyValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_write_schema_casters_factory
schema_validators_factory = oas30_write_schema_validators_factory
class V30RequestParametersValidator(APICallRequestParametersValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_write_schema_casters_factory
schema_validators_factory = oas30_write_schema_validators_factory
class V30RequestSecurityValidator(APICallRequestSecurityValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_write_schema_casters_factory
schema_validators_factory = oas30_write_schema_validators_factory
class V30RequestValidator(APICallRequestValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_write_schema_casters_factory
schema_validators_factory = oas30_write_schema_validators_factory
class V31RequestBodyValidator(APICallRequestBodyValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31RequestParametersValidator(APICallRequestParametersValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31RequestSecurityValidator(APICallRequestSecurityValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31RequestValidator(APICallRequestValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31WebhookRequestBodyValidator(WebhookRequestBodyValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31WebhookRequestParametersValidator(WebhookRequestParametersValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31WebhookRequestSecurityValidator(WebhookRequestSecurityValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
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
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/ 0000775 0000000 0000000 00000000000 15163577675 0026302 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/__init__.py 0000664 0000000 0000000 00000005521 15163577675 0030416 0 ustar 00root root 0000000 0000000 """OpenAPI core validation response module"""
from typing import Mapping
from openapi_spec_validator.versions import consts as versions
from openapi_spec_validator.versions.datatypes import SpecVersion
from openapi_core.validation.response.types import ResponseValidatorType
from openapi_core.validation.response.types import WebhookResponseValidatorType
from openapi_core.validation.response.validators import (
V30ResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V30ResponseHeadersValidator,
)
from openapi_core.validation.response.validators import V30ResponseValidator
from openapi_core.validation.response.validators import (
V31ResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V31ResponseHeadersValidator,
)
from openapi_core.validation.response.validators import V31ResponseValidator
from openapi_core.validation.response.validators import (
V31WebhookResponseDataValidator,
)
from openapi_core.validation.response.validators import (
V31WebhookResponseHeadersValidator,
)
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",
"WEBHOOK_VALIDATORS",
"V30ResponseDataValidator",
"V30ResponseHeadersValidator",
"V30ResponseValidator",
"V31ResponseDataValidator",
"V31ResponseHeadersValidator",
"V31ResponseValidator",
"V31WebhookResponseDataValidator",
"V31WebhookResponseHeadersValidator",
"V31WebhookResponseValidator",
"V32ResponseDataValidator",
"V32ResponseHeadersValidator",
"V32ResponseValidator",
"V32WebhookResponseDataValidator",
"V32WebhookResponseHeadersValidator",
"V32WebhookResponseValidator",
"V3ResponseValidator",
"V3WebhookResponseValidator",
]
# versions mapping
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 = V32ResponseValidator
V3WebhookResponseValidator = V32WebhookResponseValidator
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/exceptions.py 0000664 0000000 0000000 00000002364 15163577675 0031042 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from typing import Any
from typing import Dict
from typing import Iterable
from openapi_core.exceptions import OpenAPIError
from openapi_core.validation.exceptions import ValidationError
from openapi_core.validation.schemas.exceptions import ValidateError
@dataclass
class HeadersError(Exception):
headers: Dict[str, Any]
context: Iterable[OpenAPIError]
class ResponseValidationError(ValidationError):
"""Response error"""
class DataValidationError(ResponseValidationError):
"""Data error"""
class InvalidData(DataValidationError, ValidateError):
"""Invalid data"""
class MissingData(DataValidationError):
def __str__(self) -> str:
return "Missing response data"
@dataclass
class HeaderValidationError(ResponseValidationError):
name: str
class InvalidHeader(HeaderValidationError, ValidateError):
"""Invalid header"""
class MissingHeaderError(HeaderValidationError):
"""Missing header error"""
class MissingHeader(MissingHeaderError):
def __str__(self) -> str:
return f"Missing header (without default value): {self.name}"
class MissingRequiredHeader(MissingHeaderError):
def __str__(self) -> str:
return f"Missing required header: {self.name}"
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/protocols.py 0000664 0000000 0000000 00000006657 15163577675 0030716 0 ustar 00root root 0000000 0000000 """OpenAPI core validation response protocols module"""
from typing import Iterator
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from jsonschema_path import SchemaPath
from openapi_spec_validator.validation.types import SpecValidatorType
from openapi_core.casting.schemas.factories import SchemaCastersFactory
from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import Request
from openapi_core.protocols import Response
from openapi_core.protocols import WebhookRequest
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@runtime_checkable
class ResponseValidator(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
forbid_unspecified_additional_properties: bool = False,
enforce_properties_required: bool = False,
): ...
def iter_errors(
self,
request: Request,
response: Response,
) -> Iterator[Exception]: ...
def validate(
self,
request: Request,
response: Response,
) -> None: ...
@runtime_checkable
class WebhookResponseValidator(Protocol):
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
extra_media_type_deserializers: Optional[
MediaTypeDeserializersDict
] = None,
forbid_unspecified_additional_properties: bool = False,
enforce_properties_required: bool = False,
): ...
def iter_errors(
self,
request: WebhookRequest,
response: Response,
) -> Iterator[Exception]: ...
def validate(
self,
request: WebhookRequest,
response: Response,
) -> None: ...
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/types.py 0000664 0000000 0000000 00000000626 15163577675 0030024 0 ustar 00root root 0000000 0000000 from typing import Type
from typing import Union
from openapi_core.validation.response.protocols import ResponseValidator
from openapi_core.validation.response.protocols import WebhookResponseValidator
ResponseValidatorType = Type[ResponseValidator]
WebhookResponseValidatorType = Type[WebhookResponseValidator]
AnyResponseValidatorType = Union[
ResponseValidatorType, WebhookResponseValidatorType
]
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/response/validators.py 0000664 0000000 0000000 00000034142 15163577675 0031030 0 ustar 00root root 0000000 0000000 """OpenAPI core validation response validators module"""
import warnings
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
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
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.templating.responses.exceptions import ResponseFinderError
from openapi_core.validation.decorators import ValidationErrorWrapper
from openapi_core.validation.exceptions import ValidationError
from openapi_core.validation.response.exceptions import DataValidationError
from openapi_core.validation.response.exceptions import HeadersError
from openapi_core.validation.response.exceptions import HeaderValidationError
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 MissingHeader
from openapi_core.validation.response.exceptions import MissingRequiredHeader
from openapi_core.validation.schemas import (
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
class BaseResponseValidator(BaseValidator):
def _iter_errors(
self,
status_code: int,
data: Optional[bytes],
headers: HeadersType,
mimetype: str,
operation: SchemaPath,
) -> Iterator[Exception]:
try:
operation_response = self._find_operation_response(
status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
yield exc
return
try:
self._get_data(data, mimetype, operation_response)
except DataValidationError as exc:
yield exc
try:
self._get_headers(headers, operation_response)
except HeadersError as exc:
yield from exc.context
def _iter_data_errors(
self,
status_code: int,
data: Optional[bytes],
mimetype: str,
operation: SchemaPath,
) -> Iterator[Exception]:
try:
operation_response = self._find_operation_response(
status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
yield exc
return
try:
self._get_data(data, mimetype, operation_response)
except DataValidationError as exc:
yield exc
def _iter_headers_errors(
self,
status_code: int,
headers: HeadersType,
operation: SchemaPath,
) -> Iterator[Exception]:
try:
operation_response = self._find_operation_response(
status_code, operation
)
# don't process if operation errors
except ResponseFinderError as exc:
yield exc
return
try:
self._get_headers(headers, operation_response)
except HeadersError as exc:
yield from exc.context
def _find_operation_response(
self,
status_code: int,
operation: SchemaPath,
) -> SchemaPath:
from openapi_core.templating.responses.finders import ResponseFinder
finder = ResponseFinder(operation / "responses")
return finder.find(str(status_code))
@ValidationErrorWrapper(DataValidationError, InvalidData)
def _get_data(
self,
data: Optional[bytes],
mimetype: str,
operation_response: SchemaPath,
) -> Any:
if "content" not in operation_response:
return None
content = operation_response / "content"
raw_data = self._get_data_value(data)
value, _ = self._get_content_and_schema(raw_data, content, mimetype)
return value
def _get_data_value(self, data: Optional[bytes]) -> bytes:
if not data:
raise MissingData
return data
def _get_headers(
self, headers: HeadersType, operation_response: SchemaPath
) -> Dict[str, Any]:
if "headers" not in operation_response:
return {}
errors: List[OpenAPIError] = []
validated: Dict[str, Any] = {}
for name, header in (operation_response / "headers").str_items():
# ignore Content-Type header
if name.lower() == "content-type":
continue
try:
value = self._get_header(headers, name, header)
except MissingHeader:
continue
except ValidationError as exc:
errors.append(exc)
continue
else:
validated[name] = value
if errors:
raise HeadersError(context=iter(errors), headers=validated)
return validated
@ValidationErrorWrapper(HeaderValidationError, InvalidHeader, name="name")
def _get_header(
self, headers: Mapping[str, Any], name: str, header: SchemaPath
) -> Any:
deprecated = (header / "deprecated").read_bool(default=False)
if deprecated and name in headers:
warnings.warn(
f"{name} header is deprecated",
DeprecationWarning,
)
try:
value, _ = self._get_param_or_header_and_schema(
header, headers, name=name
)
except KeyError:
required = (header / "required").read_bool(default=False)
if required:
raise MissingRequiredHeader(name)
raise MissingHeader(name)
else:
return value
class BaseAPICallResponseValidator(
BaseResponseValidator, BaseAPICallValidator
):
def iter_errors(
self,
request: Request,
response: Response,
) -> Iterator[Exception]:
raise NotImplementedError
def validate(
self,
request: Request,
response: Response,
) -> None:
for err in self.iter_errors(request, response):
raise err
class BaseWebhookResponseValidator(
BaseResponseValidator, BaseWebhookValidator
):
def iter_errors(
self,
request: WebhookRequest,
response: Response,
) -> Iterator[Exception]:
raise NotImplementedError
def validate(
self,
request: WebhookRequest,
response: Response,
) -> None:
for err in self.iter_errors(request, response):
raise err
class APICallResponseDataValidator(BaseAPICallResponseValidator):
def iter_errors(
self,
request: Request,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_data_errors(
response.status_code,
response.data,
response.content_type,
operation,
)
class APICallResponseHeadersValidator(BaseAPICallResponseValidator):
def iter_errors(
self,
request: Request,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_headers_errors(
response.status_code, response.headers, operation
)
class APICallResponseValidator(BaseAPICallResponseValidator):
def iter_errors(
self,
request: Request,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_errors(
response.status_code,
response.data,
response.headers,
response.content_type,
operation,
)
class WebhookResponseDataValidator(BaseWebhookResponseValidator):
def iter_errors(
self,
request: WebhookRequest,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_data_errors(
response.status_code,
response.data,
response.content_type,
operation,
)
class WebhookResponseHeadersValidator(BaseWebhookResponseValidator):
def iter_errors(
self,
request: WebhookRequest,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_headers_errors(
response.status_code, response.headers, operation
)
class WebhookResponseValidator(BaseWebhookResponseValidator):
def iter_errors(
self,
request: WebhookRequest,
response: Response,
) -> Iterator[Exception]:
try:
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
yield exc
return
yield from self._iter_errors(
response.status_code,
response.data,
response.headers,
response.content_type,
operation,
)
class V30ResponseDataValidator(APICallResponseDataValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_read_schema_casters_factory
schema_validators_factory = oas30_read_schema_validators_factory
class V30ResponseHeadersValidator(APICallResponseHeadersValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_read_schema_casters_factory
schema_validators_factory = oas30_read_schema_validators_factory
class V30ResponseValidator(APICallResponseValidator):
spec_validator_cls = OpenAPIV30SpecValidator
schema_casters_factory = oas30_read_schema_casters_factory
schema_validators_factory = oas30_read_schema_validators_factory
class V31ResponseDataValidator(APICallResponseDataValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31ResponseHeadersValidator(APICallResponseHeadersValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31ResponseValidator(APICallResponseValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31WebhookResponseDataValidator(WebhookResponseDataValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
class V31WebhookResponseHeadersValidator(WebhookResponseHeadersValidator):
spec_validator_cls = OpenAPIV31SpecValidator
schema_casters_factory = oas31_schema_casters_factory
schema_validators_factory = oas31_schema_validators_factory
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
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/ 0000775 0000000 0000000 00000000000 15163577675 0026067 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/__init__.py 0000664 0000000 0000000 00000002523 15163577675 0030202 0 ustar 00root root 0000000 0000000 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(
OAS30WriteValidator,
)
oas30_read_schema_validators_factory = SchemaValidatorsFactory(
OAS30ReadValidator,
)
oas31_schema_validators_factory = DialectSchemaValidatorsFactory(
OAS31Validator,
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,
)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/_validators.py 0000664 0000000 0000000 00000007013 15163577675 0030751 0 ustar 00root root 0000000 0000000 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,
},
),
)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/datatypes.py 0000664 0000000 0000000 00000000247 15163577675 0030442 0 ustar 00root root 0000000 0000000 from typing import Any
from typing import Callable
from typing import Dict
FormatValidator = Callable[[Any], bool]
FormatValidatorsDict = Dict[str, FormatValidator]
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/exceptions.py 0000664 0000000 0000000 00000001157 15163577675 0030626 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from dataclasses import field
from typing import Iterable
from openapi_core.exceptions import OpenAPIError
class ValidateError(OpenAPIError):
"""Schema validate operation error"""
@dataclass
class InvalidSchemaValue(ValidateError):
"""Value not valid for schema"""
value: str
type: str | list[str]
schema_errors: Iterable[Exception] = field(default_factory=list)
def __str__(self) -> str:
return (
"Value {value} not valid for schema of type {type}: {errors}"
).format(value=self.value, type=self.type, errors=self.schema_errors)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/factories.py 0000664 0000000 0000000 00000011765 15163577675 0030432 0 ustar 00root root 0000000 0000000 from copy import deepcopy
from functools import lru_cache
from typing import Any
from typing import Optional
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
class SchemaValidatorsFactory:
def __init__(
self,
schema_validator_cls: type[Validator],
format_checker: Optional[FormatChecker] = None,
):
self.schema_validator_cls = schema_validator_cls
if format_checker is None:
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(cast(FormatChecker, self.format_checker))
else:
format_checker = FormatChecker([])
format_checker = self._add_validators(
cast(FormatChecker, format_checker), format_validators
)
format_checker = self._add_validators(
cast(FormatChecker, format_checker), extra_format_validators
)
return format_checker
def _add_validators(
self,
base_format_checker: FormatChecker,
format_validators: Optional[FormatValidatorsDict] = None,
) -> FormatChecker:
if format_validators is not None:
for name, check in format_validators.items():
base_format_checker.checks(name)(check)
return base_format_checker
def create(
self,
spec: SchemaPath,
schema: SchemaPath,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
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
)
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),
),
)
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/schemas/validators.py 0000664 0000000 0000000 00000017152 15163577675 0030617 0 ustar 00root root 0000000 0000000 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
from jsonschema.exceptions import FormatError
from jsonschema.protocols import Validator
from jsonschema_path import SchemaPath
from openapi_core.validation.schemas.datatypes import FormatValidator
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__)
class SchemaValidator:
def __init__(
self,
schema: SchemaPath,
validator: Validator,
):
self.schema = schema
self.validator = validator
def __contains__(self, schema_format: str) -> bool:
return schema_format in self.validator.format_checker.checkers
def validate(self, value: Any) -> None:
errors_iter = self.validator.iter_errors(value)
errors = tuple(errors_iter)
if errors:
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.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
) -> bool:
callable = self.get_type_validator_callable(
type_override=type_override
)
return callable(value)
def format_validator(self, value: Any) -> bool:
try:
self.format_validator_callable(value)
except FormatError:
return False
else:
return True
def get_type_validator_callable(
self, type_override: Optional[str] = None
) -> FormatValidator:
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
)
return lambda x: True
@cached_property
def format_validator_callable(self) -> FormatValidator:
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
)
return lambda x: True
def get_primitive_type(self, value: Any) -> Optional[str]:
schema_types = (self.schema / "type").read_str_or_list(None)
if isinstance(schema_types, str):
return schema_types
if schema_types is None:
schema_types = sorted(self.validator.TYPE_CHECKER._type_checkers)
assert isinstance(schema_types, list)
for schema_type in schema_types:
result = self.type_validator(value, type_override=schema_type)
if not result:
continue
result = self.format_validator(value)
if not result:
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]:
yield self.schema
one_of_schema = self.get_one_of_schema(value)
if one_of_schema is not None:
yield one_of_schema
yield from self.iter_any_of_schemas(value)
yield from self.iter_all_of_schemas(value)
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
one_of_schemas = self.schema / "oneOf"
for subschema in one_of_schemas:
validator = self.evolve(subschema)
try:
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:
return subschema
log.warning("valid oneOf schema not found")
return None
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
any_of_schemas = self.schema / "anyOf"
for subschema in any_of_schemas:
validator = self.evolve(subschema)
try:
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:
yield subschema
def iter_all_of_schemas(
self,
value: Any,
) -> Iterator[SchemaPath]:
if "allOf" not in self.schema:
return
all_of_schemas = self.schema / "allOf"
for subschema in all_of_schemas:
if "type" not in subschema:
continue
validator = self.evolve(subschema)
try:
validator.validate(value)
except ValidateError:
log.warning("invalid allOf schema found")
else:
yield subschema
python-openapi-openapi-core-d6cdb4f/openapi_core/validation/validators.py 0000664 0000000 0000000 00000027763 15163577675 0027205 0 ustar 00root root 0000000 0000000 """OpenAPI core validation validators module"""
import warnings
from functools import cached_property
from typing import Any
from typing import Mapping
from typing import Optional
from typing import Tuple
from urllib.parse import urljoin
from jsonschema_path import SchemaPath
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
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
from openapi_core.deserializing.styles.exceptions import (
EmptyQueryParameterValue,
)
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.protocols import Request
from openapi_core.protocols import WebhookRequest
from openapi_core.schema.parameters import get_style_and_explode
from openapi_core.templating.media_types.datatypes import MediaType
from openapi_core.templating.paths.datatypes import PathOperationServer
from openapi_core.templating.paths.finders import APICallPathFinder
from openapi_core.templating.paths.finders import BasePathFinder
from openapi_core.templating.paths.finders import WebhookPathFinder
from openapi_core.templating.paths.types import PathFinderType
from openapi_core.validation.schemas.datatypes import FormatValidatorsDict
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
class BaseValidator:
schema_casters_factory: SchemaCastersFactory = NotImplemented
schema_validators_factory: SchemaValidatorsFactory = NotImplemented
path_finder_cls: PathFinderType = NotImplemented
spec_validator_cls: Optional[SpecValidatorType] = None
def __init__(
self,
spec: SchemaPath,
base_url: Optional[str] = None,
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,
spec_validator_cls: Optional[SpecValidatorType] = None,
format_validators: Optional[FormatValidatorsDict] = None,
extra_format_validators: Optional[FormatValidatorsDict] = None,
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
self.schema_casters_factory = (
schema_casters_factory or self.schema_casters_factory
)
if self.schema_casters_factory is NotImplemented:
raise NotImplementedError("schema_casters_factory is not assigned")
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
)
if self.schema_validators_factory is NotImplemented:
raise NotImplementedError(
"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:
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:
return self.path_finder_cls(self.spec, base_url=self.base_url)
def check_spec(self, spec: SchemaPath) -> None:
if self.spec_validator_cls is None:
return
validator = self.spec_validator_cls(spec)
validator.validate()
def _find_media_type(
self, content: SchemaPath, mimetype: Optional[str] = None
) -> MediaType:
from openapi_core.templating.media_types.finders import MediaTypeFinder
finder = MediaTypeFinder(content)
if mimetype is None:
return finder.get_first()
return finder.find(mimetype)
def _deserialise_media_type(
self,
media_type: SchemaPath,
mimetype: str,
parameters: Mapping[str, str],
value: bytes,
) -> Any:
schema = media_type.get("schema")
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,
)
return deserializer.deserialize(value)
def _deserialise_style(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Any:
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(
self.spec, schema, style, explode, name=name
)
return deserializer.deserialize(location)
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)
def _get_param_or_header_and_schema(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Tuple[Any, Optional[SchemaPath]]:
schema: Optional[SchemaPath] = None
# Simple scenario
if "content" not in param_or_header:
casted, schema = self._get_simple_param_or_header(
param_or_header, location, name=name
)
# Complex scenario
else:
casted, schema = self._get_complex_param_or_header(
param_or_header, location, name=name
)
if schema is None:
return casted, None
self._validate_schema(schema, casted)
return casted, schema
def _get_simple_param_or_header(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Tuple[Any, SchemaPath]:
allow_empty_values = (param_or_header / "allowEmptyValue").read_bool(
default=None
)
if allow_empty_values:
warnings.warn(
"Use of allowEmptyValue property is deprecated",
DeprecationWarning,
)
# in simple scenrios schema always exist
schema = param_or_header / "schema"
try:
deserialised = self._deserialise_style(
param_or_header, location, name=name
)
except KeyError:
if "default" not in schema:
raise
return (schema / "default").read_value(), schema
if allow_empty_values is not None:
warnings.warn(
"Use of allowEmptyValue property is deprecated",
DeprecationWarning,
)
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 / "in").read_str("header")
if (
location_name == "query"
and deserialised == ""
and not allow_empty_values
):
param_or_header_name = (param_or_header / "name").read_str()
raise EmptyQueryParameterValue(param_or_header_name)
return deserialised, schema
def _get_complex_param_or_header(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Tuple[Any, Optional[SchemaPath]]:
content = param_or_header / "content"
raw = self._get_media_type_value(param_or_header, location, name=name)
return self._get_content_schema_value_and_schema(raw, content)
def _get_content_schema_value_and_schema(
self,
raw: bytes,
content: SchemaPath,
mimetype: Optional[str] = None,
) -> Tuple[Any, Optional[SchemaPath]]:
mime_type, parameters, media_type = self._find_media_type(
content, mimetype
)
# no point to catch KetError
# in complex scenrios schema doesn't exist
deserialised = self._deserialise_media_type(
media_type, mime_type, parameters, raw
)
if "schema" not in media_type:
return deserialised, None
schema = media_type / "schema"
return deserialised, schema
def _get_content_and_schema(
self, raw: bytes, content: SchemaPath, mimetype: Optional[str] = None
) -> Tuple[Any, Optional[SchemaPath]]:
deserialised, schema = self._get_content_schema_value_and_schema(
raw, content, mimetype
)
if schema is None:
return deserialised, None
self._validate_schema(schema, deserialised)
return deserialised, schema
def _get_media_type_value(
self,
param_or_header: SchemaPath,
location: Mapping[str, Any],
name: Optional[str] = None,
) -> Any:
name = name or (param_or_header / "name").read_str()
return location[name]
class BaseAPICallValidator(BaseValidator):
path_finder_cls = APICallPathFinder
def _find_path(self, request: Request) -> PathOperationServer:
path_pattern = getattr(request, "path_pattern", None) or request.path
full_url = urljoin(request.host_url, path_pattern)
return self.path_finder.find(request.method, full_url)
class BaseWebhookValidator(BaseValidator):
path_finder_cls = WebhookPathFinder
def _find_path(self, request: WebhookRequest) -> PathOperationServer:
return self.path_finder.find(request.method, request.name)
python-openapi-openapi-core-d6cdb4f/poetry.lock 0000664 0000000 0000000 00001266617 15163577675 0022066 0 ustar 00root root 0000000 0000000 # 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.13.4"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{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"
propcache = ">=0.2.0"
yarl = ">=1.17.0,<2.0"
[package.extras]
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.13.0"
description = "itertools and builtins for AsyncIO and mixed iterables"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"fastapi\" or extra == \"starlette\""
files = [
{file = "aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be"},
{file = "aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c"},
]
[[package]]
name = "aiosignal"
version = "1.4.0"
description = "aiosignal: a list of registered asynchronous callbacks"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{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 = "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 = "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.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{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]]
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\""}
idna = ">=2.8"
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) ; 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 = "appnope"
version = "0.1.4"
description = "Disable App Nap on macOS >= 10.9"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
markers = "platform_system == \"Darwin\""
files = [
{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.11.1"
description = "ASGI specs, helper code, and adapters"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{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\""}
[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]
astroid = ["astroid (>=2,<5)"]
test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"]
[[package]]
name = "async-timeout"
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"
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"},
]
[package.extras]
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 ; 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"
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]
setuptools = {version = "*", markers = "python_version >= \"3.12\""}
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
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.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 = "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]
extras = ["regex"]
[[package]]
name = "black"
version = "26.3.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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 = ">=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.10)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""]
[[package]]
name = "blinker"
version = "1.9.0"
description = "Fast, simple object-to-object and broadcast signaling"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"flask\""
files = [
{file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
{file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
]
[[package]]
name = "certifi"
version = "2024.7.4"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main", "dev", "docs"]
files = [
{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 = "cffi"
version = "2.0.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
markers = "implementation_name == \"pypy\""
files = [
{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"},
]
[[package]]
name = "charset-normalizer"
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"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
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"
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\""}
[[package]]
name = "colorama"
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.10.7"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{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 ; 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.24.0"
description = "A command line utility to check for unused, missing and transitive dependencies in a Python project."
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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\""}
packaging = ">=23.2"
requirements-parser = ">=0.11.0,<1"
tomli = {version = ">=2.0.1", markers = "python_full_version < \"3.11.0\""}
[[package]]
name = "distlib"
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"},
]
[[package]]
name = "django"
version = "5.2.12"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
groups = ["main", "dev"]
files = [
{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.8.1"
sqlparse = ">=0.3.1"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "djangorestframework"
version = "3.17.0"
description = "Web APIs for Django, made easy."
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "djangorestframework-3.17.0-py3-none-any.whl", hash = "sha256:d84fe85f30b7ac6e8c0076ce9ff635e4eaedca5912f8d7d2926ce448c08533ba"},
{file = "djangorestframework-3.17.0.tar.gz", hash = "sha256:456fd992a33f9e64c9d0f47e85d9787db0efb44f894c1e513315b5e74765bd4c"},
]
[package.dependencies]
django = ">=4.2"
[[package]]
name = "docopt"
version = "0.6.2"
description = "Pythonic argument parser, that will make you smile"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
[[package]]
name = "exceptiongroup"
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 = "executing"
version = "2.2.1"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{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]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""]
[[package]]
name = "falcon"
version = "4.2.0"
description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"falcon\""
files = [
{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.135.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = true
python-versions = ">=3.10"
groups = ["main"]
markers = "extra == \"fastapi\""
files = [
{file = "fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5"},
{file = "fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56"},
]
[package.dependencies]
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)", "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.20.3"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"},
{file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"},
]
[[package]]
name = "flake8"
version = "7.3.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{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.7.0,<0.8.0"
pycodestyle = ">=2.14.0,<2.15.0"
pyflakes = ">=3.4.0,<3.5.0"
[[package]]
name = "flask"
version = "3.1.3"
description = "A simple framework for building complex web applications."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"flask\""
files = [
{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.9.0"
click = ">=8.1.3"
itsdangerous = ">=2.2.0"
jinja2 = ">=3.1.2"
markupsafe = ">=2.1.1"
werkzeug = ">=3.1.0"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "frozenlist"
version = "1.6.0"
description = "A list-like structure which implements collections.abc.MutableSequence"
optional = false
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 = "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.16.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{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.9"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{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.16"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
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"
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"},
]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "idna"
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 = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipykernel"
version = "7.2.0"
description = "IPython Kernel for Jupyter"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661"},
{file = "ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e"},
]
[package.dependencies]
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]
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 = "ipython"
version = "8.38.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86"},
{file = "ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39"},
]
[package.dependencies]
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]
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.7.2"
description = "An ISO 8601 date/time/duration parser and formatter"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"},
{file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"},
]
[[package]]
name = "isort"
version = "8.0.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.10.0"
groups = ["dev"]
files = [
{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"]
[[package]]
name = "itsdangerous"
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.6"
groups = ["dev"]
files = [
{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.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["main", "docs"]
files = [
{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"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "jsonschema"
version = "4.24.1"
description = "An implementation of JSON Schema validation for Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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"
jsonschema-specifications = ">=2023.03.6"
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 (>=24.6.0)"]
[[package]]
name = "jsonschema-path"
version = "0.4.5"
description = "JSONSchema Spec with object-oriented paths"
optional = false
python-versions = "<4.0.0,>=3.10"
groups = ["main"]
files = [
{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.5.0,<0.6.0"
PyYAML = ">=5.1"
referencing = "<0.38.0"
[package.extras]
requests = ["requests (>=2.31.0,<3.0.0)"]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
optional = false
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 = "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]
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.12.0"
description = "A fast and thorough lazy object proxy."
optional = false
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 = "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"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
[[package]]
name = "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 = "mergedeep"
version = "1.3.4"
description = "A deep merge function for 🐍."
optional = false
python-versions = ">=3.6"
groups = ["docs"]
files = [
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
]
[[package]]
name = "mkdocs"
version = "1.6.1"
description = "Project documentation with Markdown."
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{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 = "mkdocs-autorefs"
version = "1.4.3"
description = "Automatically link across pages in MkDocs."
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{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.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"]
[[package]]
name = "mypy-extensions"
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"},
]
[package.dependencies]
setuptools = "*"
[[package]]
name = "openapi-schema-validator"
version = "0.8.1"
description = "OpenAPI schema validation for Python"
optional = false
python-versions = "<4.0.0,>=3.10.0"
groups = ["main"]
files = [
{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 = ">=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.8.4"
description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator"
optional = false
python-versions = "<4.0,>=3.10"
groups = ["main"]
files = [
{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]
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"
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 = "paginate"
version = "0.5.7"
description = "Divides large result sets into pages for easier browsing"
optional = false
python-versions = "*"
groups = ["docs"]
files = [
{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.5.0"
description = "Object-oriented paths"
optional = false
python-versions = "<4.0,>=3.10"
groups = ["main"]
files = [
{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 = "1.0.4"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.9"
groups = ["dev", "docs"]
files = [
{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 = "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 = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
]
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "platformdirs"
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"},
]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "4.5.1"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
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.12.5"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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.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.41.5"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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.10"
groups = ["main"]
files = [
{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.7.0"
python-dotenv = ">=0.21.0"
typing-inspection = ">=0.4.0"
[package.extras]
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.4.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
]
[[package]]
name = "pygments"
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 ; 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 = "9.0.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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 = ">=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]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-aiohttp"
version = "1.1.0"
description = "Pytest plugin for aiohttp support"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{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.11.0b0"
pytest = ">=6.1.0"
pytest-asyncio = ">=0.17.2"
[package.extras]
testing = ["coverage (==6.2)", "mypy (==1.12.1)"]
[[package]]
name = "pytest-asyncio"
version = "1.3.0"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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]
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)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "pytest-cov"
version = "7.0.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{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]
coverage = {version = ">=7.10.6", extras = ["toml"]}
pluggy = ">=1.2"
pytest = ">=7"
[package.extras]
testing = ["process-tests", "pytest-xdist", "virtualenv"]
[[package]]
name = "pytest-flake8"
version = "1.3.0"
description = "pytest plugin to check FLAKE8 requirements"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{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]
flake8 = ">=4.0"
pytest = ">=7.0"
[package.extras]
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 = "python-dateutil"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["dev", "docs"]
files = [
{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]
six = ">=1.5"
[[package]]
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.10"
groups = ["main"]
files = [
{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]
cli = ["click (>=5.0)"]
[[package]]
name = "python-multipart"
version = "0.0.22"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{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"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
[[package]]
name = "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.37.0"
description = "JSON Referencing + Python"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{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.33.0"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.10"
groups = ["main", "dev", "docs"]
files = [
{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 = ">=2023.5.7"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.26,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
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.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.26.0-py3-none-any.whl", hash = "sha256:03ec4409088cd5c66b71ecbbbd27fe2c58ddfad801c66203457b3e6a04868c37"},
{file = "responses-0.26.0.tar.gz", hash = "sha256:c7f6923e6343ef3682816ba421c006626777893cb0d5e1434f674b649bac9eb4"},
]
[package.dependencies]
pyyaml = "*"
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 ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"]
[[package]]
name = "rfc3339-validator"
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"},
]
[package.dependencies]
six = "*"
[[package]]
name = "rpds-py"
version = "0.27.1"
description = "Python bindings to Rust's persistent data structures (rpds)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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 = "78.1.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
groups = ["dev", "docs"]
files = [
{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]
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"
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"},
]
[[package]]
name = "sniffio"
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 = "sqlparse"
version = "0.5.4"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"},
{file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"},
]
markers = {main = "extra == \"django\""}
[package.extras]
dev = ["build"]
doc = ["sphinx"]
[[package]]
name = "stack-data"
version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{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]
asttokens = ">=2.1.0"
executing = ">=1.2.0"
pure-eval = "*"
[package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
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 = "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]
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
[[package]]
name = "strict-rfc3339"
version = "0.7"
description = "Strict, simple, lightweight RFC3339 functions"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"},
]
[[package]]
name = "tabulate"
version = "0.9.0"
description = "Pretty-print tabular data"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
{file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
]
[package.extras]
widechars = ["wcwidth"]
[[package]]
name = "tbump"
version = "6.11.0"
description = "Bump software releases"
optional = false
python-versions = ">=3.7,<4.0"
groups = ["dev"]
files = [
{file = "tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526"},
{file = "tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870"},
]
[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 = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_full_version <= \"3.11.0a6\""
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomlkit"
version = "0.11.8"
description = "Style preserving TOML library"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"},
{file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"},
]
[[package]]
name = "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.9"
groups = ["dev"]
files = [
{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]]
name = "traitlets"
version = "5.14.3"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
]
[package.extras]
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 = "typing-extensions"
version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev", "docs"]
files = [
{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 = "typing-inspection"
version = "0.4.2"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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.dependencies]
typing-extensions = ">=4.12.0"
[[package]]
name = "tzdata"
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.6.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev", "docs"]
files = [
{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.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 = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
[[package]]
name = "virtualenv"
version = "20.36.1"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{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 = {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]
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.9"
description = "WSGI request and response object"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["dev"]
files = [
{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.1.6"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{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"
[package.extras]
watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "yarl"
version = "1.18.3"
description = "Yet another URL library"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{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"
propcache = ">=0.2.0"
[extras]
aiohttp = ["aiohttp", "multidict"]
django = ["django"]
falcon = ["falcon"]
fastapi = ["aioitertools", "fastapi"]
flask = ["flask"]
requests = ["requests"]
starlette = ["aioitertools", "starlette"]
werkzeug = []
[metadata]
lock-version = "2.1"
python-versions = "^3.10.0"
content-hash = "d7e0dda94eff30865daa9fe709e2d396bf6f1fa9a96ce049a577aa5f94b2e5f2"
python-openapi-openapi-core-d6cdb4f/pyproject.toml 0000664 0000000 0000000 00000022420 15163577675 0022563 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.coverage.run]
branch = true
source =["openapi_core"]
[tool.coverage.xml]
output = "reports/coverage.xml"
[tool.mypy]
files = "openapi_core"
strict = true
[[tool.mypy.overrides]]
module = [
"asgiref.*",
"django.*",
"falcon.*",
"isodate.*",
"jsonschema.*",
"more_itertools.*",
"requests.*",
"werkzeug.*",
]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "lazy_object_proxy.*"
ignore_missing_imports = true
[tool.poetry]
name = "openapi-core"
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.md"
repository = "https://github.com/python-openapi/openapi-core"
documentation = "https://openapi-core.readthedocs.io"
keywords = ["openapi", "swagger", "schema"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"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",
]
include = [
{path = "tests", format = "sdist"},
]
[tool.poetry.dependencies]
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.40.0,<1.1.0", optional = true}
isodate = "*"
more-itertools = "*"
openapi-schema-validator = ">=0.7.0,<0.9.0"
openapi-spec-validator = "^0.8.0"
requests = {version = "*", optional = true}
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.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", "aioitertools"]
flask = ["flask"]
requests = ["requests"]
aiohttp = ["aiohttp", "multidict"]
starlette = ["starlette", "aioitertools"]
werkzeug = []
[tool.poetry.group.dev.dependencies]
black = ">=23.3,<27.0"
djangorestframework = "^3.11.2"
isort = ">=5.11.5,<9.0.0"
pre-commit = "*"
pytest = ">=8,<10"
pytest-flake8 = "*"
pytest-cov = "*"
python-multipart = "*"
responses = "*"
strict-rfc3339 = "^0.7"
webob = "*"
mypy = "^1.2"
httpx = ">=0.24,<0.29"
deptry = ">=0.11,<0.25"
pytest-aiohttp = "^1.1.0"
pyflakes = "^3.1.0"
tbump = "^6.11.0"
ipykernel = "^7.2.0"
[tool.poetry.group.docs.dependencies]
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 = """
--capture=no
--verbose
--showlocals
--junitxml=reports/junit.xml
--cov=openapi_core
--cov-report=term-missing
--cov-report=xml
"""
asyncio_mode = "strict"
filterwarnings = [
"error",
# falcon.media.handlers uses cgi to parse data
"ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
"ignore:co_lnotab is deprecated, use co_lines instead:DeprecationWarning",
]
[tool.black]
line-length = 79
[tool.isort]
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" }
python-openapi-openapi-core-d6cdb4f/tests/ 0000775 0000000 0000000 00000000000 15163577675 0021011 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/benchmarks/ 0000775 0000000 0000000 00000000000 15163577675 0023126 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/benchmarks/bench_paths.py 0000664 0000000 0000000 00000010514 15163577675 0025757 0 ustar 00root root 0000000 0000000 #!/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()
python-openapi-openapi-core-d6cdb4f/tests/integration/ 0000775 0000000 0000000 00000000000 15163577675 0023334 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/conftest.py 0000664 0000000 0000000 00000003553 15163577675 0025541 0 ustar 00root root 0000000 0000000 from base64 import b64decode
from os import path
from urllib import request
import pytest
from jsonschema_path import SchemaPath
from openapi_spec_validator.readers import read_from_filename
from yaml import safe_load
def content_from_file(spec_file):
directory = path.abspath(path.dirname(__file__))
path_full = path.join(directory, spec_file)
return read_from_filename(path_full)
def schema_path_from_file(spec_file):
spec_dict, base_uri = content_from_file(spec_file)
return SchemaPath.from_dict(spec_dict, base_uri=base_uri)
def schema_path_from_url(base_uri):
content = request.urlopen(base_uri)
spec_dict = safe_load(content)
return SchemaPath.from_dict(spec_dict, base_uri=base_uri)
@pytest.fixture(scope="session")
def data_gif():
return b64decode("""
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
Fzk0lpcjIQA7
""")
class Factory(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
@pytest.fixture(scope="session")
def content_factory():
return Factory(
from_file=content_from_file,
)
@pytest.fixture(scope="session")
def schema_path_factory():
return Factory(
from_file=schema_path_from_file,
from_url=schema_path_from_url,
)
@pytest.fixture(scope="session")
def v30_petstore_content(content_factory):
content, _ = content_factory.from_file("data/v3.0/petstore.yaml")
return content
@pytest.fixture(scope="session")
def v30_petstore_spec(v30_petstore_content):
base_uri = "file://tests/integration/data/v3.0/petstore.yaml"
return SchemaPath.from_dict(v30_petstore_content, base_uri=base_uri)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/ 0000775 0000000 0000000 00000000000 15163577675 0024774 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/ 0000775 0000000 0000000 00000000000 15163577675 0026444 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/conftest.py 0000664 0000000 0000000 00000007227 15163577675 0030653 0 ustar 00root root 0000000 0000000 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
from openapi_core import V30RequestUnmarshaller
from openapi_core import V30ResponseUnmarshaller
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebResponse
@pytest.fixture
def schema_path(schema_path_factory):
directory = pathlib.Path(__file__).parent
specfile = directory / "data" / "v3.0" / "aiohttp_factory.yaml"
return schema_path_factory.from_file(str(specfile))
@pytest.fixture
def response_getter() -> mock.MagicMock:
# Using a mock here allows us to control the return value for different scenarios.
return mock.MagicMock(return_value={"data": "data"})
@pytest.fixture
def no_validation(response_getter):
async def test_route(request: web.Request) -> web.Response:
await asyncio.sleep(0)
response = web.json_response(
response_getter(),
headers={"X-Rate-Limit": "12"},
status=200,
)
return response
return test_route
@pytest.fixture
def request_validation(schema_path, response_getter):
async def test_route(request: web.Request) -> web.Response:
request_body = await request.text()
openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body)
unmarshaller = V30RequestUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request)
response: dict[str, Any] = response_getter()
status = 200
if result.errors:
status = 400
response = {"errors": [{"message": str(e) for e in result.errors}]}
return web.json_response(
response,
headers={"X-Rate-Limit": "12"},
status=status,
)
return test_route
@pytest.fixture
def response_validation(schema_path, response_getter):
async def test_route(request: web.Request) -> web.Response:
request_body = await request.text()
openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body)
response_body = response_getter()
response = web.json_response(
response_body,
headers={"X-Rate-Limit": "12"},
status=200,
)
openapi_response = AIOHTTPOpenAPIWebResponse(response)
unmarshaller = V30ResponseUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request, openapi_response)
if result.errors:
response = web.json_response(
{"errors": [{"message": str(e) for e in result.errors}]},
headers={"X-Rate-Limit": "12"},
status=400,
)
return response
return test_route
@pytest.fixture(
params=["no_validation", "request_validation", "response_validation"]
)
def router(
request,
no_validation,
request_validation,
response_validation,
) -> web.RouteTableDef:
test_routes = dict(
no_validation=no_validation,
request_validation=request_validation,
response_validation=response_validation,
)
router_ = web.RouteTableDef()
handler = test_routes[request.param]
router_.post("/browse/{id}/")(handler)
return router_
@pytest.fixture
def app(router):
app = web.Application()
app.add_routes(router)
return 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()
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/ 0000775 0000000 0000000 00000000000 15163577675 0027355 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0030043 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttp_factory.yaml0000664 0000000 0000000 00000003535 15163577675 0034134 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Basic OpenAPI specification used with starlette integration tests
version: "0.1"
servers:
- url: 'http://localhost'
description: 'testing'
paths:
'/browse/{id}/':
parameters:
- name: id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
- name: q
in: query
required: true
description: query key
schema:
type: string
post:
requestBody:
description: request data
required: True
content:
application/json:
schema:
type: object
required:
- param1
properties:
param1:
type: integer
responses:
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/ 0000775 0000000 0000000 00000000000 15163577675 0033102 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035122 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject __main__.py 0000664 0000000 0000000 00000000370 15163577675 0035115 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject from aiohttp import web
from aiohttpproject.pets.views import PetPhotoView
routes = [
web.view("/v1/pets/{petId}/photo", PetPhotoView),
]
def get_app():
app = web.Application()
app.add_routes(routes)
return app
app = get_app()
openapi.py 0000664 0000000 0000000 00000000370 15163577675 0035030 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject from pathlib import Path
import yaml
from openapi_core import OpenAPI
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
openapi = OpenAPI.from_dict(spec_dict)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/0000775 0000000 0000000 00000000000 15163577675 0034055 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036075 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets views.py 0000664 0000000 0000000 00000003544 15163577675 0035513 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets from base64 import b64decode
from aiohttp import web
from aiohttpproject.openapi import openapi
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebResponse
class PetPhotoView(web.View):
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()
openapi_request = AIOHTTPOpenAPIWebRequest(
self.request, body=request_body
)
request_unmarshalled = openapi.unmarshal_request(openapi_request)
request_unmarshalled.raise_for_errors()
response = web.Response(
body=self.OPENID_LOGO,
content_type="image/gif",
)
openapi_response = AIOHTTPOpenAPIWebResponse(response)
response_unmarshalled = openapi.unmarshal_response(
openapi_request, openapi_response
)
response_unmarshalled.raise_for_errors()
return response
async def post(self):
request_body = await self.request.read()
openapi_request = AIOHTTPOpenAPIWebRequest(
self.request, body=request_body
)
request_unmarshalled = openapi.unmarshal_request(openapi_request)
request_unmarshalled.raise_for_errors()
response = web.Response(status=201)
openapi_response = AIOHTTPOpenAPIWebResponse(response)
response_unmarshalled = openapi.unmarshal_response(
openapi_request, openapi_response
)
response_unmarshalled.raise_for_errors()
return response
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/test_aiohttp_project.py 0000664 0000000 0000000 00000004207 15163577675 0033256 0 ustar 00root root 0000000 0000000 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")
def project_setup():
directory = os.path.abspath(os.path.dirname(__file__))
project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, project_dir)
yield
sys.path.remove(project_dir)
@pytest.fixture
def app(project_setup):
from aiohttpproject.__main__ import get_app
return get_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:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
class TestPetPhotoView(BaseTestPetstore):
async def test_get_valid(self, client, data_gif):
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Host": "petstore.swagger.io",
}
cookies = {"user": "1"}
response = await client.get(
"/v1/pets/1/photo",
headers=headers,
cookies=cookies,
)
assert await response.content.read() == data_gif
assert response.status == 200
async def test_post_valid(self, client, data_gif):
content_type = "image/gif"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
"Host": "petstore.swagger.io",
}
data = {
"file": BytesIO(data_gif),
}
cookies = {"user": "1"}
response = await client.post(
"/v1/pets/1/photo",
headers=headers,
data=data,
cookies=cookies,
)
assert not await response.text()
assert response.status == 201
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/aiohttp/test_aiohttp_validation.py 0000664 0000000 0000000 00000005237 15163577675 0033746 0 ustar 00root root 0000000 0000000 from typing import TYPE_CHECKING
from unittest import mock
import pytest
if TYPE_CHECKING:
from aiohttp.test_utils import TestClient
pytestmark = pytest.mark.asyncio
async def test_aiohttp_integration_valid_input(client: "TestClient"):
# Given
given_query_string = {
"q": "string",
}
given_headers = {
"content-type": "application/json",
"Host": "localhost",
}
given_data = {"param1": 1}
expected_status_code = 200
expected_response_data = {"data": "data"}
# When
response = await client.post(
"/browse/12/",
params=given_query_string,
json=given_data,
headers=given_headers,
)
response_data = await response.json()
# Then
assert response.status == expected_status_code
assert response_data == expected_response_data
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
given_query_string = {
"q": "string",
}
given_headers = {
"content-type": "application/json",
"Host": "petstore.swagger.io",
}
given_data = {"param1": 1}
expected_status_code = 400
expected_response_data = {
"errors": [
{
"message": (
"Server not found for "
"http://petstore.swagger.io/browse/12/"
),
}
]
}
# When
response = await client.post(
"/browse/12/",
params=given_query_string,
json=given_data,
headers=given_headers,
)
response_data = await response.json()
# Then
assert response.status == expected_status_code
assert response_data == expected_response_data
async def test_aiohttp_integration_invalid_input(
client: "TestClient", response_getter, request
):
if "no_validation" in request.node.name:
pytest.skip("No validation for given handler.")
# Given
given_query_string = {
"q": "string",
}
given_headers = {
"content-type": "application/json",
"Host": "localhost",
}
given_data = {"param1": "string"}
response_getter.return_value = {"data": 1}
expected_status_code = 400
expected_response_data = {"errors": [{"message": mock.ANY}]}
# When
response = await client.post(
"/browse/12/",
params=given_query_string,
json=given_data,
headers=given_headers,
)
response_data = await response.json()
# Then
assert response.status == expected_status_code
assert response_data == expected_response_data
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/ 0000775 0000000 0000000 00000000000 15163577675 0026236 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/ 0000775 0000000 0000000 00000000000 15163577675 0027147 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0027635 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/ 0000775 0000000 0000000 00000000000 15163577675 0032466 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0034506 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/auth.py0000664 0000000 0000000 00000001013 15163577675 0033774 0 ustar 00root root 0000000 0000000 from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class SimpleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get("X_USERNAME")
if not username:
return None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed("No such user")
return (user, None)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/pets/ 0000775 0000000 0000000 00000000000 15163577675 0033441 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035461 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/pets migrations/ 0000775 0000000 0000000 00000000000 15163577675 0035536 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/pets __init__.py 0000664 0000000 0000000 00000000000 15163577675 0037635 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/pets/migrations views.py 0000664 0000000 0000000 00000006640 15163577675 0035077 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/pets from base64 import b64decode
from django.http import FileResponse
from django.http import HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
class PetListView(APIView):
def get(self, request):
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters.query == {
"page": 1,
"limit": 12,
"search": "",
}
data = [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
]
response_dict = {
"data": data,
}
django_response = JsonResponse(response_dict)
django_response["X-Rate-Limit"] = "12"
return django_response
def post(self, request):
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters.cookie == {
"user": 1,
}
assert request.openapi.parameters.header == {
"api-key": "12345",
}
assert request.openapi.body.__class__.__name__ == "PetCreate"
assert request.openapi.body.name in ["Cat", "Bird"]
if request.openapi.body.name == "Cat":
assert request.openapi.body.ears.__class__.__name__ == "Ears"
assert request.openapi.body.ears.healthy is True
if request.openapi.body.name == "Bird":
assert request.openapi.body.wings.__class__.__name__ == "Wings"
assert request.openapi.body.wings.healthy is True
django_response = HttpResponse(status=201)
django_response["X-Rate-Limit"] = "12"
return django_response
@staticmethod
def get_extra_actions():
return []
class PetDetailView(APIView):
def get(self, request, petId):
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters.path == {
"petId": 12,
}
data = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
response_dict = {
"data": data,
}
django_response = JsonResponse(response_dict)
django_response["X-Rate-Limit"] = "12"
return django_response
@staticmethod
def get_extra_actions():
return []
class PetPhotoView(APIView):
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
assert not request.openapi.errors
assert request.openapi.parameters.path == {
"petId": 12,
}
django_response = FileResponse(
[self.OPENID_LOGO],
content_type="image/gif",
)
return django_response
def post(self, request, petId):
assert request.openapi
assert not request.openapi.errors
# implement file upload here
django_response = HttpResponse(status=201)
return django_response
@staticmethod
def get_extra_actions():
return []
settings.py 0000664 0000000 0000000 00000006355 15163577675 0034632 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject """
Django settings for djangoproject project.
Generated by 'django-admin startproject' using Django 2.2.18.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
from pathlib import Path
import yaml
from jsonschema_path import SchemaPath
from openapi_core import OpenAPI
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "9=z^yj5yo%g_dyvgdzbceyph^nae)91lq(7^!qqmr1t9wi8b^="
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["petstore.swagger.io", "staging.gigantic-server.com"]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware",
]
ROOT_URLCONF = "djangoproject.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "djangoproject.wsgi.application"
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = []
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/static/"
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"djangoproject.auth.SimpleAuthentication",
]
}
OPENAPI_SPEC_PATH = Path("tests/integration/data/v3.0/petstore.yaml")
OPENAPI_SPEC_DICT = yaml.load(OPENAPI_SPEC_PATH.read_text(), yaml.Loader)
OPENAPI_SPEC = SchemaPath.from_dict(OPENAPI_SPEC_DICT)
OPENAPI = OpenAPI(OPENAPI_SPEC)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/status/0000775 0000000 0000000 00000000000 15163577675 0034011 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036031 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/status migrations/ 0000775 0000000 0000000 00000000000 15163577675 0036106 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/status __init__.py 0000664 0000000 0000000 00000000000 15163577675 0040205 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/status/migrations views.py 0000664 0000000 0000000 00000000653 15163577675 0035445 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/status 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")
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/tags/ 0000775 0000000 0000000 00000000000 15163577675 0033424 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035444 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/tags views.py 0000664 0000000 0000000 00000000472 15163577675 0035057 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/tags from django.http import HttpResponse
from rest_framework.views import APIView
class TagListView(APIView):
def get(self, request):
assert request.openapi
assert not request.openapi.errors
return HttpResponse("success")
@staticmethod
def get_extra_actions():
return []
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py0000664 0000000 0000000 00000003163 15163577675 0034030 0 ustar 00root root 0000000 0000000 """djangotest URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include
from django.urls import path
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 = [
path("admin/", admin.site.urls),
path(
"api-auth/",
include("rest_framework.urls", namespace="rest_framework"),
),
path(
"v1/pets",
PetListView.as_view(),
name="pet_list_view",
),
path(
"v1/pets/",
PetDetailView.as_view(),
name="pet_detail_view",
),
path(
"v1/pets//photo",
PetPhotoView.as_view(),
name="pet_photo_view",
),
path(
"v1/tags",
TagListView.as_view(),
name="tag_list_view",
),
path(
"status",
get_status,
name="get_status_view",
),
]
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/django/test_django_project.py 0000664 0000000 0000000 00000033435 15163577675 0032647 0 ustar 00root root 0000000 0000000 import os
import sys
from base64 import b64encode
from json import dumps
from unittest import mock
import pytest
from django.test.utils import override_settings
class BaseTestDjangoProject:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture(autouse=True, scope="module")
def django_setup(self):
directory = os.path.abspath(os.path.dirname(__file__))
django_project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, django_project_dir)
with mock.patch.dict(
os.environ,
{
"DJANGO_SETTINGS_MODULE": "djangoproject.settings",
},
):
import django
django.setup()
yield
sys.path.remove(django_project_dir)
@pytest.fixture
def client(self):
from django.test import Client
return Client()
class TestPetListView(BaseTestDjangoProject):
def test_get_no_required_param(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
with pytest.warns(DeprecationWarning):
response = client.get("/v1/pets", **headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required query parameter: limit",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_get_valid(self, client):
data_json = {
"limit": 12,
}
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
with pytest.warns(DeprecationWarning):
response = client.get("/v1/pets", data_json, **headers)
expected_data = {
"data": [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
],
}
assert response.status_code == 200
assert response.json() == expected_data
def test_post_server_invalid(self, client):
headers = {
"HTTP_HOST": "petstore.swagger.io",
}
response = client.post("/v1/pets", **headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for "
"http://petstore.swagger.io/v1/pets"
),
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_required_header_param_missing(self, client):
client.cookies.load({"user": 1})
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
content_type = "application/json"
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "staging.gigantic-server.com",
}
response = client.post(
"/v1/pets", data_json, content_type, secure=True, **headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required header parameter: api-key",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_media_type_invalid(self, client):
client.cookies.load({"user": 1})
data = "data"
content_type = "text/html"
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "staging.gigantic-server.com",
"HTTP_API_KEY": self.api_key_encoded,
}
response = client.post(
"/v1/pets", data, content_type, secure=True, **headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
"text/html. "
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
),
}
]
}
assert response.status_code == 415
assert response.json() == expected_data
def test_post_required_cookie_param_missing(self, client):
data_json = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
content_type = "application/json"
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "staging.gigantic-server.com",
"HTTP_API_KEY": self.api_key_encoded,
}
response = client.post(
"/v1/pets", data_json, content_type, secure=True, **headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required cookie parameter: user",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
@pytest.mark.parametrize(
"data_json",
[
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
{
"id": 12,
"name": "Bird",
"wings": {
"healthy": True,
},
},
],
)
def test_post_valid(self, client, data_json):
client.cookies.load({"user": 1})
content_type = "application/json"
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "staging.gigantic-server.com",
"HTTP_API_KEY": self.api_key_encoded,
}
response = client.post(
"/v1/pets", data_json, content_type, secure=True, **headers
)
assert response.status_code == 201
assert not response.content
class TestPetDetailView(BaseTestDjangoProject):
def test_get_server_invalid(self, client):
response = client.get("/v1/pets/12")
expected_data = (
b"You may need to add 'testserver' to ALLOWED_HOSTS."
)
assert response.status_code == 400
assert expected_data in response.content
def test_get_unauthorized(self, client):
headers = {
"HTTP_HOST": "petstore.swagger.io",
}
response = client.get("/v1/pets/12", **headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 403,
"title": (
"Security not found. Schemes not valid for any "
"requirement: [['petstore_auth']]"
),
}
]
}
assert response.status_code == 403
assert response.json() == expected_data
def test_delete_method_invalid(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
response = client.delete("/v1/pets/12", **headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 405,
"title": (
"Operation delete not found for "
"http://petstore.swagger.io/v1/pets/12"
),
}
]
}
assert response.status_code == 405
assert response.json() == expected_data
def test_get_valid(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
response = client.get("/v1/pets/12", **headers)
expected_data = {
"data": {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
}
assert response.status_code == 200
assert response.json() == expected_data
class BaseTestDRF(BaseTestDjangoProject):
@pytest.fixture
def api_client(self):
from rest_framework.test import APIClient
return APIClient()
class TestDRFPetListView(BaseTestDRF):
def test_post_valid(self, api_client):
api_client.cookies.load({"user": 1})
content_type = "application/json"
data_json = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "staging.gigantic-server.com",
"HTTP_API_KEY": self.api_key_encoded,
}
response = api_client.post(
"/v1/pets",
dumps(data_json),
content_type=content_type,
secure=True,
**headers,
)
assert response.status_code == 201
assert not response.content
class TestDRFTagListView(BaseTestDRF):
def test_get_response_invalid(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
response = client.get("/v1/tags", **headers)
assert response.status_code == 415
def test_get_skip_response_validation(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
with override_settings(OPENAPI_RESPONSE_CLS=None):
response = client.get("/v1/tags", **headers)
assert response.status_code == 200
assert response.content == b"success"
class TestPetPhotoView(BaseTestDjangoProject):
def test_get_valid(self, client, data_gif):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
response = client.get("/v1/pets/12/photo", **headers)
assert response.status_code == 200
assert b"".join(list(response.streaming_content)) == data_gif
def test_post_valid(self, client, data_gif):
client.cookies.load({"user": 1})
content_type = "image/gif"
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
"HTTP_API_KEY": self.api_key_encoded,
}
response = client.post(
"/v1/pets/12/photo", data_gif, content_type, **headers
)
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
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/ 0000775 0000000 0000000 00000000000 15163577675 0026236 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/conftest.py 0000664 0000000 0000000 00000003717 15163577675 0030445 0 ustar 00root root 0000000 0000000 import os
import sys
import pytest
from falcon import Request
from falcon import RequestOptions
from falcon import Response
from falcon import ResponseOptions
from falcon.routing import DefaultRouter
from falcon.status_codes import HTTP_200
from falcon.testing import TestClient
from falcon.testing import create_environ
@pytest.fixture
def environ_factory():
def create_env(method, path, server_name):
return create_environ(
host=server_name,
path=path,
)
return create_env
@pytest.fixture
def router():
router = DefaultRouter()
router.add_route("/browse/{id:int}/", lambda x: x)
return router
@pytest.fixture
def request_factory(environ_factory, router):
server_name = "localhost"
def create_request(
method,
path,
subdomain=None,
query_string=None,
content_type="application/json",
):
environ = environ_factory(method, path, server_name)
options = RequestOptions()
# return create_req(options=options, **environ)
req = Request(environ, options)
return req
return create_request
@pytest.fixture
def response_factory(environ_factory):
def create_response(
data, status_code=200, headers=None, content_type="application/json"
):
options = ResponseOptions()
resp = Response(options)
resp.body = data
resp.content_type = content_type
resp.status = HTTP_200
resp.set_headers(headers or {})
return resp
return create_response
@pytest.fixture(autouse=True, scope="module")
def falcon_setup():
directory = os.path.abspath(os.path.dirname(__file__))
falcon_project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, falcon_project_dir)
yield
sys.path.remove(falcon_project_dir)
@pytest.fixture
def app():
from falconproject.__main__ import app
return app
@pytest.fixture
def client(app):
return TestClient(app)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/ 0000775 0000000 0000000 00000000000 15163577675 0027147 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0027635 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject/ 0000775 0000000 0000000 00000000000 15163577675 0032466 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0034506 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject __main__.py 0000664 0000000 0000000 00000001422 15163577675 0034500 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject from falcon import App
from falcon import media
from falconproject.openapi import openapi_middleware
from falconproject.pets.resources import PetDetailResource
from falconproject.pets.resources import PetListResource
from falconproject.pets.resources import PetPhotoResource
extra_handlers = {
"application/vnd.api+json": media.JSONHandler(),
}
app = App(middleware=[openapi_middleware])
app.req_options.media_handlers.update(extra_handlers)
app.resp_options.media_handlers.update(extra_handlers)
pet_list_resource = PetListResource()
pet_detail_resource = PetDetailResource()
pet_photo_resource = PetPhotoResource()
app.add_route("/v1/pets", pet_list_resource)
app.add_route("/v1/pets/{petId}", pet_detail_resource)
app.add_route("/v1/pets/{petId}/photo", pet_photo_resource)
openapi.py 0000664 0000000 0000000 00000000665 15163577675 0034423 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject from pathlib import Path
import yaml
from jsonschema_path import SchemaPath
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
spec = SchemaPath.from_dict(spec_dict)
openapi_middleware = FalconOpenAPIMiddleware.from_spec(
spec,
extra_media_type_deserializers={},
)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/ 0000775 0000000 0000000 00000000000 15163577675 0033441 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035461 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject/pets resources.py 0000664 0000000 0000000 00000006672 15163577675 0035761 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/data/v3.0/falconproject/pets from base64 import b64decode
from json import dumps
from falcon.constants import MEDIA_JPEG
from falcon.constants import MEDIA_JSON
from falcon.status_codes import HTTP_200
from falcon.status_codes import HTTP_201
class PetListResource:
def on_get(self, request, response):
assert request.context.openapi
assert not request.context.openapi.errors
if "ids" in request.params:
assert request.context.openapi.parameters.query == {
"page": 1,
"limit": 2,
"search": "",
"ids": [1, 2],
}
else:
assert request.context.openapi.parameters.query == {
"page": 1,
"limit": 12,
"search": "",
}
data = [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
]
response.status = HTTP_200
response.content_type = MEDIA_JSON
response.text = dumps({"data": data})
response.set_header("X-Rate-Limit", "12")
def on_post(self, request, response):
assert request.context.openapi
assert not request.context.openapi.errors
assert request.context.openapi.parameters.cookie == {
"user": 1,
}
assert request.context.openapi.parameters.header == {
"api-key": "12345",
}
assert request.context.openapi.body.__class__.__name__ == "PetCreate"
assert request.context.openapi.body.name in ["Cat", "Bird"]
if request.context.openapi.body.name == "Cat":
assert (
request.context.openapi.body.ears.__class__.__name__ == "Ears"
)
assert request.context.openapi.body.ears.healthy is True
if request.context.openapi.body.name == "Bird":
assert (
request.context.openapi.body.wings.__class__.__name__
== "Wings"
)
assert request.context.openapi.body.wings.healthy is True
response.status = HTTP_201
response.set_header("X-Rate-Limit", "12")
class PetDetailResource:
def on_get(self, request, response, petId=None):
assert petId == "12"
assert request.context.openapi
assert not request.context.openapi.errors
assert request.context.openapi.parameters.path == {
"petId": 12,
}
data = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
response.status = HTTP_200
response.content_type = MEDIA_JSON
response.text = dumps({"data": data})
response.set_header("X-Rate-Limit", "12")
class PetPhotoResource:
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
response.stream = [self.OPENID_LOGO]
def on_post(self, request, response, petId=None):
data = request.stream.read()
assert data == self.OPENID_LOGO
response.status = HTTP_201
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/test_falcon_asgi_middleware.py 0000664 0000000 0000000 00000014454 15163577675 0034321 0 ustar 00root root 0000000 0000000 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
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/test_falcon_project.py 0000664 0000000 0000000 00000033306 15163577675 0032644 0 ustar 00root root 0000000 0000000 from base64 import b64encode
from json import dumps
import pytest
from urllib3 import encode_multipart_formdata
class BaseTestFalconProject:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
class TestPetListResource(BaseTestFalconProject):
def test_get_no_required_param(self, client):
headers = {
"Content-Type": "application/json",
}
with pytest.warns(DeprecationWarning):
response = client.simulate_get(
"/v1/pets", host="petstore.swagger.io", headers=headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required query parameter: limit",
}
]
}
assert response.status_code == 400
assert response.json == expected_data
def test_get_valid(self, client):
headers = {
"Content-Type": "application/json",
}
query_string = "limit=12"
with pytest.warns(DeprecationWarning):
response = client.simulate_get(
"/v1/pets",
host="petstore.swagger.io",
headers=headers,
query_string=query_string,
)
assert response.status_code == 200
assert response.json == {
"data": [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
],
}
def test_get_valid_multiple_ids(self, client):
headers = {
"Content-Type": "application/json",
}
query_string = "limit=2&ids=1&ids=2"
with pytest.warns(DeprecationWarning):
response = client.simulate_get(
"/v1/pets",
host="petstore.swagger.io",
headers=headers,
query_string=query_string,
)
assert response.status_code == 200
assert response.json == {
"data": [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
],
}
def test_post_server_invalid(self, client):
response = client.simulate_post(
"/v1/pets",
host="petstore.swagger.io",
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for "
"http://petstore.swagger.io/v1/pets"
),
}
]
}
assert response.status_code == 400
assert response.json == expected_data
def test_post_required_header_param_missing(self, client):
cookies = {"user": 1}
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
}
body = dumps(data_json)
response = client.simulate_post(
"/v1/pets",
host="staging.gigantic-server.com",
headers=headers,
body=body,
cookies=cookies,
protocol="https",
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required header parameter: api-key",
}
]
}
assert response.status_code == 400
assert response.json == expected_data
def test_post_media_type_invalid(self, client):
cookies = {"user": 1}
data_json = {
"data": "",
}
# noly 3 media types are supported by falcon by default:
# json, multipart and urlencoded
content_type = "application/vnd.api+json"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
body = dumps(data_json)
response = client.simulate_post(
"/v1/pets",
host="staging.gigantic-server.com",
headers=headers,
body=body,
cookies=cookies,
protocol="https",
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
f"{content_type}. "
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
),
}
]
}
assert response.status_code == 415
assert response.json == expected_data
def test_post_required_cookie_param_missing(self, client):
content_type = "application/json"
data_json = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
body = dumps(data_json)
response = client.simulate_post(
"/v1/pets",
host="staging.gigantic-server.com",
headers=headers,
body=body,
protocol="https",
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required cookie parameter: user",
}
]
}
assert response.status_code == 400
assert response.json == expected_data
@pytest.mark.parametrize(
"data_json",
[
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
{
"id": 12,
"name": "Bird",
"wings": {
"healthy": True,
},
},
],
)
def test_post_valid(self, client, data_json):
cookies = {"user": 1}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
body = dumps(data_json)
response = client.simulate_post(
"/v1/pets",
host="staging.gigantic-server.com",
headers=headers,
body=body,
cookies=cookies,
protocol="https",
)
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):
headers = {"Content-Type": "application/json"}
response = client.simulate_get("/v1/pets/12", headers=headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for "
"http://falconframework.org/v1/pets/12"
),
}
]
}
assert response.status_code == 400
assert response.json == expected_data
def test_get_path_invalid(self, client):
headers = {"Content-Type": "application/json"}
response = client.simulate_get(
"/v1/pet/invalid", host="petstore.swagger.io", headers=headers
)
assert response.status_code == 404
def test_get_unauthorized(self, client):
headers = {"Content-Type": "application/json"}
response = client.simulate_get(
"/v1/pets/12", host="petstore.swagger.io", headers=headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 403,
"title": (
"Security not found. Schemes not valid for any "
"requirement: [['petstore_auth']]"
),
}
]
}
assert response.status_code == 403
assert response.json == expected_data
def test_get_valid(self, client):
auth = "authuser"
content_type = "application/json"
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": content_type,
}
response = client.simulate_get(
"/v1/pets/12", host="petstore.swagger.io", headers=headers
)
assert response.status_code == 200
def test_delete_method_invalid(self, client):
auth = "authuser"
content_type = "application/json"
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": content_type,
}
response = client.simulate_delete(
"/v1/pets/12", host="petstore.swagger.io", headers=headers
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 405,
"title": (
"Operation delete not found for "
"http://petstore.swagger.io/v1/pets/12"
),
}
]
}
assert response.status_code == 405
assert response.json == expected_data
class TestPetPhotoResource(BaseTestFalconProject):
def test_get_valid(self, client, data_gif):
cookies = {"user": 1}
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
}
response = client.simulate_get(
"/v1/pets/1/photo",
host="petstore.swagger.io",
headers=headers,
cookies=cookies,
)
assert response.content == data_gif
assert response.status_code == 200
@pytest.mark.xfail(
reason="falcon request binary handler not implemented",
strict=True,
)
def test_post_valid(self, client, data_gif):
cookies = {"user": 1}
content_type = "image/jpeg"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
response = client.simulate_post(
"/v1/pets/1/photo",
host="petstore.swagger.io",
headers=headers,
body=data_gif,
cookies=cookies,
)
assert not response.content
assert response.status_code == 201
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/falcon/test_falcon_wsgi_middleware.py 0000664 0000000 0000000 00000003452 15163577675 0034343 0 ustar 00root root 0000000 0000000 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,
},
}
]
}
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/ 0000775 0000000 0000000 00000000000 15163577675 0026423 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/ 0000775 0000000 0000000 00000000000 15163577675 0027334 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0030022 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/ 0000775 0000000 0000000 00000000000 15163577675 0033040 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035060 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject __main__.py 0000664 0000000 0000000 00000000455 15163577675 0035057 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject from fastapi import FastAPI
from fastapiproject.openapi import openapi
from fastapiproject.routers import pets
from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware
app = FastAPI()
app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi)
app.include_router(pets.router)
openapi.py 0000664 0000000 0000000 00000000370 15163577675 0034766 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject from pathlib import Path
import yaml
from openapi_core import OpenAPI
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
openapi = OpenAPI.from_dict(spec_dict)
routers/ 0000775 0000000 0000000 00000000000 15163577675 0034464 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036563 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers pets.py 0000664 0000000 0000000 00000005645 15163577675 0036023 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routers from base64 import b64decode
from fastapi import APIRouter
from fastapi import Body
from fastapi import Request
from fastapi import Response
from fastapi import status
try:
from typing import Annotated
except ImportError:
from typing_extensions import Annotated
OPENID_LOGO = b64decode("""
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
Fzk0lpcjIQA7
""")
router = APIRouter(
prefix="/v1/pets",
tags=["pets"],
responses={404: {"description": "Not found"}},
)
@router.get("")
async def list_pets(request: Request, response: Response):
assert request.scope["openapi"]
assert not request.scope["openapi"].errors
assert request.scope["openapi"].parameters.query == {
"page": 1,
"limit": 12,
"search": "",
}
data = [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
]
response.headers["X-Rate-Limit"] = "12"
return {"data": data}
@router.post("")
async def create_pet(request: Request):
assert request.scope["openapi"].parameters.cookie == {
"user": 1,
}
assert request.scope["openapi"].parameters.header == {
"api-key": "12345",
}
assert request.scope["openapi"].body.__class__.__name__ == "PetCreate"
assert request.scope["openapi"].body.name in ["Cat", "Bird"]
if request.scope["openapi"].body.name == "Cat":
assert request.scope["openapi"].body.ears.__class__.__name__ == "Ears"
assert request.scope["openapi"].body.ears.healthy is True
if request.scope["openapi"].body.name == "Bird":
assert (
request.scope["openapi"].body.wings.__class__.__name__ == "Wings"
)
assert request.scope["openapi"].body.wings.healthy is True
headers = {
"X-Rate-Limit": "12",
}
return Response(status_code=status.HTTP_201_CREATED, headers=headers)
@router.get("/{petId}")
async def detail_pet(request: Request, response: Response):
assert request.scope["openapi"]
assert not request.scope["openapi"].errors
assert request.scope["openapi"].parameters.path == {
"petId": 12,
}
data = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
response.headers["X-Rate-Limit"] = "12"
return {
"data": data,
}
@router.get("/{petId}/photo")
async def download_pet_photo():
return Response(content=OPENID_LOGO, media_type="image/gif")
@router.post("/{petId}/photo")
async def upload_pet_photo(
image: Annotated[bytes, Body(media_type="image/jpg")],
):
assert image == OPENID_LOGO
return Response(status_code=status.HTTP_201_CREATED)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/fastapi/test_fastapi_project.py 0000664 0000000 0000000 00000025741 15163577675 0033222 0 ustar 00root root 0000000 0000000 import os
import sys
from base64 import b64encode
import pytest
from fastapi.testclient import TestClient
@pytest.fixture(autouse=True, scope="module")
def project_setup():
directory = os.path.abspath(os.path.dirname(__file__))
project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, project_dir)
yield
sys.path.remove(project_dir)
@pytest.fixture
def app():
from fastapiproject.__main__ import app
return app
@pytest.fixture
def client(app):
with TestClient(app, base_url="http://petstore.swagger.io") as test_client:
yield test_client
class BaseTestPetstore:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
class TestPetListEndpoint(BaseTestPetstore):
def test_get_no_required_param(self, client):
headers = {
"Authorization": "Basic testuser",
}
with pytest.warns(DeprecationWarning):
response = client.get("/v1/pets", headers=headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required query parameter: limit",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_get_valid(self, client):
data_json = {
"limit": 12,
}
headers = {
"Authorization": "Basic testuser",
}
with pytest.warns(DeprecationWarning):
response = client.get(
"/v1/pets",
params=data_json,
headers=headers,
)
expected_data = {
"data": [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
],
}
assert response.status_code == 200
assert response.json() == expected_data
def test_post_server_invalid(self, client):
response = client.post("/v1/pets")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for "
"http://petstore.swagger.io/v1/pets"
),
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_required_header_param_missing(self, client):
client.cookies.set("user", "1")
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required header parameter: api-key",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_media_type_invalid(self, client):
client.cookies.set("user", "1")
content = "data"
content_type = "text/html"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
content=content,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
"text/html. "
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
),
}
]
}
assert response.status_code == 415
assert response.json() == expected_data
def test_post_required_cookie_param_missing(self, client):
data_json = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required cookie parameter: user",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
@pytest.mark.parametrize(
"data_json",
[
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
{
"id": 12,
"name": "Bird",
"wings": {
"healthy": True,
},
},
],
)
def test_post_valid(self, client, data_json):
client.cookies.set("user", "1")
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
assert response.status_code == 201
assert not response.content
class TestPetDetailEndpoint(BaseTestPetstore):
def test_get_server_invalid(self, client):
response = client.get("http://testserver/v1/pets/12")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for " "http://testserver/v1/pets/12"
),
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_get_unauthorized(self, client):
response = client.get("/v1/pets/12")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 403,
"title": (
"Security not found. Schemes not valid for any "
"requirement: [['petstore_auth']]"
),
}
]
}
assert response.status_code == 403
assert response.json() == expected_data
def test_delete_method_invalid(self, client):
headers = {
"Authorization": "Basic testuser",
}
response = client.delete("/v1/pets/12", headers=headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 405,
"title": (
"Operation delete not found for "
"http://petstore.swagger.io/v1/pets/12"
),
}
]
}
assert response.status_code == 405
assert response.json() == expected_data
def test_get_valid(self, client):
headers = {
"Authorization": "Basic testuser",
}
response = client.get("/v1/pets/12", headers=headers)
expected_data = {
"data": {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
}
assert response.status_code == 200
assert response.json() == expected_data
class TestPetPhotoEndpoint(BaseTestPetstore):
def test_get_valid(self, client, data_gif):
client.cookies.set("user", "1")
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
}
response = client.get(
"/v1/pets/1/photo",
headers=headers,
)
assert response.content == data_gif
assert response.status_code == 200
def test_post_valid(self, client, data_gif):
client.cookies.set("user", "1")
content_type = "image/gif"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
response = client.post(
"/v1/pets/1/photo",
headers=headers,
content=data_gif,
)
assert not response.text
assert response.status_code == 201
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/ 0000775 0000000 0000000 00000000000 15163577675 0026074 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/conftest.py 0000664 0000000 0000000 00000001354 15163577675 0030276 0 ustar 00root root 0000000 0000000 import pytest
from flask import Flask
@pytest.fixture(scope="session")
def schema_path(schema_path_factory):
specfile = "contrib/flask/data/v3.0/flask_factory.yaml"
return schema_path_factory.from_file(specfile)
@pytest.fixture
def app(app_factory):
return app_factory()
@pytest.fixture
def client(client_factory, app):
return client_factory(app)
@pytest.fixture(scope="session")
def client_factory():
def create(app):
return app.test_client()
return create
@pytest.fixture(scope="session")
def app_factory():
def create(root_path=None):
app = Flask("__main__", root_path=root_path)
app.config["DEBUG"] = True
app.config["TESTING"] = True
return app
return create
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/ 0000775 0000000 0000000 00000000000 15163577675 0027005 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0027473 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flask_factory.yaml 0000664 0000000 0000000 00000006035 15163577675 0033212 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Basic OpenAPI specification used with test_flask.TestFlaskOpenAPIIValidation
version: "0.1"
servers:
- url: 'http://localhost'
paths:
'/browse/{id}/':
parameters:
- name: id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
- name: q
in: query
required: false
description: query key
schema:
type: string
get:
responses:
404:
description: Return error.
content:
text/html:
schema:
type: string
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
post:
requestBody:
description: request data
required: True
content:
application/json:
schema:
type: object
required:
- param1
properties:
param1:
type: integer
responses:
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject/ 0000775 0000000 0000000 00000000000 15163577675 0032162 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0034202 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject __main__.py 0000664 0000000 0000000 00000000427 15163577675 0034200 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject from flask import Flask
from flaskproject.openapi import openapi
from flaskproject.pets.views import PetPhotoView
app = Flask(__name__)
app.add_url_rule(
"/v1/pets//photo",
view_func=PetPhotoView.as_view("pet_photo", openapi),
methods=["GET", "POST"],
)
openapi.py 0000664 0000000 0000000 00000000370 15163577675 0034110 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject from pathlib import Path
import yaml
from openapi_core import OpenAPI
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
openapi = OpenAPI.from_dict(spec_dict)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/ 0000775 0000000 0000000 00000000000 15163577675 0033135 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0035155 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject/pets views.py 0000664 0000000 0000000 00000001500 15163577675 0034561 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/data/v3.0/flaskproject/pets from base64 import b64decode
from io import BytesIO
from flask import Response
from flask import request
from flask.helpers import send_file
from openapi_core.contrib.flask.views import FlaskOpenAPIView
class PetPhotoView(FlaskOpenAPIView):
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)
return send_file(fp, mimetype="image/gif")
def post(self, petId):
assert request.data == self.OPENID_LOGO
return Response(status=201)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/test_flask_decorator.py 0000664 0000000 0000000 00000021540 15163577675 0032651 0 ustar 00root root 0000000 0000000 import pytest
from flask import jsonify
from flask import make_response
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core.datatypes import Parameters
@pytest.fixture(scope="session")
def decorator_factory(schema_path):
def create(**kwargs):
return FlaskOpenAPIViewDecorator.from_spec(schema_path, **kwargs)
return create
@pytest.fixture(scope="session")
def view_factory(decorator_factory):
def create(
app, path, methods=None, view_response_callable=None, decorator=None
):
decorator = decorator or decorator_factory()
@app.route(path, methods=methods)
@decorator
def view(*args, **kwargs):
return view_response_callable(*args, **kwargs)
return view
return create
class TestFlaskOpenAPIDecorator:
@pytest.fixture
def decorator(self, decorator_factory):
return decorator_factory()
def test_invalid_content_type(self, client, view_factory, app, decorator):
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == Parameters(
path={
"id": 12,
}
)
resp = make_response("success", 200)
resp.headers["X-Rate-Limit"] = "12"
return resp
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=view_response_callable,
decorator=decorator,
)
result = client.get("/browse/12/")
assert result.json == {
"errors": [
{
"class": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
"text/html. Valid mimetypes: ['application/json']"
),
}
]
}
def test_server_error(self, client, view_factory, app, decorator):
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=None,
decorator=decorator,
)
result = client.get("/browse/12/", base_url="https://localhost")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 400,
"title": (
"Server not found for "
"https://localhost/browse/{id}/"
),
}
]
}
assert result.status_code == 400
assert result.json == expected_data
def test_operation_error(self, client, view_factory, app, decorator):
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=None,
decorator=decorator,
)
result = client.put("/browse/12/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 405,
"title": (
"Operation put not found for "
"http://localhost/browse/{id}/"
),
}
]
}
assert result.status_code == 405
assert result.json == expected_data
def test_path_error(self, client, view_factory, app, decorator):
view_factory(
app,
"/browse/",
view_response_callable=None,
decorator=decorator,
)
result = client.get("/browse/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 404,
"title": (
"Path not found for " "http://localhost/browse/"
),
}
]
}
assert result.status_code == 404
assert result.json == expected_data
def test_endpoint_error(self, client, view_factory, app, decorator):
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=None,
decorator=decorator,
)
result = client.get("/browse/invalidparameter/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 400,
"title": (
"Failed to cast value to integer type: "
"invalidparameter"
),
}
]
}
assert result.json == expected_data
def test_response_object_valid(self, client, view_factory, app, decorator):
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == Parameters(
path={
"id": 12,
}
)
resp = jsonify(data="data")
resp.headers["X-Rate-Limit"] = "12"
return resp
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=view_response_callable,
decorator=decorator,
)
result = client.get("/browse/12/")
assert result.status_code == 200
assert result.json == {
"data": "data",
}
def test_response_skip_validation(
self, client, view_factory, app, decorator_factory
):
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == Parameters(
path={
"id": 12,
}
)
return make_response("success", 200)
decorator = decorator_factory(response_cls=None)
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=view_response_callable,
decorator=decorator,
)
result = client.get("/browse/12/")
assert result.status_code == 200
assert result.text == "success"
@pytest.mark.parametrize(
"response,expected_status,expected_headers",
[
# ((body, status, headers)) response tuple
(
("Not found", 404, {"X-Rate-Limit": "12"}),
404,
{"X-Rate-Limit": "12"},
),
# (body, status) response tuple
(("Not found", 404), 404, {}),
# (body, headers) response tuple
(
({"data": "data"}, {"X-Rate-Limit": "12"}),
200,
{"X-Rate-Limit": "12"},
),
],
)
def test_tuple_valid(
self,
client,
view_factory,
app,
decorator,
response,
expected_status,
expected_headers,
):
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == Parameters(
path={
"id": 12,
}
)
return response
view_factory(
app,
"/browse//",
["GET", "PUT"],
view_response_callable=view_response_callable,
decorator=decorator,
)
result = client.get("/browse/12/")
assert result.status_code == expected_status
expected_body = response[0]
if isinstance(expected_body, str):
assert result.text == expected_body
else:
assert result.json == expected_body
assert dict(result.headers).items() >= expected_headers.items()
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/test_flask_project.py 0000664 0000000 0000000 00000003515 15163577675 0032337 0 ustar 00root root 0000000 0000000 import os
import sys
from base64 import b64encode
import pytest
@pytest.fixture(autouse=True, scope="module")
def flask_setup():
directory = os.path.abspath(os.path.dirname(__file__))
flask_project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, flask_project_dir)
yield
sys.path.remove(flask_project_dir)
@pytest.fixture
def app():
from flaskproject.__main__ import app
app.config["SERVER_NAME"] = "petstore.swagger.io"
app.config["DEBUG"] = True
app.config["TESTING"] = True
return app
@pytest.fixture
def client(app):
return app.test_client()
class BaseTestFlaskProject:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
class TestPetPhotoView(BaseTestFlaskProject):
def test_get_valid(self, client, data_gif):
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
}
client.set_cookie("user", "1", domain="petstore.swagger.io")
response = client.get(
"/v1/pets/1/photo",
headers=headers,
)
assert response.get_data() == data_gif
assert response.status_code == 200
def test_post_valid(self, client, data_gif):
content_type = "image/gif"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
client.set_cookie("user", "1", domain="petstore.swagger.io")
response = client.post(
"/v1/pets/1/photo",
headers=headers,
data=data_gif,
)
assert not response.text
assert response.status_code == 201
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/test_flask_validator.py 0000664 0000000 0000000 00000003112 15163577675 0032647 0 ustar 00root root 0000000 0000000 from json import dumps
from flask.testing import FlaskClient
from flask.wrappers import Response
from openapi_core import V30RequestUnmarshaller
from openapi_core.contrib.flask import FlaskOpenAPIRequest
class TestFlaskOpenAPIValidation:
def test_request_validator_root_path(self, schema_path, app_factory):
def details_view_func(id):
from flask import request
openapi_request = FlaskOpenAPIRequest(request)
unmarshaller = V30RequestUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request)
assert not result.errors
if request.args.get("q") == "string":
return Response(
dumps({"data": "data"}),
headers={"X-Rate-Limit": "12"},
mimetype="application/json",
status=200,
)
else:
return Response("Not Found", status=404)
app = app_factory(root_path="/browse")
app.add_url_rule(
"//",
view_func=details_view_func,
methods=["POST"],
)
query_string = {
"q": "string",
}
headers = {"content-type": "application/json"}
data = {"param1": 1}
client = FlaskClient(app)
result = client.post(
"/12/",
base_url="http://localhost/browse",
query_string=query_string,
json=data,
headers=headers,
)
assert result.status_code == 200
assert result.json == {"data": "data"}
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/flask/test_flask_views.py 0000664 0000000 0000000 00000015021 15163577675 0032021 0 ustar 00root root 0000000 0000000 import pytest
from flask import jsonify
from flask import make_response
from openapi_core import Config
from openapi_core import OpenAPI
from openapi_core.contrib.flask.views import FlaskOpenAPIView
@pytest.fixture(scope="session")
def view_factory(schema_path):
def create(
methods=None,
extra_media_type_deserializers=None,
extra_format_validators=None,
):
if methods is None:
def get(view, id):
return make_response("success", 200)
methods = {
"get": get,
}
MyView = type("MyView", (FlaskOpenAPIView,), methods)
extra_media_type_deserializers = extra_media_type_deserializers or {}
extra_format_validators = extra_format_validators or {}
config = Config(
extra_media_type_deserializers=extra_media_type_deserializers,
extra_format_validators=extra_format_validators,
)
openapi = OpenAPI(schema_path, config=config)
return MyView.as_view(
"myview",
openapi,
)
return create
class TestFlaskOpenAPIView:
@pytest.fixture
def client(self, client_factory, app):
client = client_factory(app)
with app.app_context():
yield client
def test_invalid_content_type(self, client, app, view_factory):
def get(view, id):
view_response = make_response("success", 200)
view_response.headers["X-Rate-Limit"] = "12"
return view_response
view_func = view_factory({"get": get})
app.add_url_rule("/browse//", view_func=view_func)
result = client.get("/browse/12/")
assert result.status_code == 415
assert result.json == {
"errors": [
{
"class": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
"text/html. Valid mimetypes: ['application/json']"
),
}
]
}
def test_server_error(self, client, app, view_factory):
view_func = view_factory()
app.add_url_rule("/browse//", view_func=view_func)
result = client.get("/browse/12/", base_url="https://localhost")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 400,
"title": (
"Server not found for "
"https://localhost/browse/{id}/"
),
}
]
}
assert result.status_code == 400
assert result.json == expected_data
def test_operation_error(self, client, app, view_factory):
def put(view, id):
return make_response("success", 200)
view_func = view_factory({"put": put})
app.add_url_rule("/browse//", view_func=view_func)
result = client.put("/browse/12/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 405,
"title": (
"Operation put not found for "
"http://localhost/browse/{id}/"
),
}
]
}
assert result.status_code == 405
assert result.json == expected_data
def test_path_error(self, client, app, view_factory):
view_func = view_factory()
app.add_url_rule("/browse/", view_func=view_func)
result = client.get("/browse/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 404,
"title": (
"Path not found for " "http://localhost/browse/"
),
}
]
}
assert result.status_code == 404
assert result.json == expected_data
def test_endpoint_error(self, client, app, view_factory):
view_func = view_factory()
app.add_url_rule("/browse//", view_func=view_func)
result = client.get("/browse/invalidparameter/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 400,
"title": (
"Failed to cast value to integer type: "
"invalidparameter"
),
}
]
}
assert result.status_code == 400
assert result.json == expected_data
def test_missing_required_header(self, client, app, view_factory):
def get(view, id):
return jsonify(data="data")
view_func = view_factory({"get": get})
app.add_url_rule("/browse//", view_func=view_func)
result = client.get("/browse/12/")
expected_data = {
"errors": [
{
"class": (
""
),
"status": 400,
"title": ("Missing required header: X-Rate-Limit"),
}
]
}
assert result.status_code == 400
assert result.json == expected_data
def test_valid(self, client, app, view_factory):
def get(view, id):
resp = jsonify(data="data")
resp.headers["X-Rate-Limit"] = "12"
return resp
view_func = view_factory({"get": get})
app.add_url_rule("/browse//", view_func=view_func)
result = client.get("/browse/12/")
assert result.status_code == 200
assert result.json == {
"data": "data",
}
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/ 0000775 0000000 0000000 00000000000 15163577675 0026647 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/conftest.py 0000664 0000000 0000000 00000000450 15163577675 0031045 0 ustar 00root root 0000000 0000000 import unittest
import pytest
@pytest.fixture(autouse=True)
def disable_builtin_socket(scope="session"):
# ResourceWarning from pytest with responses 0.24.0 workaround
# See https://github.com/getsentry/responses/issues/689
with unittest.mock.patch("socket.socket"):
yield
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/data/ 0000775 0000000 0000000 00000000000 15163577675 0027560 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/data/v3.1/ 0000775 0000000 0000000 00000000000 15163577675 0030247 5 ustar 00root root 0000000 0000000 requests_factory.yaml 0000664 0000000 0000000 00000005147 15163577675 0034465 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/data/v3.1 openapi: "3.1.0"
info:
title: Basic OpenAPI specification used with requests integration tests
version: "0.1"
servers:
- url: 'http://localhost'
paths:
'/browse/{id}/':
parameters:
- name: id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
- name: q
in: query
required: true
description: query key
schema:
type: string
post:
requestBody:
description: request data
required: True
content:
application/json:
schema:
type: object
required:
- param1
properties:
param1:
type: integer
responses:
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
webhooks:
'resourceAdded':
parameters:
- name: X-Rate-Limit
in: header
required: true
description: Rate limit
schema:
type: integer
post:
requestBody:
description: Added resource data
required: True
content:
application/json:
schema:
type: object
required:
- id
properties:
id:
type: integer
responses:
200:
description: Callback complete.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/requests/test_requests_validation.py 0000664 0000000 0000000 00000016145 15163577675 0034354 0 ustar 00root root 0000000 0000000 from base64 import b64encode
import pytest
import requests
import responses
from openapi_core import V30RequestUnmarshaller
from openapi_core import V30ResponseUnmarshaller
from openapi_core import V31RequestUnmarshaller
from openapi_core import V31ResponseUnmarshaller
from openapi_core import V31WebhookRequestUnmarshaller
from openapi_core import V31WebhookResponseUnmarshaller
from openapi_core.contrib.requests import RequestsOpenAPIRequest
from openapi_core.contrib.requests import RequestsOpenAPIResponse
from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest
class TestV31RequestsFactory:
@pytest.fixture
def schema_path(self, schema_path_factory):
specfile = "contrib/requests/data/v3.1/requests_factory.yaml"
return schema_path_factory.from_file(specfile)
@pytest.fixture
def request_unmarshaller(self, schema_path):
return V31RequestUnmarshaller(schema_path)
@pytest.fixture
def response_unmarshaller(self, schema_path):
return V31ResponseUnmarshaller(schema_path)
@pytest.fixture
def webhook_request_unmarshaller(self, schema_path):
return V31WebhookRequestUnmarshaller(schema_path)
@pytest.fixture
def webhook_response_unmarshaller(self, schema_path):
return V31WebhookResponseUnmarshaller(schema_path)
@responses.activate
def test_response_validator_path_pattern(self, response_unmarshaller):
responses.add(
responses.POST,
"http://localhost/browse/12/?q=string",
json={"data": "data"},
status=200,
headers={"X-Rate-Limit": "12"},
)
request = requests.Request(
"POST",
"http://localhost/browse/12/",
params={"q": "string"},
headers={"content-type": "application/json"},
json={"param1": 1},
)
request_prepared = request.prepare()
session = requests.Session()
response = session.send(request_prepared)
openapi_request = RequestsOpenAPIRequest(request)
openapi_response = RequestsOpenAPIResponse(response)
result = response_unmarshaller.unmarshal(
openapi_request, openapi_response
)
assert not result.errors
def test_request_validator_path_pattern(self, request_unmarshaller):
request = requests.Request(
"POST",
"http://localhost/browse/12/",
params={"q": "string"},
headers={"content-type": "application/json"},
json={"param1": 1},
)
openapi_request = RequestsOpenAPIRequest(request)
result = request_unmarshaller.unmarshal(openapi_request)
assert not result.errors
def test_request_validator_prepared_request(self, request_unmarshaller):
request = requests.Request(
"POST",
"http://localhost/browse/12/",
params={"q": "string"},
headers={"content-type": "application/json"},
json={"param1": 1},
)
request_prepared = request.prepare()
openapi_request = RequestsOpenAPIRequest(request_prepared)
result = request_unmarshaller.unmarshal(openapi_request)
assert not result.errors
def test_webhook_request_validator_path(
self, webhook_request_unmarshaller
):
request = requests.Request(
"POST",
"http://otherhost/callback/",
headers={
"content-type": "application/json",
"X-Rate-Limit": "12",
},
json={"id": 1},
)
openapi_webhook_request = RequestsOpenAPIWebhookRequest(
request, "resourceAdded"
)
result = webhook_request_unmarshaller.unmarshal(
openapi_webhook_request
)
assert not result.errors
@responses.activate
def test_webhook_response_validator_path(
self, webhook_response_unmarshaller
):
responses.add(
responses.POST,
"http://otherhost/callback/",
json={"data": "data"},
status=200,
)
request = requests.Request(
"POST",
"http://otherhost/callback/",
headers={
"content-type": "application/json",
"X-Rate-Limit": "12",
},
json={"id": 1},
)
request_prepared = request.prepare()
session = requests.Session()
response = session.send(request_prepared)
openapi_webhook_request = RequestsOpenAPIWebhookRequest(
request, "resourceAdded"
)
openapi_response = RequestsOpenAPIResponse(response)
result = webhook_response_unmarshaller.unmarshal(
openapi_webhook_request, openapi_response
)
assert not result.errors
class BaseTestPetstore:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
class TestPetstore(BaseTestPetstore):
@pytest.fixture
def schema_path(self, schema_path_factory):
specfile = "data/v3.0/petstore.yaml"
return schema_path_factory.from_file(specfile)
@pytest.fixture
def request_unmarshaller(self, schema_path):
return V30RequestUnmarshaller(schema_path)
@pytest.fixture
def response_unmarshaller(self, schema_path):
return V30ResponseUnmarshaller(schema_path)
@responses.activate
def test_response_binary_valid(self, response_unmarshaller, data_gif):
responses.add(
responses.GET,
"http://petstore.swagger.io/v1/pets/1/photo",
body=data_gif,
content_type="image/gif",
status=200,
)
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
}
request = requests.Request(
"GET",
"http://petstore.swagger.io/v1/pets/1/photo",
headers=headers,
)
request_prepared = request.prepare()
session = requests.Session()
response = session.send(request_prepared)
openapi_request = RequestsOpenAPIRequest(request)
openapi_response = RequestsOpenAPIResponse(response)
result = response_unmarshaller.unmarshal(
openapi_request, openapi_response
)
assert not result.errors
assert result.data == data_gif
@responses.activate
def test_request_binary_valid(self, request_unmarshaller, data_gif):
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": "image/gif",
}
request = requests.Request(
"POST",
"http://petstore.swagger.io/v1/pets/1/photo",
headers=headers,
data=data_gif,
)
request_prepared = request.prepare()
openapi_request = RequestsOpenAPIRequest(request_prepared)
result = request_unmarshaller.unmarshal(openapi_request)
assert not result.errors
assert result.body == data_gif
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/ 0000775 0000000 0000000 00000000000 15163577675 0027003 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/ 0000775 0000000 0000000 00000000000 15163577675 0027714 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0030402 5 ustar 00root root 0000000 0000000 starlette_factory.yaml 0000664 0000000 0000000 00000003506 15163577675 0034751 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0 openapi: "3.0.0"
info:
title: Basic OpenAPI specification used with starlette integration tests
version: "0.1"
servers:
- url: 'http://localhost'
paths:
'/browse/{id}/':
parameters:
- name: id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
- name: q
in: query
required: true
description: query key
schema:
type: string
post:
requestBody:
description: request data
required: True
content:
application/json:
schema:
type: object
required:
- param1
properties:
param1:
type: integer
responses:
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject/ 0000775 0000000 0000000 00000000000 15163577675 0034000 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036020 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject __main__.py 0000664 0000000 0000000 00000002355 15163577675 0036020 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Route
from starletteproject.openapi import openapi
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,
)
middleware = [
Middleware(
StarletteOpenAPIMiddleware,
openapi=openapi,
),
]
middleware_skip_response = [
Middleware(
StarletteOpenAPIMiddleware,
openapi=openapi,
response_cls=None,
),
]
routes = [
Route("/v1/pets", pet_list_endpoint, methods=["GET", "POST"]),
Route("/v1/pets/{petId}", pet_detail_endpoint, methods=["GET", "POST"]),
Route(
"/v1/pets/{petId}/photo", pet_photo_endpoint, methods=["GET", "POST"]
),
Route("/v1/tags", tag_list_endpoint, methods=["GET"]),
]
app = Starlette(
debug=True,
middleware=middleware,
routes=routes,
)
app_skip_response = Starlette(
debug=True,
middleware=middleware_skip_response,
routes=routes,
)
openapi.py 0000664 0000000 0000000 00000000370 15163577675 0035726 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject from pathlib import Path
import yaml
from openapi_core import OpenAPI
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
openapi = OpenAPI.from_dict(spec_dict)
pets/ 0000775 0000000 0000000 00000000000 15163577675 0034674 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036773 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets endpoints.py 0000664 0000000 0000000 00000006063 15163577675 0037256 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject/pets from base64 import b64decode
from starlette.responses import JSONResponse
from starlette.responses import Response
from starlette.responses import StreamingResponse
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):
assert request.scope["openapi"]
assert not request.scope["openapi"].errors
if request.method == "GET":
assert request.scope["openapi"].parameters.query == {
"page": 1,
"limit": 12,
"search": "",
}
data = [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
]
response_dict = {
"data": data,
}
headers = {
"X-Rate-Limit": "12",
}
return JSONResponse(response_dict, headers=headers)
elif request.method == "POST":
assert request.scope["openapi"].parameters.cookie == {
"user": 1,
}
assert request.scope["openapi"].parameters.header == {
"api-key": "12345",
}
assert request.scope["openapi"].body.__class__.__name__ == "PetCreate"
assert request.scope["openapi"].body.name in ["Cat", "Bird"]
if request.scope["openapi"].body.name == "Cat":
assert (
request.scope["openapi"].body.ears.__class__.__name__ == "Ears"
)
assert request.scope["openapi"].body.ears.healthy is True
if request.scope["openapi"].body.name == "Bird":
assert (
request.scope["openapi"].body.wings.__class__.__name__
== "Wings"
)
assert request.scope["openapi"].body.wings.healthy is True
headers = {
"X-Rate-Limit": "12",
}
return Response(status_code=201, headers=headers)
async def pet_detail_endpoint(request):
assert request.scope["openapi"]
assert not request.scope["openapi"].errors
if request.method == "GET":
assert request.scope["openapi"].parameters.path == {
"petId": 12,
}
data = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
response_dict = {
"data": data,
}
headers = {
"X-Rate-Limit": "12",
}
return JSONResponse(response_dict, headers=headers)
async def pet_photo_endpoint(request):
if request.method == "GET":
contents = iter([OPENID_LOGO])
return StreamingResponse(contents, media_type="image/gif")
elif request.method == "POST":
body = await request.body()
assert body == OPENID_LOGO
return Response(status_code=201)
tags/ 0000775 0000000 0000000 00000000000 15163577675 0034657 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject __init__.py 0000664 0000000 0000000 00000000000 15163577675 0036756 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags endpoints.py 0000664 0000000 0000000 00000000461 15163577675 0037235 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/data/v3.0/starletteproject/tags 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)
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/test_starlette_project.py 0000664 0000000 0000000 00000030556 15163577675 0034162 0 ustar 00root root 0000000 0000000 import os
import sys
from base64 import b64encode
import pytest
from starlette.testclient import TestClient
@pytest.fixture(autouse=True, scope="module")
def project_setup():
directory = os.path.abspath(os.path.dirname(__file__))
project_dir = os.path.join(directory, "data/v3.0")
sys.path.insert(0, project_dir)
yield
sys.path.remove(project_dir)
class BaseTestPetstore:
api_key = "12345"
@pytest.fixture
def app(self):
from starletteproject.__main__ import app
return app
@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):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
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 = {
"Authorization": "Basic testuser",
}
with pytest.warns(DeprecationWarning):
response = client.get("/v1/pets", headers=headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required query parameter: limit",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_get_valid(self, client):
data_json = {
"limit": 12,
}
headers = {
"Authorization": "Basic testuser",
}
with pytest.warns(DeprecationWarning):
response = client.get(
"/v1/pets",
params=data_json,
headers=headers,
)
expected_data = {
"data": [
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
],
}
assert response.status_code == 200
assert response.json() == expected_data
def test_post_server_invalid(self, client):
response = client.post("/v1/pets")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for "
"http://petstore.swagger.io/v1/pets"
),
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_required_header_param_missing(self, client):
client.cookies.set("user", "1")
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required header parameter: api-key",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_post_media_type_invalid(self, client):
client.cookies.set("user", "1")
content = "data"
content_type = "text/html"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
content=content,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 415,
"title": (
"Content for the following mimetype not found: "
"text/html. "
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
),
}
]
}
assert response.status_code == 415
assert response.json() == expected_data
def test_post_required_cookie_param_missing(self, client):
data_json = {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
}
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": "Missing required cookie parameter: user",
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
@pytest.mark.parametrize(
"data_json",
[
{
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
{
"id": 12,
"name": "Bird",
"wings": {
"healthy": True,
},
},
],
)
def test_post_valid(self, client, data_json):
client.cookies.set("user", "1")
content_type = "application/json"
headers = {
"Authorization": "Basic testuser",
"Content-Type": content_type,
"Api-Key": self.api_key_encoded,
}
response = client.post(
"https://staging.gigantic-server.com/v1/pets",
json=data_json,
headers=headers,
)
assert response.status_code == 201
assert not response.content
class TestPetDetailEndpoint(BaseTestPetstore):
def test_get_server_invalid(self, client):
response = client.get("http://testserver/v1/pets/12")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 400,
"title": (
"Server not found for " "http://testserver/v1/pets/12"
),
}
]
}
assert response.status_code == 400
assert response.json() == expected_data
def test_get_unauthorized(self, client):
response = client.get("/v1/pets/12")
expected_data = {
"errors": [
{
"type": (
""
),
"status": 403,
"title": (
"Security not found. Schemes not valid for any "
"requirement: [['petstore_auth']]"
),
}
]
}
assert response.status_code == 403
assert response.json() == expected_data
def test_delete_method_invalid(self, client):
headers = {
"Authorization": "Basic testuser",
}
response = client.delete("/v1/pets/12", headers=headers)
expected_data = {
"errors": [
{
"type": (
""
),
"status": 405,
"title": (
"Operation delete not found for "
"http://petstore.swagger.io/v1/pets/12"
),
}
]
}
assert response.status_code == 405
assert response.json() == expected_data
def test_get_valid(self, client):
headers = {
"Authorization": "Basic testuser",
}
response = client.get("/v1/pets/12", headers=headers)
expected_data = {
"data": {
"id": 12,
"name": "Cat",
"ears": {
"healthy": True,
},
},
}
assert response.status_code == 200
assert response.json() == expected_data
class TestPetPhotoEndpoint(BaseTestPetstore):
def test_get_valid(self, client, data_gif):
client.cookies.set("user", "1")
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
}
response = client.get(
"/v1/pets/1/photo",
headers=headers,
)
assert response.content == data_gif
assert response.status_code == 200
def test_post_valid(self, client, data_gif):
client.cookies.set("user", "1")
content_type = "image/gif"
headers = {
"Authorization": "Basic testuser",
"Api-Key": self.api_key_encoded,
"Content-Type": content_type,
}
response = client.post(
"/v1/pets/1/photo",
headers=headers,
content=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
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/starlette/test_starlette_validation.py0000664 0000000 0000000 00000010031 15163577675 0034630 0 ustar 00root root 0000000 0000000 from json import dumps
import pytest
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
from openapi_core import unmarshal_request
from openapi_core import unmarshal_response
from openapi_core.contrib.starlette import StarletteOpenAPIRequest
from openapi_core.contrib.starlette import StarletteOpenAPIResponse
class TestV30StarletteFactory:
@pytest.fixture
def schema_path(self, schema_path_factory):
specfile = "contrib/starlette/data/v3.0/starlette_factory.yaml"
return schema_path_factory.from_file(specfile)
@pytest.fixture
def app(self):
async def test_route(scope, receive, send):
request = Request(scope, receive)
if request.args.get("q") == "string":
response = JSONResponse(
dumps({"data": "data"}),
headers={"X-Rate-Limit": "12"},
mimetype="application/json",
status=200,
)
else:
response = PlainTextResponse("Not Found", status=404)
await response(scope, receive, send)
return Starlette(
routes=[
Route("/browse/12/", test_route),
],
)
@pytest.fixture
def client(self, app):
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"}
async def test_route(request):
body = await request.body()
openapi_request = StarletteOpenAPIRequest(request, body)
result = unmarshal_request(openapi_request, schema_path)
assert not result.errors
return JSONResponse(
response_data,
headers={"X-Rate-Limit": "12"},
media_type="application/json",
status_code=200,
)
app = Starlette(
routes=[
Route("/browse/12/", test_route, methods=["POST"]),
],
)
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
def test_response_validator_path_pattern(self, client, schema_path):
response_data = {"data": "data"}
def test_route(request):
response = JSONResponse(
response_data,
headers={"X-Rate-Limit": "12"},
media_type="application/json",
status_code=200,
)
openapi_request = StarletteOpenAPIRequest(request)
openapi_response = StarletteOpenAPIResponse(response)
result = unmarshal_response(
openapi_request, openapi_response, schema_path
)
assert not result.errors
return response
app = Starlette(
routes=[
Route("/browse/12/", test_route, methods=["POST"]),
],
)
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
python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/werkzeug/ 0000775 0000000 0000000 00000000000 15163577675 0026637 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/contrib/werkzeug/test_werkzeug_validation.py 0000664 0000000 0000000 00000006335 15163577675 0034334 0 ustar 00root root 0000000 0000000 from json import dumps
import pytest
import responses
from werkzeug.test import Client
from werkzeug.wrappers import Request
from werkzeug.wrappers import Response
from openapi_core import V30RequestUnmarshaller
from openapi_core import V30ResponseUnmarshaller
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse
class TestWerkzeugOpenAPIValidation:
@pytest.fixture
def schema_path(self, schema_path_factory):
specfile = "contrib/requests/data/v3.1/requests_factory.yaml"
return schema_path_factory.from_file(specfile)
@pytest.fixture
def app(self):
def test_app(environ, start_response):
req = Request(environ, populate_request=False)
if req.args.get("q") == "string":
response = Response(
dumps({"data": "data"}),
headers={"X-Rate-Limit": "12"},
mimetype="application/json",
status=200,
)
else:
response = Response("Not Found", status=404)
return response(environ, start_response)
return test_app
@pytest.fixture
def client(self, app):
return Client(app)
def test_request_validator_root_path(self, client, schema_path):
query_string = {
"q": "string",
}
headers = {"content-type": "application/json"}
data = {"param1": 1}
response = client.post(
"/12/",
base_url="http://localhost/browse",
query_string=query_string,
json=data,
headers=headers,
)
openapi_request = WerkzeugOpenAPIRequest(response.request)
unmarshaller = V30RequestUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request)
assert not result.errors
def test_request_validator_path_pattern(self, client, schema_path):
query_string = {
"q": "string",
}
headers = {"content-type": "application/json"}
data = {"param1": 1}
response = client.post(
"/browse/12/",
base_url="http://localhost",
query_string=query_string,
json=data,
headers=headers,
)
openapi_request = WerkzeugOpenAPIRequest(response.request)
unmarshaller = V30RequestUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request)
assert not result.errors
@responses.activate
def test_response_validator_path_pattern(self, client, schema_path):
query_string = {
"q": "string",
}
headers = {"content-type": "application/json"}
data = {"param1": 1}
response = client.post(
"/browse/12/",
base_url="http://localhost",
query_string=query_string,
json=data,
headers=headers,
)
openapi_request = WerkzeugOpenAPIRequest(response.request)
openapi_response = WerkzeugOpenAPIResponse(response)
unmarshaller = V30ResponseUnmarshaller(schema_path)
result = unmarshaller.unmarshal(openapi_request, openapi_response)
assert not result.errors
python-openapi-openapi-core-d6cdb4f/tests/integration/data/ 0000775 0000000 0000000 00000000000 15163577675 0024245 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/ 0000775 0000000 0000000 00000000000 15163577675 0024733 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/django_factory.yaml 0000664 0000000 0000000 00000000726 15163577675 0030615 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Basic OpenAPI specification used with test_flask.TestFlaskOpenAPIIValidation
version: "0.1"
servers:
- url: 'http://testserver'
paths:
'/admin/auth/group/{object_id}/':
parameters:
- name: object_id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
get:
responses:
default:
description: Return the resource.
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/empty.yaml 0000664 0000000 0000000 00000000020 15163577675 0026745 0 ustar 00root root 0000000 0000000 openapi: "3.0.0" python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/links.yaml 0000664 0000000 0000000 00000002075 15163577675 0026743 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Minimal valid OpenAPI specification
version: "0.1"
paths:
/linked/noParam:
get:
operationId: noParOp
responses:
default:
description: the linked result
/linked/withParam:
get:
operationId: paramOp
parameters:
- name: opParam
in: query
description: test
schema:
type: string
responses:
default:
description: the linked result
/status:
get:
responses:
default:
description: Return something
links:
noParamLink:
operationId: noParOp
/status/{resourceId}:
get:
parameters:
- name: resourceId
in: path
required: true
schema:
type: string
responses:
default:
description: Return something else
links:
paramLink:
operationId: paramOp
parameters:
opParam: $request.path.resourceId
requestBody: test
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/minimal.yaml 0000664 0000000 0000000 00000000300 15163577675 0027236 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Minimal valid OpenAPI specification
version: "0.1"
paths:
/status:
get:
responses:
default:
description: Return the API status.
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/minimal_with_servers.yaml 0000664 0000000 0000000 00000000362 15163577675 0032052 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Minimal valid OpenAPI specification with explicit 'servers' array
version: "0.1"
servers:
- url: /
paths:
/status:
get:
responses:
default:
description: Return the API status.
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/ 0000775 0000000 0000000 00000000000 15163577675 0030160 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/openapi.yaml 0000664 0000000 0000000 00000000147 15163577675 0032501 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: sample
version: "0.1"
paths:
/books:
$ref: "./paths/books.yaml" python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/paths/ 0000775 0000000 0000000 00000000000 15163577675 0031277 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/paths/books.yaml 0000664 0000000 0000000 00000000311 15163577675 0033273 0 ustar 00root root 0000000 0000000 get:
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "../schemas/book.yaml#/Book" python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/schemas/ 0000775 0000000 0000000 00000000000 15163577675 0031603 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/parent-reference/schemas/book.yaml 0000664 0000000 0000000 00000000166 15163577675 0033424 0 ustar 00root root 0000000 0000000 Book:
type: object
properties:
id:
$ref: "#/BookId"
title:
type: string
BookId:
type: string python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/path_param.yaml 0000664 0000000 0000000 00000000602 15163577675 0027731 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Minimal OpenAPI specification with path parameters
version: "0.1"
paths:
/resource/{resId}:
parameters:
- name: resId
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: string
get:
responses:
default:
description: Return the resource.
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/petstore.yaml 0000664 0000000 0000000 00000034016 15163577675 0027470 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
description: Swagger Petstore API specification
termsOfService: Fair use
contact:
name: Author
url: http://petstore.swagger.io
email: email@petstore.swagger.io
license:
name: MIT
url: https://opensource.org/licenses/MIT
security:
- api_key: []
- {}
servers:
- url: http://petstore.swagger.io/{version}
variables:
version:
enum:
- v1
- v2
default: v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: page
in: query
schema:
type: integer
format: int32
default: 1
- name: limit
in: query
style: form
description: How many items to return at one time (max 100)
required: true
deprecated: true
schema:
type: integer
format: int32
nullable: true
- name: search
in: query
description: Search query
schema:
type: string
default: ""
allowEmptyValue: true
- name: ids
in: query
description: Filter pets with Ids
schema:
type: array
items:
type: integer
format: int32
- name: order
in: query
schema:
oneOf:
- type: string
- type: integer
format: int32
- name: tags
in: query
description: Filter pets with tags
schema:
type: array
items:
$ref: "#/components/schemas/Tag"
explode: false
- name: coordinates
in: query
content:
application/json:
schema:
$ref: "#/components/schemas/Coordinates"
- name: color
in: query
description: RGB color
style: deepObject
required: false
explode: true
schema:
type: object
properties:
R:
type: integer
G:
type: integer
B:
type: integer
responses:
'200':
$ref: "#/components/responses/PetsResponse"
'400':
$ref: "#/components/responses/ErrorResponse"
'404':
$ref: "#/components/responses/HtmlResponse"
post:
summary: Create a pet
description: Creates new pet entry
externalDocs:
url: https://example.com
description: Find more info here
servers:
- url: https://development.gigantic-server.com/v1
description: Development server
- url: https://staging.gigantic-server.com/v1
description: Staging server
operationId: createPets
tags:
- pets
parameters:
- name: api-key
in: header
schema:
type: string
format: byte
required: true
- name: user
in: cookie
schema:
type: integer
format: int32
required: true
- name: userdata
in: cookie
content:
application/json:
schema:
$ref: '#/components/schemas/Userdata'
required: false
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PetCreate'
example:
name: "Pet"
wings: []
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PetCreate'
multipart/form-data:
schema:
$ref: '#/components/schemas/PetWithPhotoCreate'
text/plain: {}
responses:
'201':
description: Null response
default:
$ref: "#/components/responses/ErrorResponse"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: integer
format: int64
security:
- petstore_auth:
- write:pets
- read:pets
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/PetData"
example: |
{
"data": []
}
image/*:
schema:
type: string
format: binary
default:
$ref: "#/components/responses/ErrorResponse"
/pets/{petId}/photo:
get:
summary: Photo for a specific pet
operationId: showPetPhotoById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: integer
format: int64
responses:
'200':
description: Expected response to a valid request
content:
image/*:
schema:
type: string
format: binary
default:
$ref: "#/components/responses/ErrorResponse"
post:
summary: Create a pet photo
description: Creates new pet photo entry
operationId: createPetPhotoById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: integer
format: int64
requestBody:
required: true
content:
image/*:
schema:
type: string
format: binary
responses:
'201':
description: Null response
default:
$ref: "#/components/responses/ErrorResponse"
/tags:
get:
summary: List all tags
operationId: listTags
tags:
- tags
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/TagList"
example:
- dogs
- cats
default:
$ref: "#/components/responses/ErrorResponse"
post:
summary: Create new tag
operationId: createTag
tags:
- tags
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TagCreate'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/TagCreate'
responses:
'200':
description: Null response
default:
$ref: "#/components/responses/ErrorResponse"
delete:
summary: Delete tags
operationId: deleteTag
tags:
- tags
parameters:
- name: x-delete-force
in: header
schema:
type: boolean
required: false
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/TagDelete'
responses:
'200':
description: Null response
headers:
x-delete-confirm:
description: Confirmation automation
deprecated: true
schema:
type: boolean
required: true
x-delete-date:
description: Confirmation automation date
schema:
type: string
format: date
default:
$ref: "#/components/responses/ErrorResponse"
components:
schemas:
Coordinates:
x-model: Coordinates
type: object
required:
- lat
- lon
properties:
lat:
type: number
lon:
type: number
Userdata:
x-model: Userdata
type: object
required:
- name
properties:
name:
type: string
Utctime:
oneOf:
- type: string
enum: [always, now]
- type: string
format: date-time
Address:
type: object
x-model: Address
required:
- city
properties:
street:
type: string
city:
type: string
Tag:
type: string
enum:
- cats
- dogs
- birds
Position:
type: integer
enum:
- 1
- 2
- 3
Pet:
type: object
x-model: Pet
allOf:
- $ref: "#/components/schemas/PetCreate"
required:
- id
properties:
id:
type: integer
format: int64
PetCreate:
type: object
x-model: PetCreate
allOf:
- $ref: "#/components/schemas/PetCreatePartOne"
- $ref: "#/components/schemas/PetCreatePartTwo"
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
required:
- name
properties:
name:
type: string
tag:
$ref: "#/components/schemas/Tag"
address:
$ref: "#/components/schemas/Address"
PetCreatePartTwo:
type: object
x-model: PetCreatePartTwo
properties:
position:
$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
required:
- wings
properties:
wings:
$ref: "#/components/schemas/Wings"
Wings:
type: object
x-model: Wings
required:
- healthy
properties:
healthy:
type: boolean
Cat:
type: object
x-model: Cat
required:
- ears
properties:
ears:
$ref: "#/components/schemas/Ears"
Ears:
type: object
x-model: Ears
required:
- healthy
properties:
healthy:
type: boolean
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
PetsData:
type: object
x-model: PetsData
required:
- data
properties:
data:
$ref: "#/components/schemas/Pets"
PetData:
type: object
x-model: PetData
required:
- data
properties:
data:
$ref: "#/components/schemas/Pet"
TagCreate:
type: object
x-model: TagCreate
required:
- name
properties:
created:
$ref: "#/components/schemas/Utctime"
name:
type: string
additionalProperties: false
TagDelete:
type: object
x-model: TagDelete
required:
- ids
properties:
ids:
type: array
items:
type: integer
format: int64
additionalProperties: false
TagList:
type: array
items:
$ref: "#/components/schemas/Tag"
Error:
type: object
required:
- message
properties:
code:
type: integer
format: int32
default: 400
message:
type: string
StandardError:
type: object
x-model: StandardError
required:
- title
- status
- type
properties:
title:
type: string
status:
type: integer
format: int32
default: 400
type:
type: string
StandardErrors:
type: object
required:
- errors
properties:
errors:
type: array
items:
$ref: "#/components/schemas/StandardError"
ExtendedError:
type: object
x-model: ExtendedError
allOf:
- $ref: "#/components/schemas/Error"
- type: object
required:
- rootCause
properties:
correlationId:
type: string
format: uuid
rootCause:
type: string
suberror:
$ref: "#/components/schemas/ExtendedError"
additionalProperties:
oneOf:
- type: string
- type: integer
format: int32
responses:
ErrorResponse:
description: unexpected error
content:
application/json:
schema:
x-model: Error
oneOf:
- $ref: "#/components/schemas/StandardErrors"
- $ref: "#/components/schemas/ExtendedError"
HtmlResponse:
description: HTML page
content:
text/html: {}
PetsResponse:
description: An paged array of pets
headers:
content-type:
description: Content type
schema:
type: string
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/PetsData"
securitySchemes:
api_key:
type: apiKey
name: api_key
in: query
petstore_auth:
type: http
scheme: basic
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/read_only_write_only.yaml 0000664 0000000 0000000 00000001571 15163577675 0032052 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Specification Containing readOnly
version: "0.1"
paths:
/users:
post:
operationId: createUser
requestBody:
description: Post data for creating a user
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
default:
description: Create a user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
x-model: User
type: object
required:
- id
- name
properties:
id:
type: integer
format: int32
readOnly: true
name:
type: string
hidden:
type: boolean
writeOnly: true python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.0/security_override.yaml 0000664 0000000 0000000 00000001514 15163577675 0031366 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: Minimal OpenAPI specification with security override
version: "0.1"
security:
- api_key: []
paths:
/resource/{resId}:
parameters:
- name: resId
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: string
get:
responses:
default:
description: Default security.
post:
security:
- petstore_auth:
- write:pets
- read:pets
responses:
default:
description: Override security.
put:
security: []
responses:
default:
description: Remove security.
components:
securitySchemes:
api_key:
type: apiKey
name: api_key
in: query
petstore_auth:
type: http
scheme: basic
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/ 0000775 0000000 0000000 00000000000 15163577675 0024734 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/empty.yaml 0000664 0000000 0000000 00000000021 15163577675 0026747 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/links.yaml 0000664 0000000 0000000 00000002074 15163577675 0026743 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
info:
title: Minimal valid OpenAPI specification
version: "0.1"
paths:
/linked/noParam:
get:
operationId: noParOp
responses:
default:
description: the linked result
/linked/withParam:
get:
operationId: paramOp
parameters:
- name: opParam
in: query
description: test
schema:
type: string
responses:
default:
description: the linked result
/status:
get:
responses:
default:
description: Return something
links:
noParamLink:
operationId: noParOp
/status/{resourceId}:
get:
parameters:
- name: resourceId
in: path
required: true
schema:
type: string
responses:
default:
description: Return something else
links:
paramLink:
operationId: paramOp
parameters:
opParam: $request.path.resourceId
requestBody: test python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/minimal.yaml 0000664 0000000 0000000 00000000277 15163577675 0027254 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
info:
title: Minimal valid OpenAPI specification
version: "0.1"
paths:
/status:
get:
responses:
default:
description: Return the API status. python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/minimal_with_servers.yaml 0000664 0000000 0000000 00000000361 15163577675 0032052 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
info:
title: Minimal valid OpenAPI specification with explicit 'servers' array
version: "0.1"
servers:
- url: /
paths:
/status:
get:
responses:
default:
description: Return the API status. python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/path_param.yaml 0000664 0000000 0000000 00000000601 15163577675 0027731 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
info:
title: Minimal OpenAPI specification with path parameters
version: "0.1"
paths:
/resource/{resId}:
parameters:
- name: resId
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: string
get:
responses:
default:
description: Return the resource. python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/security_override.yaml 0000664 0000000 0000000 00000001513 15163577675 0031366 0 ustar 00root root 0000000 0000000 openapi: "3.1.0"
info:
title: Minimal OpenAPI specification with security override
version: "0.1"
security:
- api_key: []
paths:
/resource/{resId}:
parameters:
- name: resId
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: string
get:
responses:
default:
description: Default security.
post:
security:
- petstore_auth:
- write:pets
- read:pets
responses:
default:
description: Override security.
put:
security: []
responses:
default:
description: Remove security.
components:
securitySchemes:
api_key:
type: apiKey
name: api_key
in: query
petstore_auth:
type: http
scheme: basic python-openapi-openapi-core-d6cdb4f/tests/integration/data/v3.1/webhook-example.yaml 0000664 0000000 0000000 00000001641 15163577675 0030711 0 ustar 00root root 0000000 0000000 openapi: 3.1.0
info:
title: Webhook Example
version: 1.0.0
# Since OAS 3.1.0 the paths element isn't necessary. Now a valid OpenAPI Document can describe only paths, webhooks, or even only reusable components
webhooks:
# Each webhook needs a name
newPet:
# This is a Path Item Object, the only difference is that the request is initiated by the API provider
post:
requestBody:
description: Information about a new pet in the system
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
description: Return a 200 status to indicate that the data was received successfully
components:
schemas:
Pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
python-openapi-openapi-core-d6cdb4f/tests/integration/schema/ 0000775 0000000 0000000 00000000000 15163577675 0024574 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/schema/test_link_spec.py 0000664 0000000 0000000 00000002556 15163577675 0030164 0 ustar 00root root 0000000 0000000 import pytest
class TestLinkSpec:
@pytest.mark.parametrize(
"spec_file",
[
"data/v3.0/links.yaml",
"data/v3.1/links.yaml",
],
)
def test_no_param(self, spec_file, schema_path_factory):
schema_path = schema_path_factory.from_file(spec_file)
resp = schema_path / "paths#/status#get#responses#default"
links = resp / "links"
assert len(links) == 1
link = links / "noParamLink"
assert link["operationId"] == "noParOp"
assert "server" not in link
assert "requestBody" not in link
assert "parameters" not in link
@pytest.mark.parametrize(
"spec_file",
[
"data/v3.0/links.yaml",
"data/v3.1/links.yaml",
],
)
def test_param(self, spec_file, schema_path_factory):
schema_path = schema_path_factory.from_file(spec_file)
resp = schema_path / "paths#/status/{resourceId}#get#responses#default"
links = resp / "links"
assert len(links) == 1
link = links / "paramLink"
assert link["operationId"] == "paramOp"
assert "server" not in link
assert link["requestBody"] == "test"
parameters = link["parameters"]
assert len(parameters) == 1
param = parameters["opParam"]
assert param == "$request.path.resourceId"
python-openapi-openapi-core-d6cdb4f/tests/integration/schema/test_path_params.py 0000664 0000000 0000000 00000001127 15163577675 0030505 0 ustar 00root root 0000000 0000000 import pytest
class TestMinimal:
@pytest.mark.parametrize(
"spec_file",
[
"data/v3.0/path_param.yaml",
"data/v3.1/path_param.yaml",
],
)
def test_param_present(self, spec_file, schema_path_factory):
schema_path = schema_path_factory.from_file(spec_file)
path = schema_path / "paths#/resource/{resId}"
parameters = path / "parameters"
assert len(parameters) == 1
param = parameters[0]
assert param["name"] == "resId"
assert param["required"]
assert param["in"] == "path"
python-openapi-openapi-core-d6cdb4f/tests/integration/schema/test_spec.py 0000664 0000000 0000000 00000034127 15163577675 0027146 0 ustar 00root root 0000000 0000000 from base64 import b64encode
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
class TestPetstore:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture
def base_uri(self):
return "file://tests/integration/data/v3.0/petstore.yaml"
@pytest.fixture
def spec_dict(self, content_factory):
content, _ = content_factory.from_file("data/v3.0/petstore.yaml")
return content
@pytest.fixture
def schema_path(self, spec_dict, base_uri):
return SchemaPath.from_dict(spec_dict, base_uri=base_uri)
def test_spec(self, schema_path, spec_dict):
url = "http://petstore.swagger.io/v1"
info = schema_path / "info"
info_spec = spec_dict["info"]
assert info["title"] == info_spec["title"]
assert info["description"] == info_spec["description"]
assert info["termsOfService"] == info_spec["termsOfService"]
assert info["version"] == info_spec["version"]
contact = info / "contact"
contact_spec = info_spec["contact"]
assert contact["name"] == contact_spec["name"]
assert contact["url"] == contact_spec["url"]
assert contact["email"] == contact_spec["email"]
license = info / "license"
license_spec = info_spec["license"]
assert license["name"] == license_spec["name"]
assert license["url"] == license_spec["url"]
security = schema_path / "security"
security_spec = spec_dict.get("security", [])
for idx, security_reqs in enumerate(security):
security_reqs_spec = security_spec[idx]
for scheme_name, security_req in security_reqs.items():
security_req == security_reqs_spec[scheme_name]
assert get_spec_url(schema_path) == url
servers = schema_path / "servers"
for idx, server in enumerate(servers):
server_spec = spec_dict["servers"][idx]
assert server["url"] == server_spec["url"]
assert get_server_url(server) == url
variables = server / "variables"
for variable_name, variable in variables.items():
variable_spec = server_spec["variables"][variable_name]
assert variable["default"] == variable_spec["default"]
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 / "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", [])
for idx, server in enumerate(servers):
server_spec = servers_spec[idx]
assert server.url == server_spec["url"]
assert server.default_url == server_spec["url"]
assert server.description == server_spec.get("description")
variables = server.get("variables", {})
for variable_name, variable in variables.items():
variable_spec = server_spec["variables"][variable_name]
assert variable["default"] == variable_spec["default"]
assert (
variable / "enum"
).read_value() == variable_spec.get("enum")
operations = [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
]
for http_method in operations:
if http_method not in path:
continue
operation = path / http_method
operation_spec = path_spec[http_method]
assert operation["operationId"] is not None
assert (operation / "tags").read_str_or_list(
None
) == operation_spec["tags"]
assert operation["summary"] == operation_spec.get("summary")
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 / "description").read_str(
None
) == ext_docs_spec.get("description")
servers = operation.get("servers", [])
servers_spec = operation_spec.get("servers", [])
for idx, server in enumerate(servers):
server_spec = servers_spec[idx]
assert server["url"] == server_spec["url"]
assert get_server_url(server) == server_spec["url"]
assert server["description"] == server_spec.get(
"description"
)
variables = server.get("variables", {})
for variable_name, variable in variables.items():
variable_spec = server_spec["variables"][variable_name]
assert variable["default"] == variable_spec["default"]
assert (
variable / "enum"
).read_value() == variable_spec.get("enum")
security = operation.get("security", [])
security_spec = operation_spec.get("security")
if security_spec is not None:
for idx, security_reqs in enumerate(security):
security_reqs_spec = security_spec[idx]
for scheme_name, security_req in security_reqs.items():
security_req == security_reqs_spec[scheme_name]
responses = operation / "responses"
responses_spec = operation_spec.get("responses")
for http_status, response in responses.items():
response_spec = responses_spec[http_status]
if not response_spec:
continue
# @todo: test with defererence
if "$ref" in response_spec:
continue
description_spec = response_spec["description"]
assert (response / "description").read_str(
None
) == description_spec
headers = response.get("headers", {})
for parameter_name, parameter in headers.items():
headers_spec = response_spec["headers"]
parameter_spec = headers_spec[parameter_name]
schema = parameter.get("schema")
schema_spec = parameter_spec.get("schema")
assert bool(schema_spec) == bool(schema)
if not schema_spec:
continue
# @todo: test with defererence
if "$ref" in schema_spec:
continue
assert schema["type"] == schema_spec["type"]
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")
assert bool(content_spec) == bool(content)
if not content_spec:
continue
for mimetype, media_type in content.items():
media_spec = parameter_spec["content"][mimetype]
schema = media_type.get("schema")
schema_spec = media_spec.get("schema")
assert bool(schema_spec) == bool(schema)
if not schema_spec:
continue
# @todo: test with defererence
if "$ref" in schema_spec:
continue
assert schema["type"] == schema_spec["type"]
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")
if not content_spec:
continue
content = response.get("content", {})
for mimetype, media_type in content.items():
content_spec = response_spec["content"][mimetype]
example_spec = content_spec.get("example")
assert (media_type / "example").read_str_or_list(
None
) == example_spec
schema = media_type.get("schema")
schema_spec = content_spec.get("schema")
assert bool(schema_spec) == bool(schema)
if not schema_spec:
continue
# @todo: test with defererence
if "$ref" in schema_spec:
continue
assert schema["type"] == schema_spec["type"]
assert (schema / "required").read_bool(
None
) == schema_spec.get("required")
request_body = operation.get("requestBody")
request_body_spec = operation_spec.get("requestBody")
assert bool(request_body_spec) == bool(request_body)
if not request_body_spec:
continue
assert bool(
(request_body / "required").read_bool()
) == request_body_spec.get("required")
content = request_body / "content"
for mimetype, media_type in content.items():
content_spec = request_body_spec["content"][mimetype]
schema_spec = content_spec.get("schema")
if not schema_spec:
continue
# @todo: test with defererence
if "$ref" in schema_spec:
continue
schema = media_type.get("schema")
assert bool(schema_spec) == bool(schema)
assert schema["type"] == schema_spec["type"]
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:
return
schemas = components.get("schemas", {})
for schema_name, schema in schemas.items():
schema_spec = spec_dict["components"]["schemas"][schema_name]
assert (schema / "readOnly").read_bool(None) == schema_spec.get(
"readOnly"
)
assert (schema / "writeOnly").read_bool(None) == schema_spec.get(
"writeOnly"
)
class TestWebhook:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture
def base_uri(self):
return "file://tests/integration/data/v3.1/webhook-example.yaml"
@pytest.fixture
def spec_dict(self, content_factory):
content, _ = content_factory.from_file(
"data/v3.1/webhook-example.yaml"
)
return content
@pytest.fixture
def schema_path(self, spec_dict, base_uri):
return SchemaPath.from_dict(
spec_dict,
base_uri=base_uri,
)
def test_spec(self, schema_path, spec_dict):
info = schema_path / "info"
info_spec = spec_dict["info"]
assert info["title"] == info_spec["title"]
assert info["version"] == info_spec["version"]
webhooks = schema_path / "webhooks"
webhooks_spec = spec_dict["webhooks"]
assert (webhooks / "newPet").read_value() == webhooks_spec["newPet"]
components = schema_path.get("components")
if not components:
return
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"}
python-openapi-openapi-core-d6cdb4f/tests/integration/test_minimal.py 0000664 0000000 0000000 00000003436 15163577675 0026401 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core import unmarshal_request
from openapi_core import validate_request
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.testing import MockRequest
class TestMinimal:
servers = [
"http://minimal.test/",
"https://bad.remote.domain.net/",
"http://localhost",
"http://localhost:8080",
"https://u:p@a.b:1337",
]
spec_paths = [
"data/v3.0/minimal_with_servers.yaml",
"data/v3.0/minimal.yaml",
"data/v3.1/minimal_with_servers.yaml",
"data/v3.1/minimal.yaml",
]
@pytest.mark.parametrize("server", servers)
@pytest.mark.parametrize("spec_path", spec_paths)
def test_hosts(self, schema_path_factory, server, spec_path):
spec = schema_path_factory.from_file(spec_path)
request = MockRequest(server, "get", "/status")
result = unmarshal_request(request, spec=spec)
assert not result.errors
@pytest.mark.parametrize("server", servers)
@pytest.mark.parametrize("spec_path", spec_paths)
def test_invalid_operation(self, schema_path_factory, server, spec_path):
spec = schema_path_factory.from_file(spec_path)
request = MockRequest(server, "post", "/status")
with pytest.raises(OperationNotFound):
validate_request(request, spec)
@pytest.mark.parametrize("server", servers)
@pytest.mark.parametrize("spec_path", spec_paths)
def test_invalid_path(self, schema_path_factory, server, spec_path):
spec = schema_path_factory.from_file(spec_path)
request = MockRequest(server, "get", "/nonexistent")
with pytest.raises(PathNotFound):
validate_request(request, spec=spec)
python-openapi-openapi-core-d6cdb4f/tests/integration/test_petstore.py 0000664 0000000 0000000 00000173046 15163577675 0026625 0 ustar 00root root 0000000 0000000 import json
from base64 import b64encode
from dataclasses import is_dataclass
from datetime import datetime
from urllib.parse import urlencode
from uuid import UUID
import pytest
from isodate.tzinfo import UTC
from openapi_core import unmarshal_request
from openapi_core import unmarshal_response
from openapi_core import validate_request
from openapi_core import validate_response
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.datatypes import Parameters
from openapi_core.deserializing.styles.exceptions import (
EmptyQueryParameterValue,
)
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.testing import MockRequest
from openapi_core.testing import MockResponse
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestBodyUnmarshaller,
)
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestParametersUnmarshaller,
)
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestSecurityUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseDataUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseHeadersUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseUnmarshaller,
)
from openapi_core.validation.request.exceptions import MissingRequiredParameter
from openapi_core.validation.request.exceptions import ParameterValidationError
from openapi_core.validation.request.exceptions import (
RequestBodyValidationError,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
from openapi_core.validation.request.validators import V30RequestBodyValidator
from openapi_core.validation.request.validators import (
V30RequestParametersValidator,
)
from openapi_core.validation.request.validators import (
V30RequestSecurityValidator,
)
from openapi_core.validation.response.exceptions import InvalidData
from openapi_core.validation.response.exceptions import MissingRequiredHeader
from openapi_core.validation.response.validators import (
V30ResponseDataValidator,
)
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
class TestPetstore:
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture(scope="module")
def spec_dict(self, v30_petstore_content):
return v30_petstore_content
@pytest.fixture(scope="module")
def spec(self, v30_petstore_spec):
return v30_petstore_spec
@pytest.fixture(scope="module")
def response_unmarshaller(self, spec):
return V30ResponseUnmarshaller(spec)
def test_get_pets(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestBodyUnmarshaller,
)
assert result.body is None
data_json = {
"data": [],
}
data = json.dumps(data_json).encode()
headers = {
"Content-Type": "application/json",
"x-next": "next-url",
}
response = MockResponse(data, headers=headers)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.data == []
assert response_result.headers == {
"x-next": "next-url",
}
def test_get_pets_response(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data_json = {
"data": [
{
"id": 1,
"name": "Cat",
"ears": {
"healthy": True,
},
}
],
}
data = json.dumps(data_json).encode()
response = MockResponse(data)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert len(response_result.data.data) == 1
assert response_result.data.data[0].id == 1
assert response_result.data.data[0].name == "Cat"
def test_get_pets_response_media_type(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data = b"\xb1\xbc"
response = MockResponse(
data, status_code=404, content_type="text/html; charset=iso-8859-2"
)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert response_result.data == data.decode("iso-8859-2")
def test_get_pets_invalid_response(self, spec, response_unmarshaller):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
response_data_json = {
"data": [
{
"id": 1,
"name": {
"first_name": "Cat",
},
}
],
}
response_data = json.dumps(response_data_json).encode()
response = MockResponse(response_data)
with pytest.raises(InvalidData) as exc_info:
validate_response(
request,
response,
spec=spec,
cls=V30ResponseDataValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
response_result = response_unmarshaller.unmarshal(request, response)
assert response_result.errors == [InvalidData()]
schema_errors = response_result.errors[0].__cause__.schema_errors
assert response_result.errors[0].__cause__ == InvalidSchemaValue(
type="object",
value=response_data_json,
schema_errors=schema_errors,
)
assert response_result.data is None
def test_get_pets_ids_param(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
"ids": ["12", "13"],
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
"ids": [12, 13],
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data_json = {
"data": [],
}
data = json.dumps(data_json).encode()
response = MockResponse(data)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.data == []
def test_get_pets_tags_param(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = [
("limit", "20"),
("tags", "cats,dogs"),
]
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": 20,
"page": 1,
"search": "",
"tags": ["cats", "dogs"],
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data_json = {
"data": [],
}
data = json.dumps(data_json).encode()
response = MockResponse(data)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.data == []
def test_get_pets_parameter_schema_error(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "1",
"tags": ",,",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
with pytest.raises(ParameterValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_wrong_parameter_type(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "twenty",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
with pytest.raises(ParameterValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
assert type(exc_info.value.__cause__) is CastError
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_raises_missing_required_param(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
)
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
with pytest.raises(MissingRequiredParameter):
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_empty_value(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "1",
"order": "",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
with pytest.raises(ParameterValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
assert type(exc_info.value.__cause__) is EmptyQueryParameterValue
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_allow_empty_value(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": "20",
"search": "",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"page": 1,
"limit": 20,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_none_value(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": None,
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": None,
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_param_order(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
"limit": None,
"order": "desc",
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
query={
"limit": None,
"order": "desc",
"page": 1,
"search": "",
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_get_pets_param_coordinates(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
coordinates = {
"lat": 1.12,
"lon": 32.12,
}
query_params = {
"limit": None,
"coordinates": json.dumps(coordinates),
}
request = MockRequest(
host_url,
"GET",
"/pets",
path_pattern=path_pattern,
args=query_params,
)
with pytest.warns(
DeprecationWarning, match="limit parameter is deprecated"
):
with pytest.warns(
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert is_dataclass(result.parameters.query["coordinates"])
assert (
result.parameters.query["coordinates"].__class__.__name__
== "Coordinates"
)
assert result.parameters.query["coordinates"].lat == coordinates["lat"]
assert result.parameters.query["coordinates"].lon == coordinates["lon"]
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
def test_post_birds(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
userdata = {
"name": "user1",
}
userdata_json = json.dumps(userdata)
cookies = {
"user": "123",
"userdata": userdata_json,
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert is_dataclass(result.parameters.cookie["userdata"])
assert (
result.parameters.cookie["userdata"].__class__.__name__
== "Userdata"
)
assert result.parameters.cookie["userdata"].name == "user1"
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
assert result.body.healthy == pet_healthy
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestSecurityUnmarshaller,
)
assert result.security == {}
def test_post_cats(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"ears": {
"healthy": pet_healthy,
},
"extra": None,
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
assert result.body.healthy == pet_healthy
assert result.body.extra is None
def test_post_cats_boolean_string(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"ears": {
"healthy": pet_healthy,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
assert result.body.healthy is False
@pytest.mark.xfail(
reason="urlencoded object with oneof not supported",
strict=True,
)
def test_post_urlencoded(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
pet_healthy = False
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"healthy": pet_healthy,
"wings": {
"healthy": pet_healthy,
},
}
data = urlencode(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
userdata = {
"name": "user1",
}
userdata_json = json.dumps(userdata)
cookies = {
"user": "123",
"userdata": userdata_json,
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
content_type="application/x-www-form-urlencoded",
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert is_dataclass(result.parameters.cookie["userdata"])
assert (
result.parameters.cookie["userdata"].__class__.__name__
== "Userdata"
)
assert result.parameters.cookie["userdata"].name == "user1"
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
assert result.body.healthy == pet_healthy
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestSecurityUnmarshaller,
)
assert result.security == {}
def test_post_no_one_of_schema(self, spec):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
alias = "kitty"
data_json = {
"name": pet_name,
"alias": alias,
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
def test_post_cats_only_required_body(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_healthy = True
data_json = {
"name": pet_name,
"ears": {
"healthy": pet_healthy,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert not hasattr(result.body, "tag")
assert not hasattr(result.body, "address")
def test_post_pets_raises_invalid_mimetype(self, spec):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
data_json = {
"name": "Cat",
"tag": "cats",
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
content_type="text/html",
headers=headers,
cookies=cookies,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is MediaTypeNotFound
def test_post_pets_missing_cookie(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_healthy = True
data_json = {
"name": pet_name,
"ears": {
"healthy": pet_healthy,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
headers=headers,
)
with pytest.raises(MissingRequiredParameter):
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert not hasattr(result.body, "tag")
assert not hasattr(result.body, "address")
def test_post_pets_missing_header(self, spec, spec_dict):
host_url = "https://staging.gigantic-server.com/v1"
path_pattern = "/v1/pets"
pet_name = "Cat"
pet_healthy = True
data_json = {
"name": pet_name,
"ears": {
"healthy": pet_healthy,
},
}
data = json.dumps(data_json).encode()
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
cookies=cookies,
)
with pytest.raises(MissingRequiredParameter):
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert not hasattr(result.body, "tag")
assert not hasattr(result.body, "address")
def test_post_pets_raises_invalid_server_error(self, spec):
host_url = "http://flowerstore.swagger.io/v1"
path_pattern = "/v1/pets"
data_json = {
"name": "Cat",
"tag": "cats",
}
data = json.dumps(data_json).encode()
headers = {
"api-key": "12345",
}
cookies = {
"user": "123",
}
request = MockRequest(
host_url,
"POST",
"/pets",
path_pattern=path_pattern,
data=data,
content_type="text/html",
headers=headers,
cookies=cookies,
)
with pytest.raises(ServerNotFound):
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
with pytest.raises(ServerNotFound):
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
data_id = 1
data_name = "test"
data_json = {
"data": {
"id": data_id,
"name": data_name,
"ears": {
"healthy": True,
},
},
}
data = json.dumps(data_json).encode()
response = MockResponse(data)
with pytest.raises(ServerNotFound):
validate_response(
request,
response,
spec=spec,
cls=V30ResponseDataValidator,
)
def test_get_pet_invalid_security(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets/{petId}"
view_args = {
"petId": "1",
}
request = MockRequest(
host_url,
"GET",
"/pets/1",
path_pattern=path_pattern,
view_args=view_args,
)
with pytest.raises(SecurityValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestSecurityValidator,
)
assert exc_info.value.__cause__ == SecurityNotFound(
[["petstore_auth"]]
)
def test_get_pet(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets/{petId}"
view_args = {
"petId": "1",
}
auth = "authuser"
headers = {
"Authorization": f"Basic {auth}",
}
request = MockRequest(
host_url,
"GET",
"/pets/1",
path_pattern=path_pattern,
view_args=view_args,
headers=headers,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
path={
"petId": 1,
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestSecurityUnmarshaller,
)
assert result.security == {
"petstore_auth": auth,
}
data_id = 1
data_name = "test"
data_json = {
"data": {
"id": data_id,
"name": data_name,
"ears": {
"healthy": True,
},
},
}
data = json.dumps(data_json).encode()
response = MockResponse(data)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert is_dataclass(response_result.data.data)
assert response_result.data.data.id == data_id
assert response_result.data.data.name == data_name
def test_get_pet_not_found(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets/{petId}"
view_args = {
"petId": "1",
}
request = MockRequest(
host_url,
"GET",
"/pets/1",
path_pattern=path_pattern,
view_args=view_args,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
path={
"petId": 1,
}
)
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
code = 404
message = "Not found"
rootCause = "Pet not found"
data_json = {
"code": 404,
"message": message,
"rootCause": rootCause,
}
data = json.dumps(data_json).encode()
response = MockResponse(data, status_code=404)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.rootCause == rootCause
def test_get_pet_wildcard(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets/{petId}"
view_args = {
"petId": "1",
}
request = MockRequest(
host_url,
"GET",
"/pets/1",
path_pattern=path_pattern,
view_args=view_args,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters(
path={
"petId": 1,
}
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestBodyUnmarshaller,
)
assert result.body is None
data = b"imagedata"
response = MockResponse(data, content_type="image/png")
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert response_result.data == data
def test_get_tags(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
request = MockRequest(
host_url,
"GET",
"/tags",
path_pattern=path_pattern,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data_json = ["cats", "birds"]
data = json.dumps(data_json).encode()
response = MockResponse(data)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert response_result.data == data_json
def test_post_tags_extra_body_properties(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
pet_name = "Dog"
alias = "kitty"
data_json = {
"name": pet_name,
"alias": alias,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
def test_post_tags_empty_body(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
data_json = {}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
def test_post_tags_wrong_property_type(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
tag_name = 123
data = json.dumps(tag_name).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
def test_post_tags_additional_properties(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
pet_name = "Dog"
data_json = {
"name": pet_name,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert is_dataclass(result.body)
assert result.body.name == pet_name
code = 400
message = "Bad request"
rootCause = "Tag already exist"
additionalinfo = "Tag Dog already exist"
data_json = {
"code": code,
"message": message,
"rootCause": rootCause,
"additionalinfo": additionalinfo,
}
data = json.dumps(data_json).encode()
response = MockResponse(data, status_code=404)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.rootCause == rootCause
assert response_result.data.additionalinfo == additionalinfo
def test_post_tags_created_now(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
created = "now"
pet_name = "Dog"
data_json = {
"created": created,
"name": pet_name,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert is_dataclass(result.body)
assert result.body.created == created
assert result.body.name == pet_name
code = 400
message = "Bad request"
rootCause = "Tag already exist"
additionalinfo = "Tag Dog already exist"
data_json = {
"code": 400,
"message": "Bad request",
"rootCause": "Tag already exist",
"additionalinfo": "Tag Dog already exist",
}
data = json.dumps(data_json).encode()
response = MockResponse(data, status_code=404)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.rootCause == rootCause
assert response_result.data.additionalinfo == additionalinfo
def test_post_tags_created_datetime(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
created = "2016-04-16T16:06:05Z"
pet_name = "Dog"
data_json = {
"created": created,
"name": pet_name,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert is_dataclass(result.body)
assert result.body.created == datetime(
2016, 4, 16, 16, 6, 5, tzinfo=UTC
)
assert result.body.name == pet_name
code = 400
message = "Bad request"
rootCause = "Tag already exist"
additionalinfo = "Tag Dog already exist"
response_data_json = {
"code": code,
"message": message,
"rootCause": rootCause,
"additionalinfo": additionalinfo,
}
response_data = json.dumps(response_data_json).encode()
response = MockResponse(response_data, status_code=404)
result = unmarshal_response(
request,
response,
spec=spec,
cls=V30ResponseDataUnmarshaller,
)
assert is_dataclass(result.data)
assert result.data.code == code
assert result.data.message == message
assert result.data.rootCause == rootCause
assert result.data.additionalinfo == additionalinfo
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.rootCause == rootCause
assert response_result.data.additionalinfo == additionalinfo
def test_post_tags_urlencoded(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
created = "2016-04-16T16:06:05Z"
pet_name = "Dog"
data_json = {
"created": created,
"name": pet_name,
}
data = urlencode(data_json).encode()
content_type = "application/x-www-form-urlencoded"
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
content_type=content_type,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert is_dataclass(result.body)
assert result.body.created == datetime(
2016, 4, 16, 16, 6, 5, tzinfo=UTC
)
assert result.body.name == pet_name
code = 400
message = "Bad request"
rootCause = "Tag already exist"
additionalinfo = "Tag Dog already exist"
response_data_json = {
"code": code,
"message": message,
"rootCause": rootCause,
"additionalinfo": additionalinfo,
}
response_data = json.dumps(response_data_json).encode()
response = MockResponse(response_data, status_code=404)
result = unmarshal_response(
request,
response,
spec=spec,
cls=V30ResponseDataUnmarshaller,
)
assert is_dataclass(result.data)
assert result.data.code == code
assert result.data.message == message
assert result.data.rootCause == rootCause
assert result.data.additionalinfo == additionalinfo
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.rootCause == rootCause
assert response_result.data.additionalinfo == additionalinfo
def test_post_tags_created_invalid_type(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
created = "long time ago"
pet_name = "Dog"
data_json = {
"created": created,
"name": pet_name,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"POST",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
with pytest.raises(RequestBodyValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestBodyValidator,
)
assert type(exc_info.value.__cause__) is InvalidSchemaValue
code = 400
message = "Bad request"
correlationId = UUID("a8098c1a-f86e-11da-bd1a-00112444be1e")
rootCause = "Tag already exist"
additionalinfo = "Tag Dog already exist"
data_json = {
"message": message,
"correlationId": str(correlationId),
"rootCause": rootCause,
"additionalinfo": additionalinfo,
}
data = json.dumps(data_json).encode()
response = MockResponse(data, status_code=404)
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert is_dataclass(response_result.data)
assert response_result.data.code == code
assert response_result.data.message == message
assert response_result.data.correlationId == correlationId
assert response_result.data.rootCause == rootCause
assert response_result.data.additionalinfo == additionalinfo
def test_delete_tags_with_requestbody(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
ids = [1, 2, 3]
data_json = {
"ids": ids,
}
data = json.dumps(data_json).encode()
request = MockRequest(
host_url,
"DELETE",
"/tags",
path_pattern=path_pattern,
data=data,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert is_dataclass(result.body)
assert result.body.ids == ids
data = None
headers = {
"x-delete-confirm": "true",
}
response = MockResponse(data, status_code=200, headers=headers)
with pytest.warns(
DeprecationWarning, match="x-delete-confirm header is deprecated"
):
response_result = unmarshal_response(request, response, spec=spec)
assert response_result.errors == []
assert response_result.data is None
with pytest.warns(
DeprecationWarning, match="x-delete-confirm header is deprecated"
):
result = unmarshal_response(
request,
response,
spec=spec,
cls=V30ResponseHeadersUnmarshaller,
)
assert result.headers == {
"x-delete-confirm": True,
}
def test_delete_tags_no_requestbody(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
request = MockRequest(
host_url,
"DELETE",
"/tags",
path_pattern=path_pattern,
)
validate_request(request, spec=spec)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
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
):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/tags"
request = MockRequest(
host_url,
"DELETE",
"/tags",
path_pattern=path_pattern,
)
result = unmarshal_request(
request,
spec=spec,
cls=V30RequestParametersUnmarshaller,
)
assert result.parameters == Parameters()
result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
)
assert result.body is None
data = None
response = MockResponse(data, status_code=200)
response_result = response_unmarshaller.unmarshal(request, response)
assert response_result.errors == [
MissingRequiredHeader(name="x-delete-confirm"),
]
assert response_result.data is None
python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/ 0000775 0000000 0000000 00000000000 15163577675 0026200 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/test_read_only_write_only.py 0000664 0000000 0000000 00000006042 15163577675 0034042 0 ustar 00root root 0000000 0000000 import json
from dataclasses import is_dataclass
import pytest
from openapi_core.testing import MockRequest
from openapi_core.testing import MockResponse
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseUnmarshaller,
)
from openapi_core.validation.request.exceptions import InvalidRequestBody
from openapi_core.validation.response.exceptions import InvalidData
@pytest.fixture(scope="class")
def schema_path(schema_path_factory):
return schema_path_factory.from_file("data/v3.0/read_only_write_only.yaml")
@pytest.fixture(scope="class")
def request_unmarshaller(schema_path):
return V30RequestUnmarshaller(schema_path)
@pytest.fixture(scope="class")
def response_unmarshaller(schema_path):
return V30ResponseUnmarshaller(schema_path)
class TestReadOnly:
def test_write_a_read_only_property(self, request_unmarshaller):
data = json.dumps(
{
"id": 10,
"name": "Pedro",
}
).encode()
request = MockRequest(
host_url="", method="POST", path="/users", data=data
)
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidRequestBody
assert result.body is None
def test_read_only_property_response(self, response_unmarshaller):
data = json.dumps(
{
"id": 10,
"name": "Pedro",
}
).encode()
request = MockRequest(host_url="", method="POST", path="/users")
response = MockResponse(data)
result = response_unmarshaller.unmarshal(request, response)
assert not result.errors
assert is_dataclass(result.data)
assert result.data.__class__.__name__ == "User"
assert result.data.id == 10
assert result.data.name == "Pedro"
class TestWriteOnly:
def test_write_only_property(self, request_unmarshaller):
data = json.dumps(
{
"name": "Pedro",
"hidden": False,
}
).encode()
request = MockRequest(
host_url="", method="POST", path="/users", data=data
)
result = request_unmarshaller.unmarshal(request)
assert not result.errors
assert is_dataclass(result.body)
assert result.body.__class__.__name__ == "User"
assert result.body.name == "Pedro"
assert result.body.hidden == False
def test_read_a_write_only_property(self, response_unmarshaller):
data = json.dumps(
{
"id": 10,
"name": "Pedro",
"hidden": True,
}
).encode()
request = MockRequest(host_url="", method="POST", path="/users")
response = MockResponse(data)
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [InvalidData()]
assert result.data is None
python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/test_request_unmarshaller.py 0000664 0000000 0000000 00000034345 15163577675 0034067 0 ustar 00root root 0000000 0000000 import json
from base64 import b64encode
import pytest
from openapi_core import V30RequestUnmarshaller
from openapi_core.datatypes import Parameters
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.testing import MockRequest
from openapi_core.validation.request.exceptions import InvalidParameter
from openapi_core.validation.request.exceptions import MissingRequiredParameter
from openapi_core.validation.request.exceptions import (
MissingRequiredRequestBody,
)
from openapi_core.validation.request.exceptions import (
RequestBodyValidationError,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
class TestRequestUnmarshaller:
host_url = "http://petstore.swagger.io"
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture(scope="session")
def spec_dict(self, v30_petstore_content):
return v30_petstore_content
@pytest.fixture(scope="session")
def spec(self, v30_petstore_spec):
return v30_petstore_spec
@pytest.fixture(scope="session")
def request_unmarshaller(self, spec):
return V30RequestUnmarshaller(spec)
def test_request_server_error(self, request_unmarshaller):
request = MockRequest("http://petstore.invalid.net/v1", "get", "/")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == PathNotFound
assert result.body is None
assert result.parameters == Parameters()
def test_invalid_path(self, request_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == PathNotFound
assert result.body is None
assert result.parameters == Parameters()
def test_invalid_operation(self, request_unmarshaller):
request = MockRequest(self.host_url, "patch", "/v1/pets")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == OperationNotFound
assert result.body is None
assert result.parameters == Parameters()
def test_missing_parameter(self, request_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
with pytest.warns(DeprecationWarning):
result = request_unmarshaller.unmarshal(request)
assert type(result.errors[0]) == MissingRequiredParameter
assert result.body is None
assert result.parameters == Parameters(
query={
"page": 1,
"search": "",
},
)
def test_get_pets(self, request_unmarshaller):
args = {"limit": "10", "ids": ["1", "2"], "api_key": self.api_key}
request = MockRequest(
self.host_url,
"get",
"/v1/pets",
path_pattern="/v1/pets",
args=args,
)
with pytest.warns(DeprecationWarning):
result = request_unmarshaller.unmarshal(request)
assert result.errors == []
assert result.body is None
assert result.parameters == Parameters(
query={
"limit": 10,
"page": 1,
"search": "",
"ids": [1, 2],
},
)
assert result.security == {
"api_key": self.api_key,
}
def test_get_pets_multidict(self, request_unmarshaller):
from multidict import MultiDict
request = MockRequest(
self.host_url,
"get",
"/v1/pets",
path_pattern="/v1/pets",
)
request.parameters.query = MultiDict(
[("limit", "5"), ("ids", "1"), ("ids", "2")],
)
with pytest.warns(DeprecationWarning):
result = request_unmarshaller.unmarshal(request)
assert result.errors == []
assert result.body is None
assert result.parameters == Parameters(
query={
"limit": 5,
"page": 1,
"search": "",
"ids": [1, 2],
},
)
def test_missing_body(self, request_unmarshaller):
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
headers=headers,
cookies=cookies,
)
result = request_unmarshaller.unmarshal(request)
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={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
def test_invalid_content_type(self, request_unmarshaller):
data = b"csv,data"
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
content_type="text/csv",
data=data,
headers=headers,
cookies=cookies,
)
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == RequestBodyValidationError
assert result.errors[0].__cause__ == MediaTypeNotFound(
mimetype="text/csv",
availableMimetypes=[
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain",
],
)
assert result.body is None
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
def test_invalid_complex_parameter(self, request_unmarshaller, spec_dict):
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"ears": {
"healthy": True,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
userdata = {
"name": 1,
}
userdata_json = json.dumps(userdata)
cookies = {
"user": "123",
"userdata": userdata_json,
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
data=data,
headers=headers,
cookies=cookies,
)
result = request_unmarshaller.unmarshal(request)
assert result.errors == [
InvalidParameter(name="userdata", location="cookie")
]
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
assert result.security == {}
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
def test_post_pets(self, request_unmarshaller, spec_dict):
pet_name = "Cat"
pet_tag = "cats"
pet_street = "Piekna"
pet_city = "Warsaw"
data_json = {
"name": pet_name,
"tag": pet_tag,
"position": 2,
"address": {
"street": pet_street,
"city": pet_city,
},
"ears": {
"healthy": True,
},
}
data = json.dumps(data_json).encode()
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
data=data,
headers=headers,
cookies=cookies,
)
result = request_unmarshaller.unmarshal(request)
assert result.errors == []
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
assert result.security == {}
schemas = spec_dict["components"]["schemas"]
pet_model = schemas["PetCreate"]["x-model"]
address_model = schemas["Address"]["x-model"]
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
def test_post_pets_plain_no_schema(self, request_unmarshaller):
data = b"plain text"
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
data=data,
headers=headers,
cookies=cookies,
content_type="text/plain",
)
result = request_unmarshaller.unmarshal(request)
assert result.errors == []
assert result.parameters == Parameters(
header={
"api-key": self.api_key,
},
cookie={
"user": 123,
},
)
assert result.security == {}
assert result.body == data.decode()
def test_get_pet_unauthorized(self, request_unmarshaller):
request = MockRequest(
self.host_url,
"get",
"/v1/pets/1",
path_pattern="/v1/pets/{petId}",
view_args={"petId": "1"},
)
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) is SecurityValidationError
assert result.errors[0].__cause__ == SecurityNotFound(
[["petstore_auth"]]
)
assert result.body is None
assert result.parameters == Parameters()
assert result.security is None
def test_get_pet(self, request_unmarshaller):
authorization = "Basic " + self.api_key_encoded
headers = {
"Authorization": authorization,
}
request = MockRequest(
self.host_url,
"get",
"/v1/pets/1",
path_pattern="/v1/pets/{petId}",
view_args={"petId": "1"},
headers=headers,
)
result = request_unmarshaller.unmarshal(request)
assert result.errors == []
assert result.body is None
assert result.parameters == Parameters(
path={
"petId": 1,
},
)
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": []}
python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/test_response_unmarshaller.py 0000664 0000000 0000000 00000017530 15163577675 0034232 0 ustar 00root root 0000000 0000000 import json
from dataclasses import is_dataclass
import pytest
from openapi_core.deserializing.media_types.exceptions import (
MediaTypeDeserializeError,
)
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.responses.exceptions import ResponseNotFound
from openapi_core.testing import MockRequest
from openapi_core.testing import MockResponse
from openapi_core.unmarshalling.response.unmarshallers import (
V30ResponseUnmarshaller,
)
from openapi_core.validation.response.exceptions import DataValidationError
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
class TestResponseUnmarshaller:
host_url = "http://petstore.swagger.io"
@pytest.fixture(scope="session")
def spec_dict(self, v30_petstore_content):
return v30_petstore_content
@pytest.fixture(scope="session")
def spec(self, v30_petstore_spec):
return v30_petstore_spec
@pytest.fixture(scope="session")
def response_unmarshaller(self, spec):
return V30ResponseUnmarshaller(spec)
def test_invalid_server(self, response_unmarshaller):
request = MockRequest("http://petstore.invalid.net/v1", "get", "/")
response = MockResponse(b"Not Found", status_code=404)
result = response_unmarshaller.unmarshal(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == PathNotFound
assert result.data is None
assert result.headers == {}
def test_invalid_operation(self, response_unmarshaller):
request = MockRequest(self.host_url, "patch", "/v1/pets")
response = MockResponse(b"Not Found", status_code=404)
result = response_unmarshaller.unmarshal(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == OperationNotFound
assert result.data is None
assert result.headers == {}
def test_invalid_response(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"Not Found", status_code=409)
result = response_unmarshaller.unmarshal(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == ResponseNotFound
assert result.data is None
assert result.headers == {}
def test_invalid_content_type(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"Not Found", content_type="text/csv")
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [DataValidationError()]
assert type(result.errors[0].__cause__) == MediaTypeNotFound
assert result.data is None
assert result.headers == {}
def test_missing_body(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(None)
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [MissingData()]
assert result.data is None
assert result.headers == {}
def test_invalid_media_type(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"abcde")
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [DataValidationError()]
assert result.errors[0].__cause__ == MediaTypeDeserializeError(
mimetype="application/json", value=b"abcde"
)
assert result.data is None
assert result.headers == {}
def test_invalid_media_type_value(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"{}")
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [InvalidData()]
assert type(result.errors[0].__cause__) == InvalidSchemaValue
assert result.data is None
assert result.headers == {}
def test_invalid_value(self, response_unmarshaller):
request = MockRequest(self.host_url, "get", "/v1/tags")
response_json = {
"data": [
{"id": 1, "name": "Sparky"},
],
}
response_data = json.dumps(response_json)
response = MockResponse(response_data)
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [InvalidData()]
assert type(result.errors[0].__cause__) == InvalidSchemaValue
assert result.data is None
assert result.headers == {}
def test_invalid_header(self, response_unmarshaller):
userdata = {
"name": 1,
}
userdata_json = json.dumps(userdata)
cookies = {
"user": "123",
"userdata": userdata_json,
}
request = MockRequest(
self.host_url,
"delete",
"/v1/tags",
path_pattern="/v1/tags",
cookies=cookies,
)
response_json = {
"data": [
{
"id": 1,
"name": "Sparky",
"ears": {
"healthy": True,
},
},
],
}
response_data = json.dumps(response_json).encode()
headers = {
"x-delete-confirm": "true",
"x-delete-date": "today",
}
response = MockResponse(response_data, headers=headers)
with pytest.warns(DeprecationWarning):
result = response_unmarshaller.unmarshal(request, response)
assert result.errors == [InvalidHeader(name="x-delete-date")]
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 = {
"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 == []
assert is_dataclass(result.data)
assert len(result.data.data) == 1
assert result.data.data[0].id == 1
assert result.data.data[0].name == "Sparky"
assert result.headers == {}
test_response_unmarshaller_response_properties_default_policy.py 0000664 0000000 0000000 00000006172 15163577675 0043430 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling 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 == []
python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/test_security_override.py 0000664 0000000 0000000 00000005321 15163577675 0033360 0 ustar 00root root 0000000 0000000 from base64 import b64encode
import pytest
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.testing import MockRequest
from openapi_core.unmarshalling.request.unmarshallers import (
V30RequestUnmarshaller,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
@pytest.fixture(scope="class")
def schema_path(schema_path_factory):
return schema_path_factory.from_file("data/v3.0/security_override.yaml")
@pytest.fixture(scope="class")
def request_unmarshaller(schema_path):
return V30RequestUnmarshaller(schema_path)
class TestSecurityOverride:
host_url = "http://petstore.swagger.io"
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
def test_default(self, request_unmarshaller):
args = {"api_key": self.api_key}
request = MockRequest(self.host_url, "get", "/resource/one", args=args)
result = request_unmarshaller.unmarshal(request)
assert not result.errors
assert result.security == {
"api_key": self.api_key,
}
def test_default_invalid(self, request_unmarshaller):
request = MockRequest(self.host_url, "get", "/resource/one")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) is SecurityValidationError
assert type(result.errors[0].__cause__) is SecurityNotFound
assert result.security is None
def test_override(self, request_unmarshaller):
authorization = "Basic " + self.api_key_encoded
headers = {
"Authorization": authorization,
}
request = MockRequest(
self.host_url, "post", "/resource/one", headers=headers
)
result = request_unmarshaller.unmarshal(request)
assert not result.errors
assert result.security == {
"petstore_auth": self.api_key_encoded,
}
def test_override_invalid(self, request_unmarshaller):
request = MockRequest(self.host_url, "post", "/resource/one")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) is SecurityValidationError
assert type(result.errors[0].__cause__) is SecurityNotFound
assert result.security is None
def test_remove(self, request_unmarshaller):
request = MockRequest(self.host_url, "put", "/resource/one")
result = request_unmarshaller.unmarshal(request)
assert not result.errors
assert result.security == {}
python-openapi-openapi-core-d6cdb4f/tests/integration/unmarshalling/test_unmarshallers.py 0000664 0000000 0000000 00000175435 15163577675 0032510 0 ustar 00root root 0000000 0000000 from datetime import date
from datetime import datetime
from uuid import UUID
from uuid import uuid4
import pytest
from isodate.tzinfo import UTC
from isodate.tzinfo import FixedOffset
from jsonschema.exceptions import SchemaError
from jsonschema.exceptions import UnknownType
from jsonschema_path import SchemaPath
from openapi_core.unmarshalling.schemas import (
oas30_read_schema_unmarshallers_factory,
)
from openapi_core.unmarshalling.schemas import (
oas30_write_schema_unmarshallers_factory,
)
from openapi_core.unmarshalling.schemas import (
oas31_schema_unmarshallers_factory,
)
from openapi_core.unmarshalling.schemas.exceptions import (
FormatterNotFoundError,
)
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
class BaseTestOASSchemaUnmarshallersFactoryCall:
@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, spec, unmarshallers_factory):
schema_dict = {
"deprecated": True,
}
schema = SchemaPath.from_dict(schema_dict)
with pytest.warns(DeprecationWarning):
unmarshallers_factory.create(spec, schema)
def test_create_formatter_not_found(self, spec, unmarshallers_factory):
custom_format = "custom"
schema_dict = {
"type": "string",
"format": custom_format,
}
schema = SchemaPath.from_dict(schema_dict)
with pytest.raises(
FormatterNotFoundError,
match="Formatter not found for custom format",
):
unmarshallers_factory.create(spec, schema)
@pytest.mark.parametrize(
"value",
[
"test",
10,
10,
3.12,
["one", "two"],
True,
False,
],
)
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)
assert result == value
@pytest.mark.parametrize(
"type,value",
[
("string", "test"),
("integer", 10),
("number", 10),
("number", 3.12),
("array", ["one", "two"]),
("boolean", True),
("boolean", False),
],
)
def test_basic_types(self, spec, unmarshallers_factory, type, value):
schema_dict = {
"type": type,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"type,value",
[
("string", 10),
("string", 3.14),
("string", True),
("string", ["one", "two"]),
("string", {"one": "two"}),
("integer", 3.14),
("integer", True),
("integer", ""),
("integer", "test"),
("integer", b"test"),
("integer", ["one", "two"]),
("integer", {"one": "two"}),
("number", True),
("number", ""),
("number", "test"),
("number", b"test"),
("number", ["one", "two"]),
("number", {"one": "two"}),
("array", 10),
("array", 3.14),
("array", True),
("array", ""),
("array", "test"),
("array", b"test"),
("array", {"one": "two"}),
("boolean", 10),
("boolean", 3.14),
("boolean", ""),
("boolean", "test"),
("boolean", b"test"),
("boolean", ["one", "two"]),
("boolean", {"one": "two"}),
("object", 10),
("object", 3.14),
("object", True),
("object", ""),
("object", "test"),
("object", b"test"),
("object", ["one", "two"]),
],
)
def test_basic_types_invalid(
self, spec, unmarshallers_factory, type, value
):
schema_dict = {
"type": type,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(
InvalidSchemaValue,
match=f"not valid for schema of type {type}",
) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"is not of type '{type}'"
in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize(
"format,value,unmarshalled",
[
("int32", 13, 13),
("int64", 13, 13),
("float", 3.14, 3.14),
("double", 3.14, 3.14),
("password", "passwd", "passwd"),
("date", "2018-12-13", date(2018, 12, 13)),
(
"date-time",
"2018-12-13T13:34:59Z",
datetime(2018, 12, 13, 13, 34, 59, tzinfo=UTC),
),
(
"date-time",
"2018-12-13T13:34:59+02:00",
datetime(2018, 12, 13, 13, 34, 59, tzinfo=FixedOffset(2)),
),
(
"uuid",
"20a53f2e-0049-463d-b2b4-3fbbbb4cd8a7",
UUID("20a53f2e-0049-463d-b2b4-3fbbbb4cd8a7"),
),
],
)
def test_basic_formats(
self, spec, unmarshallers_factory, format, value, unmarshalled
):
schema_dict = {
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == unmarshalled
@pytest.mark.parametrize(
"type,format,value,unmarshalled",
[
("integer", "int32", 13, 13),
("integer", "int64", 13, 13),
("number", "float", 3.14, 3.14),
("number", "double", 3.14, 3.14),
("string", "password", "passwd", "passwd"),
("string", "date", "2018-12-13", date(2018, 12, 13)),
(
"string",
"date-time",
"2018-12-13T13:34:59Z",
datetime(2018, 12, 13, 13, 34, 59, tzinfo=UTC),
),
(
"string",
"date-time",
"2018-12-13T13:34:59+02:00",
datetime(2018, 12, 13, 13, 34, 59, tzinfo=FixedOffset(2)),
),
(
"string",
"uuid",
"20a53f2e-0049-463d-b2b4-3fbbbb4cd8a7",
UUID("20a53f2e-0049-463d-b2b4-3fbbbb4cd8a7"),
),
],
)
def test_basic_type_formats(
self, spec, unmarshallers_factory, type, format, value, unmarshalled
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == unmarshalled
@pytest.mark.parametrize(
"type,format,value",
[
("string", "float", "test"),
("string", "double", "test"),
("number", "date", 3),
("number", "date-time", 3),
("number", "uuid", 3),
],
)
def test_basic_type_formats_ignored(
self, spec, unmarshallers_factory, type, format, value
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"type,format,value",
[
("string", "date", "test"),
("string", "date-time", "test"),
("string", "uuid", "test"),
],
)
def test_basic_type_formats_invalid(
self, spec, unmarshallers_factory, type, format, value
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"is not a '{format}'" in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize(
"value,expected",
[
("dGVzdA==", "test"),
],
)
def test_string_byte(self, spec, unmarshallers_factory, value, expected):
schema_dict = {
"type": "string",
"format": "byte",
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == expected
def test_string_date(self, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
"format": "date",
}
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)
@pytest.mark.parametrize(
"value,expected",
[
("2018-01-02T00:00:00Z", datetime(2018, 1, 2, 0, 0, tzinfo=UTC)),
(
"2020-04-01T12:00:00+02:00",
datetime(2020, 4, 1, 12, 0, 0, tzinfo=FixedOffset(2)),
),
],
)
def test_string_datetime(
self, spec, unmarshallers_factory, value, expected
):
schema_dict = {
"type": "string",
"format": "date-time",
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
"format": "date-time",
}
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:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
"is not a 'date-time'" in exc_info.value.schema_errors[0].message
)
def test_string_password(self, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
"format": "password",
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
"format": "uuid",
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
"format": "uuid",
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = "test"
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert "is not a 'uuid'" in exc_info.value.schema_errors[0].message
@pytest.mark.parametrize(
"type,format,value,expected",
[
("string", "float", "test", "test"),
("string", "double", "test", "test"),
("integer", "byte", 10, 10),
("integer", "date", 10, 10),
("integer", "date-time", 10, 10),
("string", "int32", "test", "test"),
("string", "int64", "test", "test"),
("integer", "password", 10, 10),
],
)
def test_formats_ignored(
self, spec, unmarshallers_factory, type, format, value, expected
):
schema_dict = {
"type": type,
"format": format,
}
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, spec, unmarshallers_factory, value):
schema_dict = {
"type": "string",
"pattern": "bar",
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value,pattern",
[
("foo", "baz"),
("bar", "baz"),
],
)
def test_string_pattern_invalid(
self, spec, unmarshallers_factory, value, pattern
):
schema_dict = {
"type": "string",
"pattern": pattern,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"'{value}' does not match '{pattern}'"
in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize("value", ["abc", "abcd"])
def test_string_min_length(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "string",
"minLength": 3,
}
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, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "string",
"minLength": 3,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"'{value}' is too short"
in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize("value", ["", "a"])
def test_string_max_length(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "string",
"maxLength": 1,
}
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, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "string",
"maxLength": 1,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"'{value}' is too long" in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize(
"value",
[
"",
],
)
def test_string_max_length_invalid_schema(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "string",
"maxLength": -1,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
def test_integer_enum(self, spec, unmarshallers_factory):
schema_dict = {
"type": "integer",
"enum": [1, 2, 3],
}
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, spec, unmarshallers_factory):
enum = [1, 2, 3]
schema_dict = {
"type": "integer",
"enum": enum,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = 12
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"{value} is not one of {enum}"
in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize(
"type,value",
[
("string", "test"),
("integer", 10),
("number", 10),
("number", 3.12),
("array", ["one", "two"]),
("boolean", True),
("boolean", False),
],
)
def test_array(self, spec, unmarshallers_factory, type, value):
schema_dict = {
"type": "array",
"items": {
"type": type,
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value_list = [value] * 3
result = unmarshaller.unmarshal(value_list)
assert result == value_list
@pytest.mark.parametrize(
"type,value",
[
("integer", True),
("integer", "123"),
("string", 123),
("string", True),
("boolean", 123),
("boolean", "123"),
],
)
def test_array_invalid(self, spec, unmarshallers_factory, type, value):
schema_dict = {
"type": "array",
"items": {
"type": type,
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal([value])
assert len(exc_info.value.schema_errors) == 1
assert (
f"is not of type '{type}'"
in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize("value", [[], [1], [1, 2]])
def test_array_min_items_invalid(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "array",
"items": {
"type": "number",
},
"minItems": 3,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"{value} is too short" in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize("value", [[], [1], [1, 2]])
def test_array_min_items(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "array",
"items": {
"type": "number",
},
"minItems": 0,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
[],
],
)
def test_array_max_items_invalid_schema(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "array",
"items": {
"type": "number",
},
"maxItems": -1,
}
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, spec, unmarshallers_factory, value):
schema_dict = {
"type": "array",
"items": {
"type": "number",
},
"maxItems": 1,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"{value} is too long" in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize("value", [[1, 2, 1], [2, 2]])
def test_array_unique_items_invalid(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "array",
"items": {
"type": "number",
},
"uniqueItems": True,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"{value} has non-unique elements"
in exc_info.value.schema_errors[0].message
)
def test_object_any_of(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"anyOf": [
{
"type": "object",
"required": ["someint"],
"properties": {"someint": {"type": "integer"}},
},
{
"type": "object",
"required": ["somestr"],
"properties": {"somestr": {"type": "string"}},
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"anyOf": [
{
"type": "object",
"required": ["someint"],
"properties": {"someint": {"type": "integer"}},
},
{
"type": "object",
"required": ["somestr"],
"properties": {"somestr": {"type": "string"}},
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"oneOf": [
{
"type": "object",
"properties": {
"somestr": {
"type": "string",
"default": "defaultstring",
},
},
},
{
"type": "object",
"required": ["otherstr"],
"properties": {
"otherstr": {
"type": "string",
},
},
},
],
"properties": {
"someint": {
"type": "integer",
},
},
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"anyOf": [
{
"type": "object",
"properties": {
"someint": {
"type": "integer",
},
},
},
{
"type": "object",
"properties": {
"somestr": {
"type": "string",
"default": "defaultstring",
},
},
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"allOf": [
{
"type": "object",
"properties": {
"somestr": {
"type": "string",
"default": "defaultstring",
},
},
},
{
"type": "object",
"properties": {
"someint": {
"type": "integer",
"default": 1,
},
},
},
],
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
assert unmarshaller.unmarshal({}) == {
"someint": 1,
"somestr": "defaultstring",
}
@pytest.mark.parametrize(
"value",
[
{
"someint": 123,
},
{
"somestr": "content",
},
{
"somestr": "content",
"someint": 123,
},
],
)
def test_object_with_properties(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "object",
"properties": {
"somestr": {
"type": "string",
},
"someint": {
"type": "integer",
},
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{
"somestr": {},
"someint": 123,
},
{
"somestr": ["content1", "content2"],
"someint": 123,
},
{
"somestr": 123,
"someint": 123,
},
{
"somestr": "content",
"someint": 123,
"not_in_scheme_prop": 123,
},
],
)
def test_object_with_properties_invalid(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "object",
"properties": {
"somestr": {
"type": "string",
},
"someint": {
"type": "integer",
},
},
"additionalProperties": False,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_object_default_property(self, spec, unmarshallers_factory, value):
schema_dict = {
"type": "object",
"properties": {
"prop": {
"type": "string",
"default": "value1",
}
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == {"prop": "value1"}
@pytest.mark.parametrize(
"value",
[
{"additional": 1},
],
)
def test_object_additional_properties_false(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "object",
"additionalProperties": False,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{"additional": 1},
{"foo": "bar", "bar": "foo"},
{"additional": {"bar": 1}},
],
)
@pytest.mark.parametrize("additional_properties", [True, {}])
def test_object_additional_properties_free_form_object(
self, value, additional_properties, spec, unmarshallers_factory
):
schema_dict = {
"type": "object",
"additionalProperties": additional_properties,
}
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, 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]})
assert result == {
"user_ids": [1, 2, 3, 4],
}
@pytest.mark.parametrize(
"value",
[
{"additional": 1},
],
)
def test_object_additional_properties(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "object",
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{"additional": 1},
],
)
def test_object_additional_properties_object(
self, spec, unmarshallers_factory, value
):
additional_properties = {
"type": "integer",
}
schema_dict = {
"type": "object",
"additionalProperties": additional_properties,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{"a": 1},
{"a": 1, "b": 2},
{"a": 1, "b": 2, "c": 3},
],
)
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,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{"a": 1},
{"a": 1, "b": 2},
{"a": 1, "b": 2, "c": 3},
],
)
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,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_object_min_properties_invalid_schema(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "object",
"minProperties": 2,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{"a": 1},
{"a": 1, "b": 2},
{"a": 1, "b": 2, "c": 3},
],
)
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,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{"a": 1},
{"a": 1, "b": 2},
{"a": 1, "b": 2, "c": 3},
],
)
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,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_object_max_properties_invalid_schema(
self, spec, unmarshallers_factory, value
):
schema_dict = {
"type": "object",
"maxProperties": -1,
}
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, spec, unmarshallers_factory):
schema_dict = {
"oneOf": [
{
"type": "string",
},
{
"type": "array",
"items": {
"type": "string",
},
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"anyOf": [
{
"type": "string",
},
{
"type": "array",
"items": {
"type": "string",
},
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"allOf": [
{
"type": "array",
"items": {
"type": "string",
},
}
],
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = ["hello"]
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{
"somestr": {},
"someint": 123,
},
{
"somestr": ["content1", "content2"],
"someint": 123,
},
{
"somestr": 123,
"someint": 123,
},
{
"somestr": "content",
"someint": 123,
"not_in_scheme_prop": 123,
},
],
)
def test_any_all_of_invalid_properties(
self, value, spec, unmarshallers_factory
):
schema_dict = {
"allOf": [
{
"type": "object",
"required": ["somestr"],
"properties": {
"somestr": {
"type": "string",
},
},
},
{
"type": "object",
"required": ["someint"],
"properties": {
"someint": {
"type": "integer",
},
},
},
],
"additionalProperties": False,
}
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, spec, unmarshallers_factory):
schema_dict = {
"format": "date",
"oneOf": [
{"type": "integer"},
{
"type": "string",
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"oneOf": [
{"type": "integer"},
{
"type": "string",
"format": "date",
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"anyOf": [
{},
{
"type": "string",
"format": "date",
},
],
}
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, spec, unmarshallers_factory):
schema_dict = {
"allOf": [
{},
{
"type": "string",
"format": "date",
},
],
}
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)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_any_of_no_valid(self, spec, unmarshallers_factory, value):
any_of = [
{
"type": "object",
"required": ["test1"],
"properties": {
"test1": {
"type": "string",
},
},
},
{
"type": "object",
"required": ["test2"],
"properties": {
"test2": {
"type": "string",
},
},
},
]
schema_dict = {
"anyOf": any_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_any_one_of_no_valid(self, spec, unmarshallers_factory, value):
one_of = [
{
"type": "object",
"required": [
"test1",
],
"properties": {
"test1": {
"type": "string",
},
},
},
{
"type": "object",
"required": [
"test2",
],
"properties": {
"test2": {
"type": "string",
},
},
},
]
schema_dict = {
"oneOf": one_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_any_any_of_different_type(
self, spec, unmarshallers_factory, value
):
any_of = [{"type": "integer"}, {"type": "string"}]
schema_dict = {
"anyOf": any_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_any_one_of_different_type(
self, spec, unmarshallers_factory, value
):
one_of = [
{
"type": "integer",
},
{
"type": "string",
},
]
schema_dict = {
"oneOf": one_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{
"foo": "FOO",
},
{
"foo": "FOO",
"bar": "BAR",
},
],
)
def test_any_any_of_unambiguous(self, spec, unmarshallers_factory, value):
any_of = [
{
"type": "object",
"required": ["foo"],
"properties": {
"foo": {
"type": "string",
},
},
"additionalProperties": False,
},
{
"type": "object",
"required": ["foo", "bar"],
"properties": {
"foo": {
"type": "string",
},
"bar": {
"type": "string",
},
},
"additionalProperties": False,
},
]
schema_dict = {
"anyOf": any_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
{},
],
)
def test_object_multiple_any_of(self, spec, unmarshallers_factory, value):
any_of = [
{
"type": "object",
},
{
"type": "object",
},
]
schema_dict = {
"type": "object",
"anyOf": any_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.parametrize(
"value",
[
dict(),
],
)
def test_object_multiple_one_of(self, spec, unmarshallers_factory, value):
one_of = [
{
"type": "object",
},
{
"type": "object",
},
]
schema_dict = {
"type": "object",
"oneOf": one_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
@pytest.mark.parametrize(
"value",
[
{
"foo": "FOO",
},
{
"foo": "FOO",
"bar": "BAR",
},
],
)
def test_any_one_of_unambiguous(self, spec, unmarshallers_factory, value):
one_of = [
{
"type": "object",
"required": [
"foo",
],
"properties": {
"foo": {
"type": "string",
},
},
"additionalProperties": False,
},
{
"type": "object",
"required": ["foo", "bar"],
"properties": {
"foo": {
"type": "string",
},
"bar": {
"type": "string",
},
},
"additionalProperties": False,
},
]
schema_dict = {
"oneOf": one_of,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == value
class BaseTestOASS30chemaUnmarshallersFactoryCall:
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)
@pytest.mark.parametrize(
"type",
[
"boolean",
"array",
"integer",
"number",
"string",
],
)
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)
assert result is None
@pytest.mark.parametrize(
"type",
[
"boolean",
"array",
"integer",
"number",
"string",
],
)
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,
match=f"not valid for schema of type {type}",
) as exc_info:
unmarshaller.unmarshal(None)
assert len(exc_info.value.schema_errors) == 2
assert (
"None for not nullable" in exc_info.value.schema_errors[0].message
)
assert (
f"None is not of type '{type}'"
in exc_info.value.schema_errors[1].message
)
@pytest.mark.parametrize(
"type,format,value,unmarshalled",
[
("string", "byte", "dGVzdA==", "test"),
("string", "binary", b"test", b"test"),
],
)
def test_basic_type_oas30_formats(
self, spec, unmarshallers_factory, type, format, value, unmarshalled
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == unmarshalled
@pytest.mark.parametrize(
"type,format,value",
[
("string", "byte", "passwd"),
],
)
def test_basic_type_oas30_formats_invalid(
self, spec, unmarshallers_factory, type, format, value
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(
InvalidSchemaValue,
match=f"not valid for schema of type {type}",
) as exc_info:
unmarshaller.unmarshal(value)
assert len(exc_info.value.schema_errors) == 1
assert (
f"is not a '{format}'" in exc_info.value.schema_errors[0].message
)
@pytest.mark.xfail(
reason=(
"OAS 3.0 string type checker allows byte. "
"See https://github.com/python-openapi/openapi-schema-validator/issues/64"
),
strict=True,
)
def test_string_format_binary_invalid(self, spec, unmarshallers_factory):
schema_dict = {
"type": "string",
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = b"true"
with pytest.raises(
InvalidSchemaValue,
match=f"not valid for schema of type {type}",
):
unmarshaller.unmarshal(value)
@pytest.mark.xfail(
reason=(
"Rraises TypeError not SchemaError. "
"See ttps://github.com/python-openapi/openapi-schema-validator/issues/65"
),
strict=True,
)
@pytest.mark.parametrize(
"types,value",
[
(["string", "null"], "string"),
(["number", "null"], 2),
(["number", "null"], 3.14),
(["boolean", "null"], True),
(["array", "null"], [1, 2]),
(["object", "null"], {}),
],
)
def test_nultiple_types_undefined(
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(SchemaError):
unmarshaller.unmarshal(value)
def test_integer_default_nullable(self, spec, unmarshallers_factory):
default_value = 123
schema_dict = {
"type": "integer",
"default": default_value,
"nullable": True,
}
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, spec, unmarshallers_factory):
schema_dict = {
"type": "array",
"items": {
"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
def test_object_property_nullable(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"properties": {
"foo": {
"type": "object",
"nullable": True,
}
},
}
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,
BaseTestOASS30chemaUnmarshallersFactoryCall,
):
@pytest.fixture
def unmarshallers_factory(self):
return oas30_write_schema_unmarshallers_factory
def test_write_only_properties(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"writeOnly": True,
}
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = {"id": 10}
# readOnly properties may be admitted in a Response context
result = unmarshaller.unmarshal(value)
assert result == value
def test_read_only_properties_invalid(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"readOnly": True,
}
},
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
value = {"id": 10}
# readOnly properties are not admitted on a Request context
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
class TestOAS30ResponseSchemaUnmarshallersFactory(
BaseTestOASSchemaUnmarshallersFactoryCall,
BaseTestOASS30chemaUnmarshallersFactoryCall,
):
@pytest.fixture
def unmarshallers_factory(self):
return oas30_read_schema_unmarshallers_factory
def test_read_only_properties(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"readOnly": True,
}
},
}
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})
assert result == {
"id": 10,
}
def test_write_only_properties_invalid(self, spec, unmarshallers_factory):
schema_dict = {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"writeOnly": True,
}
},
}
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):
unmarshaller.unmarshal({"id": 10})
class TestOAS31SchemaUnmarshallersFactory(
BaseTestOASSchemaUnmarshallersFactoryCall
):
@pytest.fixture
def unmarshallers_factory(self):
return oas31_schema_unmarshallers_factory
@pytest.mark.xfail(
reason=(
"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,
)
@pytest.mark.parametrize(
"type,format",
[
("string", "byte"),
("string", "binary"),
],
)
def test_create_oas30_formatter_not_found(
self, spec, unmarshallers_factory, type, format
):
schema_dict = {
"type": type,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
with pytest.raises(FormatterNotFoundError):
unmarshallers_factory.create(spec, schema)
@pytest.mark.parametrize(
"type,value",
[
("string", b"test"),
("integer", b"test"),
("number", b"test"),
("array", b"test"),
("boolean", b"test"),
("object", b"test"),
],
)
def test_basic_types_invalid(
self, spec, unmarshallers_factory, type, value
):
schema_dict = {
"type": type,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
with pytest.raises(
InvalidSchemaValue,
match=f"not valid for schema of type {type}",
):
unmarshaller.unmarshal(value)
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, 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)
assert len(exc_info.value.schema_errors) == 1
assert (
"is not of type 'null'" in exc_info.value.schema_errors[0].message
)
@pytest.mark.parametrize(
"types,value",
[
(["string", "null"], "string"),
(["number", "null"], 2),
(["number", "null"], 3.14),
(["boolean", "null"], True),
(["array", "null"], [1, 2]),
(["object", "null"], {}),
],
)
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)
assert result == value
@pytest.mark.parametrize(
"types,value",
[
(["string", "null"], 2),
(["number", "null"], "string"),
(["number", "null"], True),
(["boolean", "null"], 3.14),
(["array", "null"], {}),
(["object", "null"], [1, 2]),
],
)
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)
assert len(exc_info.value.schema_errors) == 1
assert "is not of type" in exc_info.value.schema_errors[0].message
@pytest.mark.parametrize(
"types,format,value,expected",
[
(["string", "null"], "date", None, None),
(["string", "null"], "date", "2018-12-13", date(2018, 12, 13)),
],
)
def test_multiple_types_format_valid_or_ignored(
self, spec, unmarshallers_factory, types, format, value, expected
):
schema_dict = {
"type": types,
"format": format,
}
schema = SchemaPath.from_dict(schema_dict)
unmarshaller = unmarshallers_factory.create(spec, schema)
result = unmarshaller.unmarshal(value)
assert result == expected
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
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/ 0000775 0000000 0000000 00000000000 15163577675 0025466 5 ustar 00root root 0000000 0000000 test_additional_properties_default_policy.py 0000664 0000000 0000000 00000013473 15163577675 0036477 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/validation 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)
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/test_dialect_validators.py 0000664 0000000 0000000 00000011163 15163577675 0032736 0 ustar 00root root 0000000 0000000 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)
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/test_parent_reference.py 0000664 0000000 0000000 00000002703 15163577675 0032410 0 ustar 00root root 0000000 0000000 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)
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/test_request_validators.py 0000664 0000000 0000000 00000017505 15163577675 0033027 0 ustar 00root root 0000000 0000000 from base64 import b64encode
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
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
from openapi_core.testing import MockRequest
from openapi_core.validation.request.exceptions import MissingRequiredParameter
from openapi_core.validation.request.exceptions import (
RequestBodyValidationError,
)
from openapi_core.validation.request.exceptions import SecurityValidationError
class TestRequestValidator:
host_url = "http://petstore.swagger.io"
api_key = "12345"
@property
def api_key_encoded(self):
api_key_bytes = self.api_key.encode("utf8")
api_key_bytes_enc = b64encode(api_key_bytes)
return str(api_key_bytes_enc, "utf8")
@pytest.fixture(scope="session")
def spec_dict(self, v30_petstore_content):
return v30_petstore_content
@pytest.fixture(scope="session")
def spec(self, v30_petstore_spec):
return v30_petstore_spec
@pytest.fixture(scope="session")
def request_validator(self, spec):
return V30RequestValidator(spec)
def test_request_server_error(self, request_validator):
request = MockRequest("http://petstore.invalid.net/v1", "get", "/")
with pytest.raises(PathNotFound):
request_validator.validate(request)
def test_path_not_found(self, request_validator):
request = MockRequest(self.host_url, "get", "/v1")
with pytest.raises(PathNotFound):
request_validator.validate(request)
def test_operation_not_found(self, request_validator):
request = MockRequest(self.host_url, "patch", "/v1/pets")
with pytest.raises(OperationNotFound):
request_validator.validate(request)
def test_missing_parameter(self, request_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
with pytest.raises(MissingRequiredParameter):
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,
"get",
"/v1/pets/1",
path_pattern="/v1/pets/{petId}",
view_args={"petId": "1"},
)
with pytest.raises(SecurityValidationError) as exc_info:
request_validator.validate(request)
assert exc_info.value.__cause__ == SecurityNotFound(
[["petstore_auth"]]
)
def test_media_type_not_found(self, request_validator):
data = b"csv,data"
headers = {
"api-key": self.api_key_encoded,
}
cookies = {
"user": "123",
}
request = MockRequest(
"https://development.gigantic-server.com",
"post",
"/v1/pets",
path_pattern="/v1/pets",
content_type="text/csv",
data=data,
headers=headers,
cookies=cookies,
)
with pytest.raises(RequestBodyValidationError) as exc_info:
request_validator.validate(request)
assert exc_info.value.__cause__ == MediaTypeNotFound(
mimetype="text/csv",
availableMimetypes=[
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain",
],
)
def test_valid(self, request_validator):
authorization = "Basic " + self.api_key_encoded
headers = {
"Authorization": authorization,
}
request = MockRequest(
self.host_url,
"get",
"/v1/pets/1",
path_pattern="/v1/pets/{petId}",
view_args={"petId": "1"},
headers=headers,
)
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
test_response_properties_default_policy.py 0000664 0000000 0000000 00000011533 15163577675 0036220 0 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/integration/validation 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)
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/test_response_validators.py 0000664 0000000 0000000 00000014547 15163577675 0033200 0 ustar 00root root 0000000 0000000 import json
import pytest
from openapi_core import V30ResponseValidator
from openapi_core.deserializing.media_types.exceptions import (
MediaTypeDeserializeError,
)
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.responses.exceptions import ResponseNotFound
from openapi_core.testing import MockRequest
from openapi_core.testing import MockResponse
from openapi_core.validation.response.exceptions import DataValidationError
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
class TestResponseValidator:
host_url = "http://petstore.swagger.io"
@pytest.fixture(scope="session")
def spec_dict(self, v30_petstore_content):
return v30_petstore_content
@pytest.fixture(scope="session")
def spec(self, v30_petstore_spec):
return v30_petstore_spec
@pytest.fixture(scope="session")
def response_validator(self, spec):
return V30ResponseValidator(spec)
def test_invalid_server(self, response_validator):
request = MockRequest("http://petstore.invalid.net/v1", "get", "/")
response = MockResponse(b"Not Found", status_code=404)
with pytest.raises(PathNotFound):
response_validator.validate(request, response)
def test_invalid_operation(self, response_validator):
request = MockRequest(self.host_url, "patch", "/v1/pets")
response = MockResponse(b"Not Found", status_code=404)
with pytest.raises(OperationNotFound):
response_validator.validate(request, response)
def test_invalid_response(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"Not Found", status_code=409)
with pytest.raises(ResponseNotFound):
response_validator.validate(request, response)
def test_invalid_content_type(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"Not Found", content_type="text/csv")
with pytest.raises(DataValidationError) as exc_info:
response_validator.validate(request, response)
assert type(exc_info.value.__cause__) == MediaTypeNotFound
def test_missing_body(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(None)
with pytest.raises(MissingData):
response_validator.validate(request, response)
def test_invalid_media_type(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"abcde")
with pytest.raises(DataValidationError) as exc_info:
response_validator.validate(request, response)
assert exc_info.value.__cause__ == MediaTypeDeserializeError(
mimetype="application/json", value=b"abcde"
)
def test_invalid_media_type_value(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/pets")
response = MockResponse(b"{}")
with pytest.raises(DataValidationError) as exc_info:
response_validator.validate(request, response)
assert type(exc_info.value.__cause__) == InvalidSchemaValue
def test_invalid_value(self, response_validator):
request = MockRequest(self.host_url, "get", "/v1/tags")
response_json = {
"data": [
{"id": 1, "name": "Sparky"},
],
}
response_data = json.dumps(response_json).encode()
response = MockResponse(response_data)
with pytest.raises(InvalidData) as exc_info:
response_validator.validate(request, response)
assert type(exc_info.value.__cause__) == InvalidSchemaValue
def test_invalid_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()
headers = {
"x-delete-confirm": "true",
"x-delete-date": "today",
}
response = MockResponse(response_data, headers=headers)
with pytest.raises(InvalidHeader):
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 = {
"data": [
{
"id": 1,
"name": "Sparky",
"ears": {
"healthy": True,
},
},
],
}
response_data = json.dumps(response_json).encode()
response = MockResponse(response_data)
result = response_validator.validate(request, response)
assert result is None
python-openapi-openapi-core-d6cdb4f/tests/integration/validation/test_strict_json_validation.py 0000664 0000000 0000000 00000024416 15163577675 0033661 0 ustar 00root root 0000000 0000000 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)
python-openapi-openapi-core-d6cdb4f/tests/unit/ 0000775 0000000 0000000 00000000000 15163577675 0021770 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/casting/ 0000775 0000000 0000000 00000000000 15163577675 0023420 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/casting/test_schema_casters.py 0000664 0000000 0000000 00000010101 15163577675 0030006 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.casting.schemas import oas31_schema_casters_factory
from openapi_core.casting.schemas.exceptions import CastError
class TestSchemaCaster:
@pytest.fixture
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(spec, schema)
return create_caster
@pytest.mark.parametrize(
"schema_type,value,expected",
[
("integer", "2", 2),
("number", "3.14", 3.14),
("boolean", "false", False),
("boolean", "true", True),
],
)
def test_primitive_flat(
self, caster_factory, schema_type, value, expected
):
spec = {
"type": schema_type,
}
schema = SchemaPath.from_dict(spec)
result = caster_factory(schema).cast(value)
assert result == expected
def test_array_invalid_type(self, caster_factory):
spec = {
"type": "array",
"items": {
"type": "number",
},
}
schema = SchemaPath.from_dict(spec)
value = ["test", "test2"]
with pytest.raises(CastError):
caster_factory(schema).cast(value)
@pytest.mark.parametrize("value", [3.14, "foo", b"foo"])
def test_array_invalid_value(self, value, caster_factory):
spec = {
"type": "array",
"items": {
"oneOf": [{"type": "number"}, {"type": "string"}],
},
}
schema = SchemaPath.from_dict(spec)
with pytest.raises(
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
python-openapi-openapi-core-d6cdb4f/tests/unit/conftest.py 0000664 0000000 0000000 00000003030 15163577675 0024163 0 ustar 00root root 0000000 0000000 from json import dumps
from os import unlink
from tempfile import NamedTemporaryFile
import pytest
from jsonschema_path import SchemaPath
@pytest.fixture
def spec_v20():
return SchemaPath.from_dict(
{
"swagger": "2.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
)
@pytest.fixture
def spec_v30():
return SchemaPath.from_dict(
{
"openapi": "3.0.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
)
@pytest.fixture
def spec_v31():
return SchemaPath.from_dict(
{
"openapi": "3.1.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
)
@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({})
@pytest.fixture
def create_file():
files = []
def create(schema):
contents = dumps(schema).encode("utf-8")
with NamedTemporaryFile(delete=False) as tf:
files.append(tf)
tf.write(contents)
return tf.name
yield create
for tf in files:
unlink(tf.name)
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/ 0000775 0000000 0000000 00000000000 15163577675 0023430 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/aiohttp/ 0000775 0000000 0000000 00000000000 15163577675 0025100 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/aiohttp/test_aiohttp_requests.py 0000664 0000000 0000000 00000000363 15163577675 0032116 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core.contrib.aiohttp.requests import AIOHTTPOpenAPIWebRequest
class TestAIOHTTPOpenAPIWebRequest:
def test_type_invalid(self):
with pytest.raises(TypeError):
AIOHTTPOpenAPIWebRequest(None)
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/aiohttp/test_aiohttp_responses.py 0000664 0000000 0000000 00000000367 15163577675 0032270 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core.contrib.aiohttp.responses import AIOHTTPOpenAPIWebResponse
class TestAIOHTTPOpenAPIWebResponse:
def test_type_invalid(self):
with pytest.raises(TypeError):
AIOHTTPOpenAPIWebResponse(None)
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/django/ 0000775 0000000 0000000 00000000000 15163577675 0024672 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/django/test_django.py 0000664 0000000 0000000 00000016003 15163577675 0027545 0 ustar 00root root 0000000 0000000 import pytest
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.contrib.django import DjangoOpenAPIRequest
from openapi_core.contrib.django import DjangoOpenAPIResponse
from openapi_core.datatypes import RequestParameters
class BaseTestDjango:
@pytest.fixture(autouse=True, scope="module")
def django_settings(self):
import django
from django.conf import settings
from django.contrib import admin
from django.urls import path
from django.urls import re_path
if settings.configured:
from django.utils.functional import empty
settings._wrapped = empty
settings.configure(
SECRET_KEY="secretkey",
ALLOWED_HOSTS=[
"testserver",
],
INSTALLED_APPS=[
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.messages",
"django.contrib.sessions",
],
MIDDLEWARE=[
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
],
)
django.setup()
settings.ROOT_URLCONF = (
path("admin/", admin.site.urls),
re_path("^test/test-regexp/$", lambda d: None),
re_path("^object/(?P[^/.]+)/action/$", lambda d: None),
)
@pytest.fixture
def request_factory(self):
from django.test.client import RequestFactory
return RequestFactory()
@pytest.fixture
def response_factory(self):
from django.http import HttpResponse
def create(content=b"", status_code=None):
return HttpResponse(content, status=status_code)
return create
class TestDjangoOpenAPIRequest(BaseTestDjango):
def test_type_invalid(self):
with pytest.raises(TypeError):
DjangoOpenAPIRequest(None)
def test_no_resolver(self, request_factory):
data = {"test1": "test2"}
request = request_factory.get("/admin/", data)
openapi_request = DjangoOpenAPIRequest(request)
assert openapi_request.parameters == RequestParameters(
path={},
query=ImmutableMultiDict([("test1", "test2")]),
header=Headers({"Cookie": ""}),
cookie={},
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == request._current_scheme_host
assert openapi_request.path == request.path
assert openapi_request.path_pattern is None
assert openapi_request.body == b""
assert openapi_request.content_type == request.content_type
def test_simple(self, request_factory):
from django.urls import resolve
request = request_factory.get("/admin/")
request.resolver_match = resolve(request.path)
openapi_request = DjangoOpenAPIRequest(request)
assert openapi_request.parameters == RequestParameters(
path={},
query={},
header=Headers({"Cookie": ""}),
cookie={},
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == request._current_scheme_host
assert openapi_request.path == request.path
assert openapi_request.path_pattern == request.path
assert openapi_request.body == b""
assert openapi_request.content_type == request.content_type
def test_url_rule(self, request_factory):
from django.urls import resolve
request = request_factory.get("/admin/auth/group/1/")
request.resolver_match = resolve(request.path)
openapi_request = DjangoOpenAPIRequest(request)
assert openapi_request.parameters == RequestParameters(
path={"object_id": "1"},
query={},
header=Headers({"Cookie": ""}),
cookie={},
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == request._current_scheme_host
assert openapi_request.path == request.path
assert openapi_request.path_pattern == "/admin/auth/group/{object_id}/"
assert openapi_request.body == b""
assert openapi_request.content_type == request.content_type
def test_url_regexp_pattern(self, request_factory):
from django.urls import resolve
request = request_factory.get("/test/test-regexp/")
request.resolver_match = resolve(request.path)
openapi_request = DjangoOpenAPIRequest(request)
assert openapi_request.parameters == RequestParameters(
path={},
query={},
header=Headers({"Cookie": ""}),
cookie={},
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == request._current_scheme_host
assert openapi_request.path == request.path
assert openapi_request.path_pattern == request.path
assert openapi_request.body == b""
assert openapi_request.content_type == request.content_type
def test_drf_default_value_pattern(self, request_factory):
from django.urls import resolve
request = request_factory.get("/object/123/action/")
request.resolver_match = resolve(request.path)
openapi_request = DjangoOpenAPIRequest(request)
assert openapi_request.parameters == RequestParameters(
path={"pk": "123"},
query={},
header=Headers({"Cookie": ""}),
cookie={},
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == request._current_scheme_host
assert openapi_request.path == request.path
assert openapi_request.path_pattern == "/object/{pk}/action/"
assert openapi_request.body == b""
assert openapi_request.content_type == request.content_type
class TestDjangoOpenAPIResponse(BaseTestDjango):
def test_type_invalid(self):
with pytest.raises(TypeError):
DjangoOpenAPIResponse(None)
def test_stream_response(self, response_factory):
response = response_factory()
response.writelines(["foo\n", "bar\n", "baz\n"])
openapi_response = DjangoOpenAPIResponse(response)
assert openapi_response.data == b"foo\nbar\nbaz\n"
assert openapi_response.status_code == response.status_code
assert openapi_response.content_type == response["Content-Type"]
def test_redirect_response(self, response_factory):
data = b"/redirected/"
response = response_factory(data, status_code=302)
openapi_response = DjangoOpenAPIResponse(response)
assert openapi_response.data == data
assert openapi_response.status_code == response.status_code
assert openapi_response.content_type == response["Content-Type"]
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/flask/ 0000775 0000000 0000000 00000000000 15163577675 0024530 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/flask/conftest.py 0000664 0000000 0000000 00000003432 15163577675 0026731 0 ustar 00root root 0000000 0000000 import pytest
from flask.wrappers import Request
from flask.wrappers import Response
from werkzeug.routing import Map
from werkzeug.routing import Rule
from werkzeug.routing import Subdomain
from werkzeug.test import create_environ
@pytest.fixture
def environ_factory():
return create_environ
@pytest.fixture
def map():
return Map(
[
# Static URLs
Rule("/", endpoint="static/index"),
Rule("/about", endpoint="static/about"),
Rule("/help", endpoint="static/help"),
# Knowledge Base
Subdomain(
"kb",
[
Rule("/", endpoint="kb/index"),
Rule("/browse/", endpoint="kb/browse"),
Rule("/browse//", endpoint="kb/browse"),
Rule("/browse//", endpoint="kb/browse"),
],
),
],
default_subdomain="www",
)
@pytest.fixture
def request_factory(map, environ_factory):
server_name = "localhost"
def create_request(method, path, subdomain=None, query_string=None):
environ = environ_factory(query_string=query_string)
req = Request(environ)
urls = map.bind_to_environ(
environ, server_name=server_name, subdomain=subdomain
)
req.url_rule, req.view_args = urls.match(
path, method, return_rule=True
)
return req
return create_request
@pytest.fixture
def response_factory():
def create_response(
data, status_code=200, headers=None, content_type="application/json"
):
return Response(
data,
status=status_code,
headers=headers,
content_type=content_type,
)
return create_response
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/flask/test_flask_requests.py 0000664 0000000 0000000 00000005434 15163577675 0031202 0 ustar 00root root 0000000 0000000 import pytest
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.contrib.flask import FlaskOpenAPIRequest
from openapi_core.datatypes import RequestParameters
class TestFlaskOpenAPIRequest:
def test_type_invalid(self):
with pytest.raises(TypeError):
FlaskOpenAPIRequest(None)
def test_simple(self, request_factory, request):
request = request_factory("GET", "/", subdomain="www")
openapi_request = FlaskOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict([])
headers = Headers(request.headers)
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
assert openapi_request.method == "get"
assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path
assert openapi_request.body == b""
assert openapi_request.content_type == "application/octet-stream"
def test_multiple_values(self, request_factory, request):
request = request_factory(
"GET", "/", subdomain="www", query_string="a=b&a=c"
)
openapi_request = FlaskOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict(
[
("a", "b"),
("a", "c"),
]
)
headers = Headers(request.headers)
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
assert openapi_request.method == "get"
assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path
assert openapi_request.body == b""
assert openapi_request.content_type == "application/octet-stream"
def test_url_rule(self, request_factory, request):
request = request_factory("GET", "/browse/12/", subdomain="kb")
openapi_request = FlaskOpenAPIRequest(request)
path = {"id": 12}
query = ImmutableMultiDict([])
headers = Headers(request.headers)
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
assert openapi_request.method == "get"
assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path
assert openapi_request.path_pattern == "/browse/{id}/"
assert openapi_request.body == b""
assert openapi_request.content_type == "application/octet-stream"
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/flask/test_flask_responses.py 0000664 0000000 0000000 00000001161 15163577675 0031341 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core.contrib.flask import FlaskOpenAPIResponse
class TestFlaskOpenAPIResponse:
def test_type_invalid(self):
with pytest.raises(TypeError):
FlaskOpenAPIResponse(None)
def test_invalid_server(self, response_factory):
data = b"Not Found"
status_code = 404
response = response_factory(data, status_code=status_code)
openapi_response = FlaskOpenAPIResponse(response)
assert openapi_response.data == data
assert openapi_response.status_code == status_code
assert openapi_response.content_type == response.mimetype
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/requests/ 0000775 0000000 0000000 00000000000 15163577675 0025303 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/requests/conftest.py 0000664 0000000 0000000 00000002417 15163577675 0027506 0 ustar 00root root 0000000 0000000 from io import BytesIO
from urllib.parse import parse_qs
from urllib.parse import urljoin
import pytest
from requests.models import Request
from requests.models import Response
from requests.structures import CaseInsensitiveDict
from urllib3.response import HTTPResponse
@pytest.fixture
def request_factory():
schema = "http"
server_name = "localhost"
def create_request(
method,
path,
subdomain=None,
query_string="",
content_type="application/json",
):
base_url = "://".join([schema, server_name])
url = urljoin(base_url, path)
params = parse_qs(query_string)
headers = {
"Content-Type": content_type,
}
return Request(method, url, params=params, headers=headers)
return create_request
@pytest.fixture
def response_factory():
def create_response(
data, status_code=200, content_type="application/json"
):
fp = BytesIO(data)
raw = HTTPResponse(fp, preload_content=False)
resp = Response()
resp.headers = CaseInsensitiveDict(
{
"Content-Type": content_type,
}
)
resp.status_code = status_code
resp.raw = raw
return resp
return create_response
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/requests/test_requests_requests.py 0000664 0000000 0000000 00000011571 15163577675 0032527 0 ustar 00root root 0000000 0000000 import pytest
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.contrib.requests import RequestsOpenAPIRequest
from openapi_core.datatypes import RequestParameters
class TestRequestsOpenAPIRequest:
def test_type_invalid(self):
with pytest.raises(TypeError):
RequestsOpenAPIRequest(None)
def test_simple(self, request_factory, request):
request = request_factory("GET", "/", subdomain="www")
openapi_request = RequestsOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict([])
headers = Headers(dict(request.headers))
cookies = {}
prepared = request.prepare()
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == "http://localhost"
assert openapi_request.path == "/"
assert openapi_request.body == prepared.body
assert openapi_request.content_type == "application/json"
def test_multiple_values(self, request_factory, request):
request = request_factory(
"GET", "/", subdomain="www", query_string="a=b&a=c"
)
openapi_request = RequestsOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict(
[
("a", "b"),
("a", "c"),
]
)
headers = Headers(dict(request.headers))
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
prepared = request.prepare()
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == "http://localhost"
assert openapi_request.path == "/"
assert openapi_request.body == prepared.body
assert openapi_request.content_type == "application/json"
def test_url_rule(self, request_factory, request):
request = request_factory("GET", "/browse/12/", subdomain="kb")
openapi_request = RequestsOpenAPIRequest(request)
# empty when not bound to spec
path = {}
query = ImmutableMultiDict([])
headers = Headers(
{
"Content-Type": "application/json",
}
)
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
prepared = request.prepare()
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == "http://localhost"
assert openapi_request.path == "/browse/12/"
assert openapi_request.body == prepared.body
assert openapi_request.content_type == "application/json"
def test_hash_param(self, request_factory, request):
request = request_factory("GET", "/browse/#12", subdomain="kb")
openapi_request = RequestsOpenAPIRequest(request)
# empty when not bound to spec
path = {}
query = ImmutableMultiDict([])
headers = Headers(
{
"Content-Type": "application/json",
}
)
cookies = {}
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
prepared = request.prepare()
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == "http://localhost"
assert openapi_request.path == "/browse/#12"
assert openapi_request.body == prepared.body
assert openapi_request.content_type == "application/json"
def test_content_type_with_charset(self, request_factory, request):
request = request_factory(
"GET",
"/",
subdomain="www",
content_type="application/json; charset=utf-8",
)
openapi_request = RequestsOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict([])
headers = Headers(dict(request.headers))
cookies = {}
prepared = request.prepare()
assert openapi_request.parameters == RequestParameters(
path=path,
query=query,
header=headers,
cookie=cookies,
)
assert openapi_request.method == request.method.lower()
assert openapi_request.host_url == "http://localhost"
assert openapi_request.path == "/"
assert openapi_request.body == prepared.body
assert (
openapi_request.content_type == "application/json; charset=utf-8"
)
python-openapi-openapi-core-d6cdb4f/tests/unit/contrib/requests/test_requests_responses.py 0000664 0000000 0000000 00000001257 15163577675 0032675 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core.contrib.requests import RequestsOpenAPIResponse
class TestRequestsOpenAPIResponse:
def test_type_invalid(self):
with pytest.raises(TypeError):
RequestsOpenAPIResponse(None)
def test_invalid_server(self, response_factory):
data = b"Not Found"
status_code = 404
response = response_factory(data, status_code=status_code)
openapi_response = RequestsOpenAPIResponse(response)
assert openapi_response.data == data
assert openapi_response.status_code == status_code
mimetype = response.headers.get("Content-Type")
assert openapi_response.content_type == mimetype
python-openapi-openapi-core-d6cdb4f/tests/unit/deserializing/ 0000775 0000000 0000000 00000000000 15163577675 0024621 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/deserializing/test_media_types_deserializers.py 0000664 0000000 0000000 00000050511 15163577675 0033464 0 ustar 00root root 0000000 0000000 from xml.etree.ElementTree import Element
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 as default_media_type_deserializers,
)
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
)
from openapi_core.validation.schemas import oas31_schema_validators_factory
class TestMediaTypeDeserializer:
@pytest.fixture
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=default_media_type_deserializers,
extra_media_type_deserializers=None,
):
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,
)
return create_deserializer
@pytest.mark.parametrize(
"mimetype,parameters,value,expected",
[
(
"text/plain",
{"charset": "iso-8859-2"},
b"\xb1\xb6\xbc\xe6",
"ąśźć",
),
(
"text/plain",
{"charset": "utf-8"},
b"\xc4\x85\xc5\x9b\xc5\xba\xc4\x87",
"ąśźć",
),
("text/plain", {}, b"\xc4\x85\xc5\x9b\xc5\xba\xc4\x87", "ąśźć"),
("text/plain", {}, "somestr", "somestr"),
("text/html", {}, "somestr", "somestr"),
],
)
def test_plain_valid(
self, deserializer_factory, mimetype, parameters, value, expected
):
deserializer = deserializer_factory(mimetype, parameters=parameters)
result = deserializer.deserialize(value)
assert result == expected
@pytest.mark.parametrize(
"mimetype",
[
"application/json",
"application/vnd.api+json",
],
)
def test_json_valid(self, deserializer_factory, mimetype):
parameters = {"charset": "utf-8"}
deserializer = deserializer_factory(mimetype, parameters=parameters)
value = b'{"test": "test"}'
result = deserializer.deserialize(value)
assert type(result) is dict
assert result == {"test": "test"}
@pytest.mark.parametrize(
"mimetype",
[
"application/json",
"application/vnd.api+json",
],
)
def test_json_empty(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = b""
with pytest.raises(DeserializeError):
deserializer.deserialize(value)
@pytest.mark.parametrize(
"mimetype",
[
"application/json",
"application/vnd.api+json",
],
)
def test_json_empty_object(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = b"{}"
result = deserializer.deserialize(value)
assert result == {}
@pytest.mark.parametrize(
"mimetype",
[
"application/xml",
"application/xhtml+xml",
],
)
def test_xml_empty(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = b""
with pytest.raises(DeserializeError):
deserializer.deserialize(value)
@pytest.mark.parametrize(
"mimetype",
[
"application/xml",
"application/xhtml+xml",
],
)
def test_xml_default_charset_valid(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = b"text"
result = deserializer.deserialize(value)
assert type(result) is Element
@pytest.mark.parametrize(
"mimetype",
[
"application/xml",
"application/xhtml+xml",
],
)
def test_xml_valid(self, deserializer_factory, mimetype):
parameters = {"charset": "utf-8"}
deserializer = deserializer_factory(mimetype, parameters=parameters)
value = b"text"
result = deserializer.deserialize(value)
assert type(result) is Element
def test_octet_stream_empty(self, deserializer_factory):
mimetype = "application/octet-stream"
deserializer = deserializer_factory(mimetype)
value = b""
result = deserializer.deserialize(value)
assert result == b""
@pytest.mark.parametrize(
"mimetype",
[
"image/gif",
"image/png",
],
)
def test_octet_stream_implicit(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = b""
result = deserializer.deserialize(value)
assert result == value
def test_octet_stream_simple(self, deserializer_factory):
mimetype = "application/octet-stream"
schema_dict = {}
schema = SchemaPath.from_dict(schema_dict)
deserializer = deserializer_factory(mimetype, schema=schema)
value = b"test"
result = deserializer.deserialize(value)
assert result == b"test"
def test_urlencoded_form_empty(self, deserializer_factory):
mimetype = "application/x-www-form-urlencoded"
schema_dict = {}
schema = SchemaPath.from_dict(schema_dict)
deserializer = deserializer_factory(mimetype, schema=schema)
value = b""
result = deserializer.deserialize(value)
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 = {
"type": "object",
"properties": {
"name": {
"type": "string",
},
},
}
schema = SchemaPath.from_dict(schema_dict)
encoding_dict = {
"name": {
"style": "form",
},
}
encoding = SchemaPath.from_dict(encoding_dict)
deserializer = deserializer_factory(
mimetype, schema=schema, encoding=encoding
)
value = b"name=foo+bar"
result = deserializer.deserialize(value)
assert result == {
"name": "foo bar",
}
def test_urlencoded_complex_cast_error(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=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": [1, 2, 3],
}
def test_urlencoded_content_type(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)
encoding_dict = {
"prop": {
"contentType": "application/json",
},
}
encoding = SchemaPath.from_dict(encoding_dict)
deserializer = deserializer_factory(
mimetype, schema=schema, encoding=encoding
)
value = b'prop=["a","b","c"]'
result = deserializer.deserialize(value)
assert result == {
"prop": ["a", "b", "c"],
}
def test_urlencoded_deepobject(self, deserializer_factory):
mimetype = "application/x-www-form-urlencoded"
schema_dict = {
"type": "object",
"properties": {
"color": {
"type": "object",
"properties": {
"R": {
"type": "integer",
},
"G": {
"type": "integer",
},
"B": {
"type": "integer",
},
},
},
},
}
schema = SchemaPath.from_dict(schema_dict)
encoding_dict = {
"color": {
"style": "deepObject",
"explode": True,
},
}
encoding = SchemaPath.from_dict(encoding_dict)
deserializer = deserializer_factory(
mimetype, schema=schema, encoding=encoding
)
value = b"color[R]=100&color[G]=200&color[B]=150"
result = deserializer.deserialize(value)
assert result == {
"color": {
"R": 100,
"G": 200,
"B": 150,
},
}
def test_multipart_form_empty(self, deserializer_factory):
mimetype = "multipart/form-data"
schema_dict = {}
schema = SchemaPath.from_dict(schema_dict)
deserializer = deserializer_factory(mimetype, schema=schema)
value = b""
result = deserializer.deserialize(value)
assert result == {}
def test_multipart_form_simple(self, deserializer_factory):
mimetype = "multipart/form-data"
schema_dict = {
"type": "object",
"properties": {
"param1": {
"type": "string",
"format": "binary",
},
"param2": {
"type": "string",
"format": "binary",
},
},
}
schema = SchemaPath.from_dict(schema_dict)
encoding_dict = {
"param1": {
"contentType": "application/octet-stream",
},
}
encoding = SchemaPath.from_dict(encoding_dict)
parameters = {
"boundary": "===============2872712225071193122==",
}
deserializer = deserializer_factory(
mimetype, schema=schema, parameters=parameters, encoding=encoding
)
value = (
b"--===============2872712225071193122==\n"
b"Content-Type: text/plain\nMIME-Version: 1.0\n"
b'Content-Disposition: form-data; name="param1"\n\ntest\n'
b"--===============2872712225071193122==\n"
b"Content-Type: text/plain\nMIME-Version: 1.0\n"
b'Content-Disposition: form-data; name="param2"\n\ntest2\n'
b"--===============2872712225071193122==--\n"
)
result = deserializer.deserialize(value)
assert result == {
"param1": b"test",
"param2": b"test2",
}
def test_multipart_form_array(self, deserializer_factory):
mimetype = "multipart/form-data"
schema_dict = {
"type": "object",
"properties": {
"file": {
"type": "array",
"items": {},
},
},
}
schema = SchemaPath.from_dict(schema_dict)
parameters = {
"boundary": "===============2872712225071193122==",
}
deserializer = deserializer_factory(
mimetype, schema=schema, parameters=parameters
)
value = (
b"--===============2872712225071193122==\n"
b"Content-Type: text/plain\nMIME-Version: 1.0\n"
b'Content-Disposition: form-data; name="file"\n\ntest\n'
b"--===============2872712225071193122==\n"
b"Content-Type: text/plain\nMIME-Version: 1.0\n"
b'Content-Disposition: form-data; name="file"\n\ntest2\n'
b"--===============2872712225071193122==--\n"
)
result = deserializer.deserialize(value)
assert result == {
"file": [b"test", b"test2"],
}
def test_custom_simple(self, deserializer_factory):
deserialized = "x-custom"
def custom_deserializer(value):
return deserialized
custom_mimetype = "application/custom"
extra_media_type_deserializers = {
custom_mimetype: custom_deserializer,
}
deserializer = deserializer_factory(
custom_mimetype,
extra_media_type_deserializers=extra_media_type_deserializers,
)
value = b"{}"
result = deserializer.deserialize(
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": []}
python-openapi-openapi-core-d6cdb4f/tests/unit/deserializing/test_styles_deserializers.py 0000664 0000000 0000000 00000032427 15163577675 0032512 0 ustar 00root root 0000000 0000000 import pytest
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
from openapi_core.deserializing.styles.factories import (
StyleDeserializersFactory,
)
from openapi_core.schema.parameters import get_style_and_explode
class TestParameterStyleDeserializer:
@pytest.fixture
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(
spec, schema, style, explode, name=name
)
return create_deserializer
@pytest.mark.parametrize(
"location_name", ["cookie", "header", "query", "path"]
)
@pytest.mark.parametrize("value", ["", "test"])
def test_unsupported(self, deserializer_factory, location_name, value):
name = "param"
schema_type = "string"
spec = {
"name": name,
"in": location_name,
"style": "unsupported",
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
location = {name: value}
with pytest.warns(UserWarning):
result = deserializer.deserialize(location)
assert result == value
@pytest.mark.parametrize(
"location_name,style,explode,schema_type,location",
[
("query", "matrix", False, "string", {";param": "invalid"}),
("query", "matrix", False, "array", {";param": "invalid"}),
("query", "matrix", False, "object", {";param": "invalid"}),
("query", "matrix", True, "string", {";param*": "invalid"}),
("query", "deepObject", True, "object", {"param": "invalid"}),
("query", "form", True, "array", {}),
],
)
def test_name_not_found(
self,
deserializer_factory,
location_name,
style,
explode,
schema_type,
location,
):
name = "param"
spec = {
"name": name,
"in": location_name,
"style": style,
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
with pytest.raises(KeyError):
deserializer.deserialize(location)
@pytest.mark.parametrize(
"location_name,style,explode,schema_type,location",
[
("path", "deepObject", False, "string", {"param": "invalid"}),
("path", "deepObject", False, "array", {"param": "invalid"}),
("path", "deepObject", False, "object", {"param": "invalid"}),
("path", "deepObject", True, "string", {"param": "invalid"}),
("path", "deepObject", True, "array", {"param": "invalid"}),
("path", "spaceDelimited", False, "string", {"param": "invalid"}),
("path", "pipeDelimited", False, "string", {"param": "invalid"}),
],
)
def test_combination_not_available(
self,
deserializer_factory,
location_name,
style,
explode,
schema_type,
location,
):
name = "param"
spec = {
"name": name,
"in": location_name,
"style": style,
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
with pytest.raises(DeserializeError):
deserializer.deserialize(location)
@pytest.mark.parametrize(
"explode,schema_type,location,expected",
[
(False, "string", {";param": ";param=blue"}, "blue"),
(True, "string", {";param*": ";param=blue"}, "blue"),
(
False,
"array",
{";param": ";param=blue,black,brown"},
["blue", "black", "brown"],
),
(
True,
"array",
{";param*": ";param=blue;param=black;param=brown"},
["blue", "black", "brown"],
),
(
False,
"object",
{";param": ";param=R,100,G,200,B,150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
(
True,
"object",
{";param*": ";R=100;G=200;B=150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_matrix_valid(
self, deserializer_factory, explode, schema_type, location, expected
):
name = "param"
spec = {
"name": name,
"in": "path",
"style": "matrix",
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
result = deserializer.deserialize(location)
assert result == expected
@pytest.mark.parametrize(
"explode,schema_type,location,expected",
[
(False, "string", {".param": ".blue"}, "blue"),
(True, "string", {".param*": ".blue"}, "blue"),
(
False,
"array",
{".param": ".blue,black,brown"},
["blue", "black", "brown"],
),
(
True,
"array",
{".param*": ".blue.black.brown"},
["blue", "black", "brown"],
),
(
False,
"object",
{".param": ".R,100,G,200,B,150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
(
True,
"object",
{".param*": ".R=100.G=200.B=150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_label_valid(
self, deserializer_factory, explode, schema_type, location, expected
):
name = "param"
spec = {
"name": name,
"in": "path",
"style": "label",
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
result = deserializer.deserialize(location)
assert result == expected
@pytest.mark.parametrize("location_name", ["query", "cookie"])
@pytest.mark.parametrize(
"explode,schema_type,location,expected",
[
(False, "string", {"param": "blue"}, "blue"),
(True, "string", {"param": "blue"}, "blue"),
(
False,
"array",
{"param": "blue,black,brown"},
["blue", "black", "brown"],
),
(
True,
"array",
ImmutableMultiDict(
[("param", "blue"), ("param", "black"), ("param", "brown")]
),
["blue", "black", "brown"],
),
(
False,
"object",
{"param": "R,100,G,200,B,150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
(
True,
"object",
{"param": "R=100&G=200&B=150"},
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_form_valid(
self,
deserializer_factory,
location_name,
explode,
schema_type,
location,
expected,
):
name = "param"
spec = {
"name": name,
"in": location_name,
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
result = deserializer.deserialize(location)
assert result == expected
@pytest.mark.parametrize("location_name", ["path", "header"])
@pytest.mark.parametrize(
"explode,schema_type,value,expected",
[
(False, "string", "blue", "blue"),
(True, "string", "blue", "blue"),
(False, "array", "blue,black,brown", ["blue", "black", "brown"]),
(True, "array", "blue,black,brown", ["blue", "black", "brown"]),
(
False,
"object",
"R,100,G,200,B,150",
{
"R": "100",
"G": "200",
"B": "150",
},
),
(
True,
"object",
"R=100,G=200,B=150",
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_simple_valid(
self,
deserializer_factory,
location_name,
explode,
schema_type,
value,
expected,
):
name = "param"
spec = {
"name": name,
"in": location_name,
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
location = {name: value}
result = deserializer.deserialize(location)
assert result == expected
@pytest.mark.parametrize(
"schema_type,value,expected",
[
("array", "blue%20black%20brown", ["blue", "black", "brown"]),
(
"object",
"R%20100%20G%20200%20B%20150",
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_space_delimited_valid(
self, deserializer_factory, schema_type, value, expected
):
name = "param"
spec = {
"name": name,
"in": "query",
"style": "spaceDelimited",
"explode": False,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
location = {name: value}
result = deserializer.deserialize(location)
assert result == expected
@pytest.mark.parametrize(
"schema_type,value,expected",
[
("array", "blue|black|brown", ["blue", "black", "brown"]),
(
"object",
"R|100|G|200|B|150",
{
"R": "100",
"G": "200",
"B": "150",
},
),
],
)
def test_pipe_delimited_valid(
self, deserializer_factory, schema_type, value, expected
):
name = "param"
spec = {
"name": name,
"in": "query",
"style": "pipeDelimited",
"explode": False,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
location = {name: value}
result = deserializer.deserialize(location)
assert result == expected
def test_deep_object_valid(self, deserializer_factory):
name = "param"
spec = {
"name": name,
"in": "query",
"style": "deepObject",
"explode": True,
"schema": {
"type": "object",
},
}
param = SchemaPath.from_dict(spec)
deserializer = deserializer_factory(param)
location = {
"param[R]": "100",
"param[G]": "200",
"param[B]": "150",
"other[0]": "value",
}
result = deserializer.deserialize(location)
assert result == {
"R": "100",
"G": "200",
"B": "150",
}
python-openapi-openapi-core-d6cdb4f/tests/unit/extensions/ 0000775 0000000 0000000 00000000000 15163577675 0024167 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/extensions/test_factories.py 0000664 0000000 0000000 00000002525 15163577675 0027563 0 ustar 00root root 0000000 0000000 from dataclasses import dataclass
from dataclasses import is_dataclass
from sys import modules
from types import ModuleType
from typing import Any
import pytest
from jsonschema_path import SchemaPath
from openapi_core.extensions.models.factories import ModelPathFactory
class TestImportModelCreate:
@pytest.fixture
def loaded_model_class(self):
@dataclass
class BarModel:
a: str
b: int
foo_module = ModuleType("foo")
foo_module.BarModel = BarModel
modules["foo"] = foo_module
yield BarModel
del modules["foo"]
def test_dynamic_model(self):
factory = ModelPathFactory()
schema = SchemaPath.from_dict({"x-model": "TestModel"})
test_model_class = factory.create(schema, ["name"])
assert is_dataclass(test_model_class)
assert test_model_class.__name__ == "TestModel"
assert list(test_model_class.__dataclass_fields__.keys()) == ["name"]
assert str(test_model_class.__dataclass_fields__["name"].type) == str(
Any
)
def test_model_path(self, loaded_model_class):
factory = ModelPathFactory()
schema = SchemaPath.from_dict({"x-model-path": "foo.BarModel"})
test_model_class = factory.create(schema, ["a", "b"])
assert test_model_class == loaded_model_class
python-openapi-openapi-core-d6cdb4f/tests/unit/schema/ 0000775 0000000 0000000 00000000000 15163577675 0023230 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/schema/test_schema_parameters.py 0000664 0000000 0000000 00000006107 15163577675 0030330 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.schema.parameters import get_explode
from openapi_core.schema.parameters import get_style
class TestGetStyle:
@pytest.mark.parametrize(
"location,expected",
[
("query", "form"),
("path", "simple"),
("header", "simple"),
("cookie", "form"),
],
)
def test_defaults(self, location, expected):
spec = {
"name": "default",
"in": location,
}
param = SchemaPath.from_dict(spec)
result = get_style(param)
assert result == expected
@pytest.mark.parametrize(
"style,location",
[
("matrix", "path"),
("label", "apth"),
("form", "query"),
("form", "cookie"),
("simple", "path"),
("simple", "header"),
("spaceDelimited", "query"),
("pipeDelimited", "query"),
("deepObject", "query"),
],
)
def test_defined(self, style, location):
spec = {
"name": "default",
"in": location,
"style": style,
}
param = SchemaPath.from_dict(spec)
result = get_style(param)
assert result == style
class TestGetExplode:
@pytest.mark.parametrize(
"style,location",
[
("matrix", "path"),
("label", "path"),
("simple", "path"),
("spaceDelimited", "query"),
("pipeDelimited", "query"),
("deepObject", "query"),
],
)
def test_defaults_false(self, style, location):
spec = {
"name": "default",
"in": location,
"style": style,
}
param = SchemaPath.from_dict(spec)
result = get_explode(param)
assert result is False
@pytest.mark.parametrize("location", ["query", "cookie"])
def test_defaults_true(self, location):
spec = {
"name": "default",
"in": location,
"style": "form",
}
param = SchemaPath.from_dict(spec)
result = get_explode(param)
assert result is True
@pytest.mark.parametrize("location", ["path", "query", "cookie", "header"])
@pytest.mark.parametrize(
"style",
[
"matrix",
"label",
"form",
"form",
"simple",
"spaceDelimited",
"pipeDelimited",
"deepObject",
],
)
@pytest.mark.parametrize(
"schema_type",
[
"string",
"array" "object",
],
)
@pytest.mark.parametrize("explode", [False, True])
def test_defined(self, location, style, schema_type, explode):
spec = {
"name": "default",
"in": location,
"explode": explode,
"schema": {
"type": schema_type,
},
}
param = SchemaPath.from_dict(spec)
result = get_explode(param)
assert result == explode
python-openapi-openapi-core-d6cdb4f/tests/unit/security/ 0000775 0000000 0000000 00000000000 15163577675 0023637 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/security/test_providers.py 0000664 0000000 0000000 00000002011 15163577675 0027257 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.security.providers import HttpProvider
from openapi_core.testing import MockRequest
class TestHttpProvider:
@pytest.mark.parametrize(
"header",
["authorization", "Authorization", "AUTHORIZATION"],
)
@pytest.mark.parametrize(
"scheme",
["basic", "bearer", "digest"],
)
def test_header(self, header, scheme):
"""Tests HttpProvider against Issue29427
https://bugs.python.org/issue29427
"""
spec = {
"type": "http",
"scheme": scheme,
}
value = "MQ"
headers = {
header: " ".join([scheme.title(), value]),
}
request = MockRequest(
"http://localhost",
"GET",
"/pets",
headers=headers,
)
scheme = SchemaPath.from_dict(spec)
provider = HttpProvider(scheme)
result = provider(request.parameters)
assert result == value
python-openapi-openapi-core-d6cdb4f/tests/unit/templating/ 0000775 0000000 0000000 00000000000 15163577675 0024134 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/templating/test_media_types_finders.py 0000664 0000000 0000000 00000003553 15163577675 0031570 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.media_types.finders import MediaTypeFinder
class TestMediaTypes:
@pytest.fixture(scope="class")
def spec(self):
return {
"application/json": {"schema": {"type": "object"}},
"text/*": {"schema": {"type": "object"}},
}
@pytest.fixture(scope="class")
def content(self, spec):
return SchemaPath.from_dict(spec)
@pytest.fixture(scope="class")
def finder(self, content):
return MediaTypeFinder(content)
@pytest.mark.parametrize(
"media_type",
[
# equivalent according to RFC 9110
"text/html;charset=utf-8",
'Text/HTML;Charset="utf-8"',
'text/html; charset="utf-8"',
"text/html;charset=UTF-8",
"text/html ; charset=utf-8",
],
)
def test_charset(self, finder, content, media_type):
mimetype, parameters, _ = finder.find(media_type)
assert mimetype == "text/*"
assert parameters == {"charset": "utf-8"}
def test_exact(self, finder, content):
mimetype = "application/json"
mimetype, parameters, _ = finder.find(mimetype)
assert mimetype == "application/json"
assert parameters == {}
def test_match(self, finder, content):
mimetype = "text/html"
mimetype, parameters, _ = finder.find(mimetype)
assert mimetype == "text/*"
assert parameters == {}
def test_not_found(self, finder, content):
mimetype = "unknown"
with pytest.raises(MediaTypeNotFound):
finder.find(mimetype)
def test_missing(self, finder, content):
mimetype = None
with pytest.raises(MediaTypeNotFound):
finder.find(mimetype)
python-openapi-openapi-core-d6cdb4f/tests/unit/templating/test_paths_finders.py 0000664 0000000 0000000 00000043340 15163577675 0030402 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.templating.datatypes import TemplateResult
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import PathsNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.paths.finders import APICallPathFinder
class BaseTestSimpleServer:
server_url = "http://petstore.swagger.io"
@pytest.fixture
def server_variable(self):
return {}
@pytest.fixture
def server_variables(self, server_variable):
if not server_variable:
return {}
return {
self.server_variable_name: server_variable,
}
@pytest.fixture
def server(self, server_variables):
server = {
"url": self.server_url,
}
if server_variables:
server["variables"] = server_variables
return server
@pytest.fixture
def servers(self, server):
return [
server,
]
class BaseTestVariableServer(BaseTestSimpleServer):
server_url = "http://petstore.swagger.io/{version}"
server_variable_name = "version"
server_variable_default = "v1"
server_variable_enum = ["v1", "v2"]
@pytest.fixture
def server_variable(self):
return {
self.server_variable_name: {
"default": self.server_variable_default,
"enum": self.server_variable_enum,
}
}
class BaseTestSimplePath:
path_name = "/resource"
@pytest.fixture
def path(self, operations):
return operations
@pytest.fixture
def paths(self, path):
return {
self.path_name: path,
}
class BaseTestVariablePath(BaseTestSimplePath):
path_name = "/resource/{resource_id}"
path_parameter_name = "resource_id"
@pytest.fixture
def parameter(self):
return {
"name": self.path_parameter_name,
"in": "path",
}
@pytest.fixture
def parameters(self, parameter):
return [
parameter,
]
@pytest.fixture
def path(self, operations, parameters):
path = operations.copy()
path["parameters"] = parameters
return path
class BaseTestSpecServer:
location = "spec"
@pytest.fixture
def info(self):
return {
"title": "Test schema",
"version": "1.0",
}
@pytest.fixture
def operation(self):
return {
"responses": [],
}
@pytest.fixture
def operations(self, operation):
return {
"get": operation,
}
@pytest.fixture
def spec(self, info, paths, servers):
spec = {
"info": info,
"servers": servers,
"paths": paths,
}
return SchemaPath.from_dict(spec)
@pytest.fixture
def finder(self, spec):
return APICallPathFinder(spec)
class BaseTestPathServer(BaseTestSpecServer):
location = "path"
@pytest.fixture
def path(self, operations, servers):
path = operations.copy()
path["servers"] = servers
return path
@pytest.fixture
def spec(self, info, paths):
spec = {
"info": info,
"paths": paths,
}
return SchemaPath.from_dict(spec)
class BaseTestOperationServer(BaseTestSpecServer):
location = "operation"
@pytest.fixture
def operation(self, servers):
return {
"responses": [],
"servers": servers,
}
@pytest.fixture
def spec(self, info, paths):
spec = {
"info": info,
"paths": paths,
}
return SchemaPath.from_dict(spec)
class BaseTestServerNotFound:
@pytest.fixture
def servers(self):
return [
{"url": "http://petstore.swagger.io/resource"},
]
def test_raises(self, finder):
method = "get"
full_url = "http://invalidserver/resource"
with pytest.raises(ServerNotFound):
finder.find(method, full_url)
class BaseTestDefaultServer:
@pytest.fixture
def servers(self):
return []
def test_returns_default_server(self, finder, spec):
method = "get"
full_url = "http://petstore.swagger.io/resource"
result = finder.find(method, full_url)
path = spec / "paths" / self.path_name
operation = spec / "paths" / self.path_name / method
server = SchemaPath.from_dict({"url": "/"})
path_result = TemplateResult(self.path_name, {})
server_result = TemplateResult("/", {})
assert result == (
path,
operation,
server,
path_result,
server_result,
)
class BaseTestOperationNotFound:
@pytest.fixture
def operations(self):
return {}
def test_raises(self, finder):
method = "get"
full_url = "http://petstore.swagger.io/resource"
with pytest.raises(OperationNotFound):
finder.find(method, full_url)
class BaseTestValid:
def test_simple(self, finder, spec):
method = "get"
full_url = "http://petstore.swagger.io/resource"
result = finder.find(method, full_url)
path = spec / "paths" / self.path_name
operation = spec / "paths" / self.path_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_name, {})
server_result = TemplateResult(self.server_url, {})
assert result == (
path,
operation,
server,
path_result,
server_result,
)
class BaseTestVariableValid:
@pytest.mark.parametrize("version", ["v1", "v2", ""])
def test_variable(self, finder, spec, version):
method = "get"
full_url = f"http://petstore.swagger.io/{version}/resource"
result = finder.find(method, full_url)
path = spec / "paths" / self.path_name
operation = spec / "paths" / self.path_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_name, {})
server_result = TemplateResult(self.server_url, {"version": version})
assert result == (
path,
operation,
server,
path_result,
server_result,
)
class BaseTestPathVariableValid:
@pytest.mark.parametrize("res_id", ["111", "222"])
def test_path_variable(self, finder, spec, res_id):
method = "get"
full_url = f"http://petstore.swagger.io/resource/{res_id}"
result = finder.find(method, full_url)
path = spec / "paths" / self.path_name
operation = spec / "paths" / self.path_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_name, {"resource_id": res_id})
server_result = TemplateResult(self.server_url, {})
assert result == (
path,
operation,
server,
path_result,
server_result,
)
class BaseTestPathNotFound:
@pytest.fixture
def paths(self):
return {}
def test_raises(self, finder):
method = "get"
full_url = "http://petstore.swagger.io/resource"
with pytest.raises(PathNotFound):
finder.find(method, full_url)
class BaseTestPathsNotFound:
@pytest.fixture
def spec(self, info):
spec = {
"info": info,
}
return SchemaPath.from_dict(spec)
def test_raises(self, finder):
method = "get"
full_url = "http://petstore.swagger.io/resource"
with pytest.raises(PathsNotFound):
finder.find(method, full_url)
class TestSpecSimpleServerDefaultServer(
BaseTestDefaultServer,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestSpecSimpleServerServerNotFound(
BaseTestServerNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestSpecSimpleServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestSpecSimpleServerPathNotFound(
BaseTestPathNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestSpecSimpleServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestSpecServer,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerDefaultServer(
BaseTestDefaultServer,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerServerNotFound(
BaseTestServerNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerPathNotFound(
BaseTestPathNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestOperationServer,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerDefaultServer(
BaseTestDefaultServer,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerServerNotFound(
BaseTestServerNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerPathNotFound(
BaseTestPathNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestPathServer,
BaseTestSimpleServer,
):
pass
class TestSpecSimpleServerValid(
BaseTestValid, BaseTestSpecServer, BaseTestSimplePath, BaseTestSimpleServer
):
pass
class TestOperationSimpleServerValid(
BaseTestValid,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerValid(
BaseTestValid, BaseTestPathServer, BaseTestSimplePath, BaseTestSimpleServer
):
pass
class TestSpecSimpleServerVariablePathValid(
BaseTestPathVariableValid,
BaseTestSpecServer,
BaseTestVariablePath,
BaseTestSimpleServer,
):
pass
class TestOperationSimpleServerVariablePathValid(
BaseTestPathVariableValid,
BaseTestOperationServer,
BaseTestVariablePath,
BaseTestSimpleServer,
):
pass
class TestPathSimpleServerVariablePathValid(
BaseTestPathVariableValid,
BaseTestPathServer,
BaseTestVariablePath,
BaseTestSimpleServer,
):
pass
class TestSpecVariableServerDefaultServer(
BaseTestDefaultServer,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestSpecVariableServerServerNotFound(
BaseTestServerNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestSpecVariableServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestSpecVariableServerPathNotFound(
BaseTestPathNotFound,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestSpecVariableServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestSpecServer,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerDefaultServer(
BaseTestDefaultServer,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerServerNotFound(
BaseTestServerNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerPathNotFound(
BaseTestPathNotFound,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestOperationServer,
BaseTestVariableServer,
):
pass
class TestPathVariableServerDefaultServer(
BaseTestDefaultServer,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestPathVariableServerServerNotFound(
BaseTestServerNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestPathVariableServerOperationNotFound(
BaseTestOperationNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestPathVariableServerPathNotFound(
BaseTestPathNotFound,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestPathVariableServerPathsNotFound(
BaseTestPathsNotFound,
BaseTestPathServer,
BaseTestVariableServer,
):
pass
class TestSpecVariableServerValid(
BaseTestVariableValid,
BaseTestSpecServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestOperationVariableServerValid(
BaseTestVariableValid,
BaseTestOperationServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestPathVariableServerValid(
BaseTestVariableValid,
BaseTestPathServer,
BaseTestSimplePath,
BaseTestVariableServer,
):
pass
class TestSimilarPaths(BaseTestSpecServer, BaseTestSimpleServer):
path_name = "/tokens"
path_2_name = "/keys/{id}/tokens"
@pytest.fixture
def operation_2(self):
return {
"responses": [],
}
@pytest.fixture
def operations_2(self, operation_2):
return {
"get": operation_2,
}
@pytest.fixture
def path(self, operations):
return operations
@pytest.fixture
def path_2(self, operations_2):
return operations_2
@pytest.fixture
def paths(self, path, path_2):
return {
self.path_name: path,
self.path_2_name: path_2,
}
def test_valid(self, finder, spec):
token_id = "123"
method = "get"
full_url = f"http://petstore.swagger.io/keys/{token_id}/tokens"
result = finder.find(method, full_url)
path_2 = spec / "paths" / self.path_2_name
operation_2 = spec / "paths" / self.path_2_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_2_name, {"id": token_id})
server_result = TemplateResult(self.server_url, {})
assert result == (
path_2,
operation_2,
server,
path_result,
server_result,
)
class TestConcretePaths(BaseTestSpecServer, BaseTestSimpleServer):
path_name = "/keys/{id}/tokens"
path_2_name = "/keys/master/tokens"
@pytest.fixture
def operation_2(self):
return {
"responses": [],
}
@pytest.fixture
def operations_2(self, operation_2):
return {
"get": operation_2,
}
@pytest.fixture
def path(self, operations):
return operations
@pytest.fixture
def path_2(self, operations_2):
return operations_2
@pytest.fixture
def paths(self, path, path_2):
return {
self.path_name: path,
self.path_2_name: path_2,
}
def test_valid(self, finder, spec):
method = "get"
full_url = "http://petstore.swagger.io/keys/master/tokens"
result = finder.find(method, full_url)
path_2 = spec / "paths" / self.path_2_name
operation_2 = spec / "paths" / self.path_2_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_2_name, {})
server_result = TemplateResult(self.server_url, {})
assert result == (
path_2,
operation_2,
server,
path_result,
server_result,
)
class TestTemplateConcretePaths(BaseTestSpecServer, BaseTestSimpleServer):
path_name = "/keys/{id}/tokens/{id2}"
path_2_name = "/keys/{id}/tokens/master"
@pytest.fixture
def operation_2(self):
return {
"responses": [],
}
@pytest.fixture
def operations_2(self, operation_2):
return {
"get": operation_2,
}
@pytest.fixture
def path(self, operations):
return operations
@pytest.fixture
def path_2(self, operations_2):
return operations_2
@pytest.fixture
def paths(self, path, path_2):
return {
self.path_name: path,
self.path_2_name: path_2,
}
def test_valid(self, finder, spec):
token_id = "123"
method = "get"
full_url = f"http://petstore.swagger.io/keys/{token_id}/tokens/master"
result = finder.find(method, full_url)
path_2 = spec / "paths" / self.path_2_name
operation_2 = spec / "paths" / self.path_2_name / method
server = eval(self.location) / "servers" / 0
path_result = TemplateResult(self.path_2_name, {"id": "123"})
server_result = TemplateResult(self.server_url, {})
assert result == (
path_2,
operation_2,
server,
path_result,
server_result,
)
python-openapi-openapi-core-d6cdb4f/tests/unit/templating/test_paths_parsers.py 0000664 0000000 0000000 00000003075 15163577675 0030430 0 ustar 00root root 0000000 0000000 import pytest
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 = parser.search(full_url_pattern)
assert result.named == {
"test": "test2",
}
def test_exact(self):
path_pattern = "/{test}/test"
parser = PathParser(path_pattern, post_expression="$")
full_url_pattern = "/test/test"
result = parser.search(full_url_pattern)
assert result.named == {
"test": "test",
}
@pytest.mark.parametrize(
"path_pattern,expected",
[
("/{test_id}/test", {"test_id": "test"}),
("/{test.id}/test", {"test.id": "test"}),
("/{test-id}/test", {"test-id": "test"}),
],
)
def test_chars_valid(self, path_pattern, expected):
parser = PathParser(path_pattern, post_expression="$")
full_url_pattern = "/test/test"
result = parser.search(full_url_pattern)
assert result.named == expected
@pytest.mark.parametrize(
"path_pattern,expected",
[
("/{test~id}/test", {"test~id": "test"}),
],
)
def test_special_chars_valid(self, path_pattern, expected):
parser = PathParser(path_pattern, post_expression="$")
full_url_pattern = "/test/test"
result = parser.search(full_url_pattern)
assert result.named == expected
python-openapi-openapi-core-d6cdb4f/tests/unit/templating/test_responses_finders.py 0000664 0000000 0000000 00000002045 15163577675 0031301 0 ustar 00root root 0000000 0000000 from unittest import mock
import pytest
from jsonschema_path import SchemaPath
from openapi_core.templating.responses.finders import ResponseFinder
class TestResponses:
@pytest.fixture(scope="class")
def spec(self):
return {
"200": mock.sentinel.response_200,
"299": mock.sentinel.response_299,
"2XX": mock.sentinel.response_2XX,
"default": mock.sentinel.response_default,
}
@pytest.fixture(scope="class")
def responses(self, spec):
return SchemaPath.from_dict(spec)
@pytest.fixture(scope="class")
def finder(self, responses):
return ResponseFinder(responses)
def test_default(self, finder, responses):
response = finder.find()
assert response == responses / "default"
def test_range(self, finder, responses):
response = finder.find("201")
assert response == responses / "2XX"
def test_exact(self, finder, responses):
response = finder.find("200")
assert response == responses / "200"
python-openapi-openapi-core-d6cdb4f/tests/unit/test_app.py 0000664 0000000 0000000 00000015651 15163577675 0024171 0 ustar 00root root 0000000 0000000 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:
def test_valid(self, create_file):
spec_dict = {
"openapi": "3.1.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
file_path = create_file(spec_dict)
path = Path(file_path)
result = OpenAPI.from_path(path)
assert type(result) == OpenAPI
assert result.spec.read_value() == spec_dict
class TestOpenAPIFromFilePath:
def test_valid(self, create_file):
spec_dict = {
"openapi": "3.1.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
file_path = create_file(spec_dict)
result = OpenAPI.from_file_path(file_path)
assert type(result) == OpenAPI
assert result.spec.read_value() == spec_dict
class TestOpenAPIFromFile:
def test_valid(self, create_file):
spec_dict = {
"openapi": "3.1.0",
"info": {
"title": "Spec",
"version": "0.0.1",
},
"paths": {},
}
file_path = create_file(spec_dict)
with open(file_path) as f:
result = OpenAPI.from_file(f)
assert type(result) == OpenAPI
assert result.spec.read_value() == spec_dict
class TestOpenAPIFromDict:
def test_spec_error(self):
spec_dict = {}
with pytest.raises(SpecError):
OpenAPI.from_dict(spec_dict)
def test_check_skipped(self):
spec_dict = {}
config = Config(spec_validator_cls=None)
result = OpenAPI.from_dict(spec_dict, config=config)
assert type(result) == OpenAPI
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)
python-openapi-openapi-core-d6cdb4f/tests/unit/test_shortcuts.py 0000664 0000000 0000000 00000112270 15163577675 0025442 0 ustar 00root root 0000000 0000000 from unittest import mock
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
from openapi_core import unmarshal_response
from openapi_core import unmarshal_webhook_request
from openapi_core import unmarshal_webhook_response
from openapi_core import validate_apicall_request
from openapi_core import validate_apicall_response
from openapi_core import validate_request
from openapi_core import validate_response
from openapi_core import validate_webhook_request
from openapi_core import validate_webhook_response
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.testing.datatypes import ResultMock
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.request.unmarshallers import (
APICallRequestUnmarshaller,
)
from openapi_core.unmarshalling.request.unmarshallers import (
WebhookRequestUnmarshaller,
)
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.response.unmarshallers import (
APICallResponseUnmarshaller,
)
from openapi_core.unmarshalling.response.unmarshallers import (
WebhookResponseUnmarshaller,
)
from openapi_core.validation.request.validators import APICallRequestValidator
from openapi_core.validation.request.validators import WebhookRequestValidator
from openapi_core.validation.response.validators import (
APICallResponseValidator,
)
from openapi_core.validation.response.validators import (
WebhookResponseValidator,
)
class MockClass:
spec_validator_cls = None
schema_casters_factory = None
schema_validators_factory = None
schema_unmarshallers_factory = None
unmarshal_calls = []
validate_calls = []
return_unmarshal = None
@classmethod
def setUp(cls, return_unmarshal=None):
cls.unmarshal_calls = []
cls.validate_calls = []
cls.return_unmarshal = return_unmarshal
class MockReqValidator(MockClass):
def validate(self, req):
self.validate_calls.append((req,))
class MockReqUnmarshaller(MockClass):
def unmarshal(self, req):
self.unmarshal_calls.append((req,))
return self.return_unmarshal
class MockRespValidator(MockClass):
def validate(self, req, resp):
self.validate_calls.append((req, resp))
class MockRespUnmarshaller(MockClass):
def unmarshal(self, req, resp):
self.unmarshal_calls.append((req, resp))
return self.return_unmarshal
@pytest.fixture(autouse=True)
def setup():
MockClass.setUp()
yield
class TestUnmarshalAPICallRequest:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
unmarshal_apicall_request(request, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
unmarshal_apicall_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
unmarshal_apicall_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_apicall_request(request, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
with pytest.raises(TypeError):
unmarshal_apicall_request(request, spec=spec_v31, cls=Exception)
class TestUnmarshalWebhookRequest:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
unmarshal_webhook_request(request, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
unmarshal_webhook_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
unmarshal_webhook_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=WebhookRequest)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_webhook_request(request, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(TypeError):
unmarshal_webhook_request(request, spec=spec_v31, cls=Exception)
def test_spec_oas30_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
unmarshal_webhook_request(request, spec=spec_v30)
@mock.patch(
"openapi_core.unmarshalling.request.unmarshallers.WebhookRequestUnmarshaller."
"unmarshal",
)
def test_request(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=WebhookRequest)
result = unmarshal_webhook_request(request, spec=spec_v31)
assert result == mock_unmarshal.return_value
mock_unmarshal.assert_called_once_with(request)
class TestUnmarshalRequest:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
unmarshal_request(request, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
unmarshal_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
unmarshal_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_request(request, spec=spec)
def test_cls_apicall_unmarshaller(self, spec_v31):
request = mock.Mock(spec=Request)
unmarshal = mock.Mock(spec=RequestUnmarshalResult)
TestAPICallReq = type(
"TestAPICallReq",
(MockReqUnmarshaller, APICallRequestUnmarshaller),
{},
)
TestAPICallReq.setUp(unmarshal)
result = unmarshal_request(request, spec=spec_v31, cls=TestAPICallReq)
assert result == unmarshal
assert TestAPICallReq.unmarshal_calls == [
(request,),
]
def test_cls_webhook_unmarshaller(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
unmarshal = mock.Mock(spec=RequestUnmarshalResult)
TestWebhookReq = type(
"TestWebhookReq",
(MockReqUnmarshaller, WebhookRequestUnmarshaller),
{},
)
TestWebhookReq.setUp(unmarshal)
result = unmarshal_request(request, spec=spec_v31, cls=TestWebhookReq)
assert result == unmarshal
assert TestWebhookReq.unmarshal_calls == [
(request,),
]
@pytest.mark.parametrize("req", [Request, WebhookRequest])
def test_cls_type_invalid(self, spec_v31, req):
request = mock.Mock(spec=req)
with pytest.raises(TypeError):
unmarshal_request(request, spec=spec_v31, cls=Exception)
@mock.patch(
"openapi_core.unmarshalling.request.unmarshallers.APICallRequestUnmarshaller."
"unmarshal",
)
def test_request(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=Request)
result = unmarshal_request(request, spec=spec_v31)
assert result == mock_unmarshal.return_value
mock_unmarshal.assert_called_once_with(request)
@mock.patch(
"openapi_core.unmarshalling.request.unmarshallers.APICallRequestUnmarshaller."
"unmarshal",
)
def test_request_error(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=Request)
mock_unmarshal.return_value = ResultMock(error_to_raise=ValueError)
with pytest.raises(ValueError):
unmarshal_request(request, spec=spec_v31)
mock_unmarshal.assert_called_once_with(request)
class TestUnmarshalAPICallResponse:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_apicall_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_apicall_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_apicall_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.sentinel.response
with pytest.raises(TypeError):
unmarshal_apicall_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_apicall_response(request, response, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_apicall_response(
request, response, spec=spec_v31, cls=Exception
)
class TestUnmarshalResponse:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.sentinel.response
with pytest.raises(TypeError):
unmarshal_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_response(request, response, spec=spec)
def test_cls_apicall_unmarshaller(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
unmarshal = mock.Mock(spec=ResponseUnmarshalResult)
TestAPICallReq = type(
"TestAPICallReq",
(MockRespUnmarshaller, APICallResponseUnmarshaller),
{},
)
TestAPICallReq.setUp(unmarshal)
result = unmarshal_response(
request, response, spec=spec_v31, cls=TestAPICallReq
)
assert result == unmarshal
assert TestAPICallReq.unmarshal_calls == [
(request, response),
]
def test_cls_webhook_unmarshaller(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
unmarshal = mock.Mock(spec=ResponseUnmarshalResult)
TestWebhookReq = type(
"TestWebhookReq",
(MockRespUnmarshaller, WebhookResponseUnmarshaller),
{},
)
TestWebhookReq.setUp(unmarshal)
result = unmarshal_response(
request, response, spec=spec_v31, cls=TestWebhookReq
)
assert result == unmarshal
assert TestWebhookReq.unmarshal_calls == [
(request, response),
]
@pytest.mark.parametrize("req", [Request, WebhookRequest])
def test_cls_type_invalid(self, spec_v31, req):
request = mock.Mock(spec=req)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_response(request, response, spec=spec_v31, cls=Exception)
@mock.patch(
"openapi_core.unmarshalling.response.unmarshallers.APICallResponseUnmarshaller."
"unmarshal",
)
def test_request_response(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
result = unmarshal_response(request, response, spec=spec_v31)
assert result == mock_unmarshal.return_value
mock_unmarshal.assert_called_once_with(request, response)
@mock.patch(
"openapi_core.unmarshalling.response.unmarshallers.APICallResponseUnmarshaller."
"unmarshal",
)
def test_request_response_error(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
mock_unmarshal.return_value = ResultMock(error_to_raise=ValueError)
with pytest.raises(ValueError):
unmarshal_response(request, response, spec=spec_v31)
mock_unmarshal.assert_called_once_with(request, response)
class TestUnmarshalWebhookResponse:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_webhook_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_webhook_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_webhook_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.sentinel.response
with pytest.raises(TypeError):
unmarshal_webhook_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
unmarshal_webhook_response(request, response, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
unmarshal_webhook_response(
request, response, spec=spec_v31, cls=Exception
)
def test_spec_oas30_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
unmarshal_webhook_response(request, response, spec=spec_v30)
@mock.patch(
"openapi_core.unmarshalling.response.unmarshallers.WebhookResponseUnmarshaller."
"unmarshal",
)
def test_request_response(self, mock_unmarshal, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
result = unmarshal_webhook_response(request, response, spec=spec_v31)
assert result == mock_unmarshal.return_value
mock_unmarshal.assert_called_once_with(request, response)
class TestValidateAPICallRequest:
def test_spec_not_detected(self, spec_invalid):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
validate_apicall_request(request, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
validate_apicall_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
validate_apicall_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_apicall_request(request, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
with pytest.raises(TypeError):
validate_apicall_request(request, spec=spec_v31, cls=Exception)
@mock.patch(
"openapi_core.validation.request.validators.APICallRequestValidator."
"validate",
)
def test_request(self, mock_validate, spec_v31):
request = mock.Mock(spec=Request)
result = validate_apicall_request(request, spec=spec_v31)
assert result is None
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)
with pytest.raises(SpecError):
validate_webhook_request(request, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
validate_webhook_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
validate_webhook_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=WebhookRequest)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_webhook_request(request, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(TypeError):
validate_webhook_request(request, spec=spec_v31, cls=Exception)
def test_spec_oas30_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
validate_webhook_request(request, spec=spec_v30)
@mock.patch(
"openapi_core.validation.request.validators.WebhookRequestValidator."
"validate",
)
def test_request(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
result = validate_webhook_request(request, spec=spec_v31)
assert result is None
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)
with pytest.raises(SpecError):
validate_request(request, spec=spec_invalid)
def test_spec_not_detected(self, spec_v20):
request = mock.Mock(spec=Request)
with pytest.raises(SpecError):
validate_request(request, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
with pytest.raises(TypeError):
validate_request(request, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_request(request, spec=spec)
@mock.patch(
"openapi_core.validation.request.validators.APICallRequestValidator."
"validate",
)
def test_request(self, mock_validate, spec_v31):
request = mock.Mock(spec=Request)
mock_validate.return_value = None
validate_request(request, spec=spec_v31)
mock_validate.assert_called_once_with(request)
def test_cls_apicall(self, spec_v31):
request = mock.Mock(spec=Request)
TestAPICallReq = type(
"TestAPICallReq",
(MockReqValidator, APICallRequestValidator),
{},
)
result = validate_request(request, spec=spec_v31, cls=TestAPICallReq)
assert result is None
assert TestAPICallReq.validate_calls == [
(request,),
]
def test_cls_apicall_with_spec_validator_cls(self, spec_v31):
request = mock.Mock(spec=Request)
TestAPICallReq = type(
"TestAPICallReq",
(MockReqValidator, APICallRequestValidator),
{
"spec_validator_cls": OpenAPIV31SpecValidator,
},
)
result = validate_request(request, spec=spec_v31, cls=TestAPICallReq)
assert result is None
assert TestAPICallReq.validate_calls == [
(request,),
]
def test_cls_webhook(self, spec_v31):
request = mock.Mock(spec=Request)
TestWebhookReq = type(
"TestWebhookReq",
(MockReqValidator, WebhookRequestValidator),
{},
)
result = validate_request(request, spec=spec_v31, cls=TestWebhookReq)
assert result is None
assert TestWebhookReq.validate_calls == [
(request,),
]
def test_cls_webhook_with_spec_validator_cls(self, spec_v31):
request = mock.Mock(spec=Request)
TestWebhookReq = type(
"TestWebhookReq",
(MockReqValidator, WebhookRequestValidator),
{
"spec_validator_cls": OpenAPIV31SpecValidator,
},
)
result = validate_request(request, spec=spec_v31, cls=TestWebhookReq)
assert result is None
assert TestWebhookReq.validate_calls == [
(request,),
]
def test_webhook_cls(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
TestWebhookReq = type(
"TestWebhookReq",
(MockReqValidator, WebhookRequestValidator),
{},
)
result = validate_request(request, spec=spec_v31, cls=TestWebhookReq)
assert result is None
assert TestWebhookReq.validate_calls == [
(request,),
]
def test_cls_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
with pytest.raises(TypeError):
validate_request(request, spec=spec_v31, cls=Exception)
@mock.patch(
"openapi_core.validation.request.validators.V31WebhookRequestValidator."
"validate",
)
def test_webhook_request(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
mock_validate.return_value = None
validate_request(request, spec=spec_v31)
mock_validate.assert_called_once_with(request)
def test_webhook_request_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(SpecError):
validate_request(request, spec=spec_v30)
@mock.patch(
"openapi_core.validation.request.validators.V31WebhookRequestValidator."
"validate",
)
def test_webhook_request_error(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
mock_validate.side_effect = ValueError
with pytest.raises(ValueError):
validate_request(request, spec=spec_v31)
mock_validate.assert_called_once_with(request)
def test_webhook_cls_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
with pytest.raises(TypeError):
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)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_apicall_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_apicall_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_apicall_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.sentinel.response
with pytest.raises(TypeError):
validate_apicall_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_apicall_response(request, response, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_apicall_response(
request, response, spec=spec_v31, cls=Exception
)
@mock.patch(
"openapi_core.validation.response.validators.APICallResponseValidator."
"validate",
)
def test_request_response(self, mock_validate, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
result = validate_apicall_response(request, response, spec=spec_v31)
assert result is None
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)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_webhook_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_webhook_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_webhook_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.sentinel.response
with pytest.raises(TypeError):
validate_webhook_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_webhook_response(request, response, spec=spec)
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_webhook_response(
request, response, spec=spec_v31, cls=Exception
)
def test_spec_oas30_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_webhook_response(request, response, spec=spec_v30)
@mock.patch(
"openapi_core.validation.response.validators.WebhookResponseValidator."
"validate",
)
def test_request_response(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
result = validate_webhook_response(request, response, spec=spec_v31)
assert result is None
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)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_response(request, response, spec=spec_invalid)
def test_spec_not_supported(self, spec_v20):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_response(request, response, spec=spec_v20)
def test_request_type_invalid(self, spec_v31):
request = mock.sentinel.request
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_response(request, response, spec=spec_v31)
def test_response_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.sentinel.response
with pytest.raises(TypeError):
validate_response(request, response, spec=spec_v31)
def test_spec_type_invalid(self):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
spec = mock.sentinel.spec
with pytest.raises(TypeError):
validate_response(request, response, spec=spec)
@mock.patch(
"openapi_core.validation.response.validators.APICallResponseValidator."
"validate",
)
def test_request_response(self, mock_validate, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
mock_validate.return_value = None
validate_response(request, response, spec=spec_v31)
mock_validate.assert_called_once_with(request, response)
def test_cls_apicall(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
TestAPICallResp = type(
"TestAPICallResp",
(MockRespValidator, APICallResponseValidator),
{},
)
result = validate_response(
request, response, spec=spec_v31, cls=TestAPICallResp
)
assert result is None
assert TestAPICallResp.validate_calls == [
(request, response),
]
def test_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=Request)
response = mock.Mock(spec=Response)
with pytest.raises(TypeError):
validate_response(request, response, spec=spec_v31, cls=Exception)
def test_webhook_response_validator_not_found(self, spec_v30):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
with pytest.raises(SpecError):
validate_response(request, response, spec=spec_v30)
@mock.patch(
"openapi_core.validation.response.validators.V31WebhookResponseValidator."
"validate",
)
def test_webhook_request(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
mock_validate.return_value = None
validate_response(request, response, spec=spec_v31)
mock_validate.assert_called_once_with(request, response)
@mock.patch(
"openapi_core.validation.response.validators.V31WebhookResponseValidator."
"validate",
)
def test_webhook_request_error(self, mock_validate, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
mock_validate.side_effect = ValueError
with pytest.raises(ValueError):
validate_response(request, response, spec=spec_v31)
mock_validate.assert_called_once_with(request, response)
def test_webhook_cls(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
TestWebhookResp = type(
"TestWebhookResp",
(MockRespValidator, WebhookResponseValidator),
{},
)
result = validate_response(
request, response, spec=spec_v31, cls=TestWebhookResp
)
assert result is None
assert TestWebhookResp.validate_calls == [
(request, response),
]
def test_webhook_cls_type_invalid(self, spec_v31):
request = mock.Mock(spec=WebhookRequest)
response = mock.Mock(spec=Response)
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)
python-openapi-openapi-core-d6cdb4f/tests/unit/test_util.py 0000664 0000000 0000000 00000001143 15163577675 0024355 0 ustar 00root root 0000000 0000000 import pytest
from openapi_core.util import forcebool
class TestForcebool:
@pytest.mark.parametrize("val", ["y", "yes", "t", "true", "on", "1", True])
def test_true(self, val):
result = forcebool(val)
assert result is True
@pytest.mark.parametrize(
"val", ["n", "no", "f", "false", "off", "0", False]
)
def test_false(self, val):
result = forcebool(val)
assert result is False
@pytest.mark.parametrize("val", ["random", "idontknow", ""])
def test_value_error(self, val):
with pytest.raises(ValueError):
forcebool(val)
python-openapi-openapi-core-d6cdb4f/tests/unit/unmarshalling/ 0000775 0000000 0000000 00000000000 15163577675 0024634 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/unmarshalling/test_path_item_params_validator.py 0000664 0000000 0000000 00000020153 15163577675 0033630 0 ustar 00root root 0000000 0000000 from dataclasses import is_dataclass
import pytest
from jsonschema_path import SchemaPath
from openapi_core import V30RequestUnmarshaller
from openapi_core import unmarshal_request
from openapi_core import validate_request
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
def spec_dict(self):
return {
"openapi": "3.0.0",
"info": {
"title": "Test path item parameter validation",
"version": "0.1",
},
"paths": {
"/resource": {
"parameters": [
{
"name": "resId",
"in": "query",
"required": True,
"schema": {
"type": "integer",
},
},
],
"get": {
"responses": {
"default": {"description": "Return the resource."}
}
},
}
},
}
@pytest.fixture
def spec(self, spec_dict):
return SchemaPath.from_dict(spec_dict)
@pytest.fixture
def request_unmarshaller(self, spec):
return V30RequestUnmarshaller(spec)
def test_request_missing_param(self, request_unmarshaller):
request = MockRequest("http://example.com", "get", "/resource")
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == MissingRequiredParameter
assert result.body is None
assert result.parameters == Parameters()
def test_request_invalid_param(self, request_unmarshaller):
request = MockRequest(
"http://example.com",
"get",
"/resource",
args={"resId": "invalid"},
)
result = request_unmarshaller.unmarshal(request)
assert result.errors == [
ParameterValidationError(name="resId", location="query")
]
assert type(result.errors[0].__cause__) is CastError
assert result.body is None
assert result.parameters == Parameters()
def test_request_valid_param(self, request_unmarshaller):
request = MockRequest(
"http://example.com",
"get",
"/resource",
args={"resId": "10"},
)
result = request_unmarshaller.unmarshal(request)
assert len(result.errors) == 0
assert result.body is None
assert result.parameters == Parameters(query={"resId": 10})
def test_request_override_param(self, spec, spec_dict):
# override path parameter on operation
spec_dict["paths"]["/resource"]["get"]["parameters"] = [
{
# full valid parameter object required
"name": "resId",
"in": "query",
"required": False,
"schema": {
"type": "integer",
},
}
]
request = MockRequest("http://example.com", "get", "/resource")
result = unmarshal_request(
request, spec, base_url="http://example.com"
)
assert len(result.errors) == 0
assert result.body is None
assert result.parameters == Parameters()
def test_request_override_param_uniqueness(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")
def test_request_object_deep_object_params(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"
)
assert len(result.errors) == 0
assert result.body is None
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"
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"
python-openapi-openapi-core-d6cdb4f/tests/unit/unmarshalling/test_request_unmarshallers.py 0000664 0000000 0000000 00000010670 15163577675 0032701 0 ustar 00root root 0000000 0000000 import enum
import pytest
from jsonschema_path import SchemaPath
from openapi_core import V30RequestUnmarshaller
from openapi_core import V31RequestUnmarshaller
from openapi_core.datatypes import Parameters
from openapi_core.testing import MockRequest
class Colors(enum.Enum):
YELLOW = "yellow"
BLUE = "blue"
RED = "red"
@classmethod
def of(cls, v: str):
for it in cls:
if it.value == v:
return it
raise ValueError(f"Invalid value: {v}")
class TestRequestUnmarshaller:
@pytest.fixture(scope="session")
def spec_dict(self):
return {
"openapi": "3.1.0",
"info": {
"title": "Test request body unmarshaller",
"version": "0.1",
},
"paths": {
"/resources": {
"post": {
"description": "POST resources test request",
"requestBody": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/createResource"
}
}
},
},
"responses": {
"201": {"description": "Resource was created."}
},
},
"get": {
"description": "POST resources test request",
"parameters": [
{
"name": "color",
"in": "query",
"required": False,
"schema": {
"$ref": "#/components/schemas/colors"
},
},
],
"responses": {
"default": {
"description": "Returned resources matching request."
}
},
},
}
},
"components": {
"schemas": {
"colors": {
"type": "string",
"enum": ["yellow", "blue", "red"],
"format": "enum_Colors",
},
"createResource": {
"type": "object",
"properties": {
"resId": {"type": "integer"},
"color": {"$ref": "#/components/schemas/colors"},
},
"required": ["resId", "color"],
},
}
},
}
@pytest.fixture(scope="session")
def spec(self, spec_dict):
return SchemaPath.from_dict(spec_dict)
@pytest.mark.parametrize(
"req_unmarshaller_cls",
[V30RequestUnmarshaller, V31RequestUnmarshaller],
)
def test_request_body_extra_unmarshaller(self, spec, req_unmarshaller_cls):
ru = req_unmarshaller_cls(
spec=spec, extra_format_unmarshallers={"enum_Colors": Colors.of}
)
request = MockRequest(
host_url="http://example.com",
method="post",
path="/resources",
data=b'{"resId": 23498572, "color": "blue"}',
)
result = ru.unmarshal(request)
assert not result.errors
assert result.body == {"resId": 23498572, "color": Colors.BLUE}
assert result.parameters == Parameters()
@pytest.mark.parametrize(
"req_unmarshaller_cls",
[V30RequestUnmarshaller, V31RequestUnmarshaller],
)
def test_request_param_extra_unmarshaller(
self, spec, req_unmarshaller_cls
):
ru = req_unmarshaller_cls(
spec=spec, extra_format_unmarshallers={"enum_Colors": Colors.of}
)
request = MockRequest(
host_url="http://example.com",
method="get",
path="/resources",
args={"color": "blue"},
)
result = ru.unmarshal(request)
assert not result.errors
assert result.parameters == Parameters(query=dict(color=Colors.BLUE))
python-openapi-openapi-core-d6cdb4f/tests/unit/unmarshalling/test_schema_unmarshallers.py 0000664 0000000 0000000 00000016661 15163577675 0032457 0 ustar 00root root 0000000 0000000 from functools import partial
import pytest
from jsonschema_path import SchemaPath
from openapi_schema_validator import OAS30WriteValidator
from openapi_core.unmarshalling.schemas import oas30_types_unmarshaller
from openapi_core.unmarshalling.schemas.exceptions import (
FormatterNotFoundError,
)
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
from openapi_core.validation.schemas import (
oas30_write_schema_validators_factory,
)
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
from openapi_core.validation.schemas.factories import SchemaValidatorsFactory
@pytest.fixture
def spec():
spec_dict = {}
return SchemaPath.from_dict(spec_dict)
@pytest.fixture
def schema_unmarshaller_factory(spec):
def create_unmarshaller(
validators_factory,
schema,
format_validators=None,
extra_format_validators=None,
extra_format_unmarshallers=None,
):
return SchemaUnmarshallersFactory(
validators_factory,
oas30_types_unmarshaller,
).create(
spec,
schema,
format_validators=format_validators,
extra_format_validators=extra_format_validators,
extra_format_unmarshallers=extra_format_unmarshallers,
)
return create_unmarshaller
@pytest.fixture
def unmarshaller_factory(schema_unmarshaller_factory):
return partial(
schema_unmarshaller_factory,
oas30_write_schema_validators_factory,
)
class TestOAS30SchemaUnmarshallerFactoryCreate:
def test_string_format_unknown(self, unmarshaller_factory):
unknown_format = "unknown"
schema = {
"type": "string",
"format": unknown_format,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(FormatterNotFoundError):
unmarshaller_factory(spec)
def test_string_format_invalid_value(self, unmarshaller_factory):
custom_format = "custom"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(
FormatterNotFoundError,
match="Formatter not found for custom format",
):
unmarshaller_factory(spec)
class TestOAS30SchemaUnmarshallerUnmarshal:
def test_schema_extra_format_unmarshaller_format_invalid(
self, schema_unmarshaller_factory, unmarshaller_factory
):
def custom_format_unmarshaller(value):
raise ValueError
custom_format = "custom"
schema = {
"type": "string",
"format": "custom",
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
extra_format_unmarshallers = {
custom_format: custom_format_unmarshaller,
}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
extra_format_unmarshallers=extra_format_unmarshallers,
)
result = unmarshaller.unmarshal(value)
assert result == value
def test_schema_extra_format_unmarshaller_format_custom(
self, schema_unmarshaller_factory
):
formatted = "x-custom"
def custom_format_unmarshaller(value):
return formatted
custom_format = "custom"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
extra_format_unmarshallers = {
custom_format: custom_format_unmarshaller,
}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
extra_format_unmarshallers=extra_format_unmarshallers,
)
result = unmarshaller.unmarshal(value)
assert result == formatted
def test_schema_extra_format_validator_format_invalid(
self, schema_unmarshaller_factory, unmarshaller_factory
):
def custom_format_validator(value):
return False
custom_format = "custom"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
extra_format_validators = {
custom_format: custom_format_validator,
}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
extra_format_validators=extra_format_validators,
)
with pytest.raises(InvalidSchemaValue):
unmarshaller.unmarshal(value)
def test_schema_extra_format_validator_format_custom(
self, schema_unmarshaller_factory
):
def custom_format_validator(value):
return True
custom_format = "custom"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
extra_format_validators = {
custom_format: custom_format_validator,
}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
extra_format_validators=extra_format_validators,
)
result = unmarshaller.unmarshal(value)
assert result == value
@pytest.mark.xfail(
reason=(
"Not registered format raises FormatterNotFoundError"
"See https://github.com/python-openapi/openapi-core/issues/515"
),
strict=True,
)
def test_schema_format_validator_format_invalid(
self, schema_unmarshaller_factory, unmarshaller_factory
):
custom_format = "date"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
format_validators = {}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
format_validators=format_validators,
)
result = unmarshaller.unmarshal(value)
assert result == value
def test_schema_format_validator_format_custom(
self, schema_unmarshaller_factory, unmarshaller_factory
):
def custom_format_validator(value):
return True
custom_format = "date"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
schema_validators_factory = SchemaValidatorsFactory(
OAS30WriteValidator
)
format_validators = {
custom_format: custom_format_validator,
}
unmarshaller = schema_unmarshaller_factory(
schema_validators_factory,
spec,
format_validators=format_validators,
)
result = unmarshaller.unmarshal(value)
assert result == value
python-openapi-openapi-core-d6cdb4f/tests/unit/validation/ 0000775 0000000 0000000 00000000000 15163577675 0024122 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/validation/schemas/ 0000775 0000000 0000000 00000000000 15163577675 0025545 5 ustar 00root root 0000000 0000000 python-openapi-openapi-core-d6cdb4f/tests/unit/validation/schemas/test_schemas_factories.py 0000664 0000000 0000000 00000004014 15163577675 0032637 0 ustar 00root root 0000000 0000000 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()
python-openapi-openapi-core-d6cdb4f/tests/unit/validation/test_schema_validators.py 0000664 0000000 0000000 00000025053 15163577675 0031230 0 ustar 00root root 0000000 0000000 import pytest
from jsonschema_path import SchemaPath
from openapi_core.validation.schemas import (
oas30_write_schema_validators_factory,
)
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
class TestSchemaValidate:
@pytest.fixture
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(spec, schema)
return create_validator
def test_string_format_custom_missing(self, validator_factory):
custom_format = "custom"
schema = {
"type": "string",
"format": custom_format,
}
spec = SchemaPath.from_dict(schema)
value = "x"
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [0, 1, 2])
def test_integer_minimum_invalid(self, value, validator_factory):
schema = {
"type": "integer",
"minimum": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [4, 5, 6])
def test_integer_minimum(self, value, validator_factory):
schema = {
"type": "integer",
"minimum": 3,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [4, 5, 6])
def test_integer_maximum_invalid(self, value, validator_factory):
schema = {
"type": "integer",
"maximum": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [0, 1, 2])
def test_integer_maximum(self, value, validator_factory):
schema = {
"type": "integer",
"maximum": 3,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [1, 2, 4])
def test_integer_multiple_of_invalid(self, value, validator_factory):
schema = {
"type": "integer",
"multipleOf": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [3, 6, 18])
def test_integer_multiple_of(self, value, validator_factory):
schema = {
"type": "integer",
"multipleOf": 3,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [0, 1, 2])
def test_number_minimum_invalid(self, value, validator_factory):
schema = {
"type": "number",
"minimum": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [3, 4, 5])
def test_number_minimum(self, value, validator_factory):
schema = {
"type": "number",
"minimum": 3,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [1, 2, 3])
def test_number_exclusive_minimum_invalid(self, value, validator_factory):
schema = {
"type": "number",
"minimum": 3,
"exclusiveMinimum": True,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [4, 5, 6])
def test_number_exclusive_minimum(self, value, validator_factory):
schema = {
"type": "number",
"minimum": 3,
"exclusiveMinimum": True,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [4, 5, 6])
def test_number_maximum_invalid(self, value, validator_factory):
schema = {
"type": "number",
"maximum": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [1, 2, 3])
def test_number_maximum(self, value, validator_factory):
schema = {
"type": "number",
"maximum": 3,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [3, 4, 5])
def test_number_exclusive_maximum_invalid(self, value, validator_factory):
schema = {
"type": "number",
"maximum": 3,
"exclusiveMaximum": True,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [0, 1, 2])
def test_number_exclusive_maximum(self, value, validator_factory):
schema = {
"type": "number",
"maximum": 3,
"exclusiveMaximum": True,
}
spec = SchemaPath.from_dict(schema)
result = validator_factory(spec).validate(value)
assert result is None
@pytest.mark.parametrize("value", [1, 2, 4])
def test_number_multiple_of_invalid(self, value, validator_factory):
schema = {
"type": "number",
"multipleOf": 3,
}
spec = SchemaPath.from_dict(schema)
with pytest.raises(InvalidSchemaValue):
validator_factory(spec).validate(value)
@pytest.mark.parametrize("value", [3, 6, 18])
def test_number_multiple_of(self, value, validator_factory):
schema = {
"type": "number",
"multipleOf": 3,
}
spec = SchemaPath.from_dict(schema)
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": {}})