pax_global_header00006660000000000000000000000064151223146340014513gustar00rootroot0000000000000052 comment=92b414756cf23d99440d7ae8165afbcd28f4c3f0 python-openapi-openapi-core-fb80538/000077500000000000000000000000001512231463400174055ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/.editorconfig000066400000000000000000000002531512231463400220620ustar00rootroot00000000000000root = 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-fb80538/.github/000077500000000000000000000000001512231463400207455ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/.github/FUNDING.yml000066400000000000000000000000201512231463400225520ustar00rootroot00000000000000github: [p1c2u] python-openapi-openapi-core-fb80538/.github/ISSUE_TEMPLATE/000077500000000000000000000000001512231463400231305ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/.github/ISSUE_TEMPLATE/00_bug_report.yml000066400000000000000000000046071512231463400263310ustar00rootroot00000000000000name: "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-fb80538/.github/ISSUE_TEMPLATE/01_enhancement.yml000066400000000000000000000017021512231463400264400ustar00rootroot00000000000000name: "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-fb80538/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003741512231463400251240ustar00rootroot00000000000000blank_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-fb80538/.github/dependabot.yml000066400000000000000000000003151512231463400235740ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" python-openapi-openapi-core-fb80538/.github/workflows/000077500000000000000000000000001512231463400230025ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/.github/workflows/build-docs.yml000066400000000000000000000027001512231463400255510ustar00rootroot00000000000000name: Build documentation on: push: pull_request: types: [opened, synchronize] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python 3.9 uses: actions/setup-python@v6 with: python-version: 3.9 - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - 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@v6 name: Upload docs as artifact with: name: docs-html path: './_build/html' if-no-files-found: error python-openapi-openapi-core-fb80538/.github/workflows/python-publish.yml000066400000000000000000000015341512231463400265150ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Publish python packages on: workflow_dispatch: release: types: - published jobs: publish: 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.2.1" - name: Build run: poetry build - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ python-openapi-openapi-core-fb80538/.github/workflows/python-test.yml000066400000000000000000000057061512231463400260330ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Test python code on: push: pull_request: types: [opened, synchronize] jobs: test: name: "Tests" runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "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: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install --all-extras - name: Test env: PYTEST_ADDOPTS: "--color=yes" run: poetry run pytest - name: Static type check run: poetry run mypy - name: Check dependencies run: poetry run deptry . - name: Upload coverage uses: codecov/codecov-action@v5 static-checks: name: "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.9 - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install - name: Run static checks run: poetry run pre-commit run -a python-openapi-openapi-core-fb80538/.gitignore000066400000000000000000000023731512231463400214020ustar00rootroot00000000000000# 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-fb80538/.pre-commit-config.yaml000066400000000000000000000017631512231463400236750ustar00rootroot00000000000000--- 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-fb80538/.readthedocs.yaml000066400000000000000000000011061512231463400226320ustar00rootroot00000000000000# 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-fb80538/CONTRIBUTING.rst000066400000000000000000000002111512231463400220400ustar00rootroot00000000000000Please read the `Contributing `__ guidelines in the documentation site. python-openapi-openapi-core-fb80538/LICENSE000066400000000000000000000027351512231463400204210ustar00rootroot00000000000000BSD 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-fb80538/Makefile000066400000000000000000000015201512231463400210430ustar00rootroot00000000000000.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.jsonpython-openapi-openapi-core-fb80538/README.md000066400000000000000000000104701512231463400206660ustar00rootroot00000000000000# openapi-core Package version Continuous Integration Tests coverage Python versions Package format Development status ## About Openapi-core is a Python library that provides client-side and server-side support for the [OpenAPI v3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) and [OpenAPI v3.1](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) 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) ## 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.html) to find officially supported implementations. For more details read about the [Unmarshalling](https://openapi-core.readthedocs.io/en/latest/unmarshalling.html) process. If you just want to validate your request/response data without unmarshalling, read about [Validation](https://openapi-core.readthedocs.io/en/latest/validation.html) 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, and OpenAPI 3.1 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 and OpenAPI Schema Specification v3.1. - [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-fb80538/SECURITY.md000066400000000000000000000017541512231463400212050ustar00rootroot00000000000000# 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-fb80538/docs/000077500000000000000000000000001512231463400203355ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/docs/configuration.md000066400000000000000000000125771512231463400235420ustar00rootroot00000000000000--- 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` ``` 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` ``` 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` ``` 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` ``` 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) ``` ## 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-fb80538/docs/contributing.md000066400000000000000000000041331512231463400233670ustar00rootroot00000000000000--- 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. python-openapi-openapi-core-fb80538/docs/extensions.md000066400000000000000000000027311512231463400230610ustar00rootroot00000000000000--- 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-fb80538/docs/index.md000066400000000000000000000051541512231463400217730ustar00rootroot00000000000000--- 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) 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, and OpenAPI 3.1 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 and OpenAPI Schema Specification v3.1. ## License The project is under the terms of the BSD 3-Clause License. python-openapi-openapi-core-fb80538/docs/integrations/000077500000000000000000000000001512231463400230435ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/docs/integrations/aiohttp.md000066400000000000000000000021731512231463400250400ustar00rootroot00000000000000# aiohttp.web This section describes integration with [aiohttp.web](https://docs.aiohttp.org/en/stable/web.html) framework. ## 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-fb80538/docs/integrations/bottle.md000066400000000000000000000001661512231463400246610ustar00rootroot00000000000000# Bottle For more information, see the [bottle-openapi-3](https://github.com/cope-systems/bottle-openapi-3) project. python-openapi-openapi-core-fb80538/docs/integrations/django.md000066400000000000000000000072041512231463400246320ustar00rootroot00000000000000# Django This section describes the integration with the [Django](https://www.djangoproject.com) web framework. The integration supports Django version 3.0 and above. ## 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-fb80538/docs/integrations/falcon.md000066400000000000000000000046461512231463400246410ustar00rootroot00000000000000# Falcon This section describes the integration with the [Falcon](https://falconframework.org) web framework. The integration supports Falcon version 3.0 and above. !!! warning This integration does not support multipart form body requests. ## Middleware The Falcon API can be integrated using the `FalconOpenAPIMiddleware` middleware. ``` python hl_lines="1 3 7" from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) app = falcon.App( # ... middleware=[openapi_middleware], ) ``` Additional customization parameters can be passed to the middleware. ``` 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-fb80538/docs/integrations/fastapi.md000066400000000000000000000035521512231463400250210ustar00rootroot00000000000000# FastAPI This section describes integration with [FastAPI](https://fastapi.tiangolo.com) ASGI framework. !!! 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-fb80538/docs/integrations/flask.md000066400000000000000000000053041512231463400244670ustar00rootroot00000000000000# Flask This section describes integration with the [Flask](https://flask.palletsprojects.com) web framework. ## 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-fb80538/docs/integrations/index.md000066400000000000000000000003021512231463400244670ustar00rootroot00000000000000# 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-fb80538/docs/integrations/pyramid.md000066400000000000000000000001631512231463400250320ustar00rootroot00000000000000# Pyramid For more information, see the [pyramid_openapi3](https://github.com/niteoweb/pyramid_openapi3) project. python-openapi-openapi-core-fb80538/docs/integrations/requests.md000066400000000000000000000030021512231463400252330ustar00rootroot00000000000000# 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-fb80538/docs/integrations/starlette.md000066400000000000000000000047671512231463400254120ustar00rootroot00000000000000# Starlette This section describes integration with the [Starlette](https://www.starlette.io) ASGI framework. ## 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-fb80538/docs/integrations/tornado.md000066400000000000000000000001611512231463400250310ustar00rootroot00000000000000# Tornado For more information, see the [tornado-openapi3](https://github.com/correl/tornado-openapi3) project. python-openapi-openapi-core-fb80538/docs/integrations/werkzeug.md000066400000000000000000000023021512231463400252250ustar00rootroot00000000000000# 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-fb80538/docs/reference/000077500000000000000000000000001512231463400222735ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/docs/reference/configurations.md000066400000000000000000000000521512231463400256440ustar00rootroot00000000000000# `Config` class ::: openapi_core.Config python-openapi-openapi-core-fb80538/docs/reference/datatypes.md000066400000000000000000000002401512231463400246070ustar00rootroot00000000000000# Datatypes ::: openapi_core.unmarshalling.request.datatypes.RequestUnmarshalResult ::: openapi_core.unmarshalling.response.datatypes.ResponseUnmarshalResult python-openapi-openapi-core-fb80538/docs/reference/index.md000066400000000000000000000002001512231463400237140ustar00rootroot00000000000000# Reference Documentation with information on functions, classes, methods, and all other parts of the OpenAPI-core public API. python-openapi-openapi-core-fb80538/docs/reference/openapi.md000066400000000000000000000005061512231463400242510ustar00rootroot00000000000000# `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-fb80538/docs/reference/protocols.md000066400000000000000000000001231512231463400246350ustar00rootroot00000000000000# `Request`, `WebhookRequest` and `Response` protocols ::: openapi_core.protocols python-openapi-openapi-core-fb80538/docs/reference/types.md000066400000000000000000000000401512231463400237530ustar00rootroot00000000000000# Types ::: openapi_core.types python-openapi-openapi-core-fb80538/docs/security.md000066400000000000000000000014311512231463400225250ustar00rootroot00000000000000--- 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-fb80538/docs/unmarshalling.md000066400000000000000000000057041512231463400235310ustar00rootroot00000000000000--- 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. 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 only. 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 only. 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-fb80538/docs/validation.md000066400000000000000000000043161512231463400230150ustar00rootroot00000000000000--- 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`. 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 only 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)). ## Response validation Use the `validate_response` function to validate response data against a given spec. By default, the OpenAPI spec version is detected: ```python from openapi_core import validate_response # 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 only 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)). python-openapi-openapi-core-fb80538/mkdocs.yml000066400000000000000000000054411512231463400214140ustar00rootroot00000000000000site_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-fb80538/openapi_core/000077500000000000000000000000001512231463400220505ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/__init__.py000066400000000000000000000066451512231463400241740ustar00rootroot00000000000000"""OpenAPI core module""" from openapi_core.app import OpenAPI from openapi_core.configurations import Config 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.spec.paths import Spec from openapi_core.unmarshalling.request import V3RequestUnmarshaller from openapi_core.unmarshalling.request import V3WebhookRequestUnmarshaller from openapi_core.unmarshalling.request import V30RequestUnmarshaller from openapi_core.unmarshalling.request import V31RequestUnmarshaller from openapi_core.unmarshalling.request import V31WebhookRequestUnmarshaller from openapi_core.unmarshalling.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.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.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 __author__ = "Artur Maciag" __email__ = "maciag.artur@gmail.com" __version__ = "0.22.0" __url__ = "https://github.com/python-openapi/openapi-core" __license__ = "BSD 3-Clause License" __all__ = [ "OpenAPI", "Config", "Spec", "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", "V30RequestUnmarshaller", "V30ResponseUnmarshaller", "V31RequestUnmarshaller", "V31ResponseUnmarshaller", "V31WebhookRequestUnmarshaller", "V31WebhookResponseUnmarshaller", "V3RequestUnmarshaller", "V3ResponseUnmarshaller", "V3WebhookRequestUnmarshaller", "V3WebhookResponseUnmarshaller", "V30RequestValidator", "V30ResponseValidator", "V31RequestValidator", "V31ResponseValidator", "V31WebhookRequestValidator", "V31WebhookResponseValidator", "V3RequestValidator", "V3ResponseValidator", "V3WebhookRequestValidator", "V3WebhookResponseValidator", ] python-openapi-openapi-core-fb80538/openapi_core/app.py000066400000000000000000000714241512231463400232120ustar00rootroot00000000000000"""OpenAPI core app module""" from functools import cached_property from pathlib import Path 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) def _get_version(self) -> SpecVersion: try: return get_spec_version(self.spec.contents()) # 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.contents(), base_uri=self.config.spec_base_uri or self.spec.accessor.resolver._base_uri, # type: ignore[attr-defined] 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, 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, ) @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, 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, ) @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, 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, 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, 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, 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 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 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 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 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 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 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-fb80538/openapi_core/casting/000077500000000000000000000000001512231463400235005ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/casting/__init__.py000066400000000000000000000000001512231463400255770ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/casting/schemas/000077500000000000000000000000001512231463400251235ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/casting/schemas/__init__.py000066400000000000000000000035271512231463400272430ustar00rootroot00000000000000from collections import OrderedDict 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 __all__ = [ "oas30_write_schema_casters_factory", "oas30_read_schema_casters_factory", "oas31_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, PrimitiveCaster, ) oas31_types_caster = TypesCaster( oas31_casters_dict, PrimitiveCaster, multi=PrimitiveCaster, ) 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, ) python-openapi-openapi-core-fb80538/openapi_core/casting/schemas/casters.py000066400000000000000000000155471512231463400271550ustar00rootroot00000000000000from 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 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.getkey( "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.getkey("nullable", False): return value schema_type = self.schema.getkey("type") type_caster = self.get_type_caster(schema_type) if value is None: return value try: return type_caster(value) except (ValueError, TypeError): raise CastError(value, schema_type) 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-fb80538/openapi_core/casting/schemas/exceptions.py000066400000000000000000000005311512231463400276550ustar00rootroot00000000000000from 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 def __str__(self) -> str: return f"Failed to cast value to {self.type} type: {self.value}" python-openapi-openapi-core-fb80538/openapi_core/casting/schemas/factories.py000066400000000000000000000021351512231463400274550ustar00rootroot00000000000000from 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, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, ) -> SchemaCaster: schema_validator = self.schema_validators_factory.create( schema, format_validators=format_validators, extra_format_validators=extra_format_validators, ) return SchemaCaster(schema, schema_validator, self.types_caster) python-openapi-openapi-core-fb80538/openapi_core/configurations.py000066400000000000000000000052561512231463400254640ustar00rootroot00000000000000from 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. """ 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-fb80538/openapi_core/contrib/000077500000000000000000000000001512231463400235105ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/__init__.py000066400000000000000000000000001512231463400256070ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/aiohttp/000077500000000000000000000000001512231463400251605ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/aiohttp/__init__.py000066400000000000000000000003501512231463400272670ustar00rootroot00000000000000from openapi_core.contrib.aiohttp.requests import AIOHTTPOpenAPIWebRequest from openapi_core.contrib.aiohttp.responses import AIOHTTPOpenAPIWebResponse __all__ = [ "AIOHTTPOpenAPIWebRequest", "AIOHTTPOpenAPIWebResponse", ] python-openapi-openapi-core-fb80538/openapi_core/contrib/aiohttp/requests.py000066400000000000000000000023141512231463400274050ustar00rootroot00000000000000"""OpenAPI core contrib aiohttp requests module""" from __future__ import annotations 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-fb80538/openapi_core/contrib/aiohttp/responses.py000066400000000000000000000017131512231463400275550ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/000077500000000000000000000000001512231463400247525ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/django/__init__.py000066400000000000000000000004001512231463400270550ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/decorators.py000066400000000000000000000071161512231463400274760ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/handlers.py000066400000000000000000000042011512231463400271210ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/integrations.py000066400000000000000000000024071512231463400300350ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/django/middlewares.py000066400000000000000000000025431512231463400276300ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/providers.py000066400000000000000000000017201512231463400273410ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/requests.py000066400000000000000000000055641512231463400272110ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/django/responses.py000066400000000000000000000026001512231463400273430ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/falcon/000077500000000000000000000000001512231463400247525ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/__init__.py000066400000000000000000000003261512231463400270640ustar00rootroot00000000000000from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse __all__ = [ "FalconOpenAPIRequest", "FalconOpenAPIResponse", ] python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/handlers.py000066400000000000000000000046721512231463400271350ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/falcon/integrations.py000066400000000000000000000023071512231463400300340ustar00rootroot00000000000000from falcon.request import Request from falcon.response import Response from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse 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) python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/middlewares.py000066400000000000000000000065071512231463400276340ustar00rootroot00000000000000"""OpenAPI core contrib falcon middlewares module""" from typing import Any from typing import Type from typing import Union from falcon.request import Request from falcon.response import Response from jsonschema._utils import Unset from jsonschema.validators import _UNSET from jsonschema_path import SchemaPath from openapi_core import Config from openapi_core import OpenAPI from openapi_core.contrib.falcon.handlers import FalconOpenAPIErrorsHandler from openapi_core.contrib.falcon.handlers import ( FalconOpenAPIValidRequestHandler, ) from openapi_core.contrib.falcon.integrations import FalconIntegration from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse from openapi_core.unmarshalling.request.types import RequestUnmarshallerType from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType class FalconOpenAPIMiddleware(FalconIntegration): 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, **unmarshaller_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: Union[ RequestUnmarshallerType, Unset ] = _UNSET, response_unmarshaller_cls: Union[ ResponseUnmarshallerType, Unset ] = _UNSET, request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest, response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse, errors_handler_cls: Type[ FalconOpenAPIErrorsHandler ] = FalconOpenAPIErrorsHandler, **unmarshaller_kwargs: Any, ) -> "FalconOpenAPIMiddleware": config = Config( request_unmarshaller_cls=request_unmarshaller_cls, response_unmarshaller_cls=response_unmarshaller_cls, ) openapi = OpenAPI(spec, config=config) return cls( openapi, request_unmarshaller_cls=request_unmarshaller_cls, response_unmarshaller_cls=response_unmarshaller_cls, request_cls=request_cls, response_cls=response_cls, errors_handler_cls=errors_handler_cls, **unmarshaller_kwargs, ) 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) python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/requests.py000066400000000000000000000055611512231463400272060ustar00rootroot00000000000000"""OpenAPI core contrib falcon responses module""" import warnings from json import dumps from typing import Any from typing import Dict from typing import Optional from 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 unpack_params from openapi_core.datatypes import RequestParameters 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 # 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]: # Falcon doesn't store raw request stream. # That's why we need to revert deserialized data # Support falcon-jsonify. if hasattr(self.request, "json"): return dumps(self.request.json).encode("utf-8") media = self.request.get_media( default_when_empty=self.default_when_empty, ) handler, _, _ = self.request.options.media_handlers._resolve( self.request.content_type, self.request.options.default_media_type ) try: body = handler.serialize(media, content_type=self.content_type) # multipart form serialization is not supported except NotImplementedError: warnings.warn( f"body serialization for {self.request.content_type} not supported" ) return None else: assert isinstance(body, bytes) return body @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 python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/responses.py000066400000000000000000000031551512231463400273510ustar00rootroot00000000000000"""OpenAPI core contrib falcon responses module""" from io import BytesIO from itertools import tee from typing import Iterable 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) python-openapi-openapi-core-fb80538/openapi_core/contrib/falcon/util.py000066400000000000000000000005471512231463400263070ustar00rootroot00000000000000from typing import Any from typing import Generator from typing import Mapping from typing import Tuple 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-fb80538/openapi_core/contrib/falcon/views.py000066400000000000000000000000001512231463400264470ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/fastapi/000077500000000000000000000000001512231463400251375ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/fastapi/__init__.py000066400000000000000000000005121512231463400272460ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/fastapi/middlewares.py000066400000000000000000000002371512231463400300130ustar00rootroot00000000000000from openapi_core.contrib.starlette.middlewares import ( StarletteOpenAPIMiddleware as FastAPIOpenAPIMiddleware, ) __all__ = ["FastAPIOpenAPIMiddleware"] python-openapi-openapi-core-fb80538/openapi_core/contrib/fastapi/requests.py000066400000000000000000000003551512231463400273670ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/fastapi/responses.py000066400000000000000000000004731512231463400275360ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/flask/000077500000000000000000000000001512231463400246105ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/flask/__init__.py000066400000000000000000000004751512231463400267270ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/flask/decorators.py000066400000000000000000000056221512231463400273340ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/flask/handlers.py000066400000000000000000000047231512231463400267700ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/flask/integrations.py000066400000000000000000000022571512231463400276760ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/flask/providers.py000066400000000000000000000004231512231463400271760ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/flask/requests.py000066400000000000000000000020301512231463400270300ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/flask/responses.py000066400000000000000000000002211512231463400271760ustar00rootroot00000000000000from openapi_core.contrib.werkzeug.responses import ( WerkzeugOpenAPIResponse as FlaskOpenAPIResponse, ) __all__ = ["FlaskOpenAPIResponse"] python-openapi-openapi-core-fb80538/openapi_core/contrib/flask/views.py000066400000000000000000000015161512231463400263220ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/requests/000077500000000000000000000000001512231463400253635ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/requests/__init__.py000066400000000000000000000005411512231463400274740ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/requests/protocols.py000066400000000000000000000003111512231463400277540ustar00rootroot00000000000000from typing import Protocol from typing import runtime_checkable from requests.cookies import RequestsCookieJar @runtime_checkable class SupportsCookieJar(Protocol): _cookies: RequestsCookieJar python-openapi-openapi-core-fb80538/openapi_core/contrib/requests/requests.py000066400000000000000000000062131512231463400276120ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/requests/responses.py000066400000000000000000000014541512231463400277620ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/starlette/000077500000000000000000000000001512231463400255175ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/starlette/__init__.py000066400000000000000000000003501512231463400276260ustar00rootroot00000000000000from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse __all__ = [ "StarletteOpenAPIRequest", "StarletteOpenAPIResponse", ] python-openapi-openapi-core-fb80538/openapi_core/contrib/starlette/handlers.py000066400000000000000000000043361512231463400276770ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/starlette/integrations.py000066400000000000000000000035621512231463400306050ustar00rootroot00000000000000from 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-fb80538/openapi_core/contrib/starlette/middlewares.py000066400000000000000000000034571512231463400304020ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/starlette/requests.py000066400000000000000000000022531512231463400277460ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/starlette/responses.py000066400000000000000000000024061512231463400301140ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/werkzeug/000077500000000000000000000000001512231463400253535ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/contrib/werkzeug/__init__.py000066400000000000000000000003421512231463400274630ustar00rootroot00000000000000from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest from openapi_core.contrib.werkzeug.responses import WerkzeugOpenAPIResponse __all__ = [ "WerkzeugOpenAPIRequest", "WerkzeugOpenAPIResponse", ] python-openapi-openapi-core-fb80538/openapi_core/contrib/werkzeug/requests.py000066400000000000000000000027731512231463400276110ustar00rootroot00000000000000"""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-fb80538/openapi_core/contrib/werkzeug/responses.py000066400000000000000000000017201512231463400277460ustar00rootroot00000000000000"""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-fb80538/openapi_core/datatypes.py000066400000000000000000000027001512231463400244170ustar00rootroot00000000000000"""OpenAPI core validation request datatypes module""" from __future__ import annotations from dataclasses import dataclass from dataclasses import field from typing import Any from typing import Mapping from typing import Union from werkzeug.datastructures import Headers from werkzeug.datastructures import ImmutableMultiDict # Type alias for headers that accepts both Mapping and werkzeug Headers HeadersType = Union[Mapping[str, Any], Headers] @dataclass class RequestParameters: """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-fb80538/openapi_core/deserializing/000077500000000000000000000000001512231463400247015ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/deserializing/__init__.py000066400000000000000000000000001512231463400270000ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/deserializing/exceptions.py000066400000000000000000000001771512231463400274410ustar00rootroot00000000000000from openapi_core.exceptions import OpenAPIError class DeserializeError(OpenAPIError): """Deserialize operation error""" python-openapi-openapi-core-fb80538/openapi_core/deserializing/media_types/000077500000000000000000000000001512231463400272045ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/deserializing/media_types/__init__.py000066400000000000000000000023331512231463400313160ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/media_types/datatypes.py000066400000000000000000000002671512231463400315610ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict DeserializerCallable = Callable[[bytes], Any] MediaTypeDeserializersDict = Dict[str, DeserializerCallable] python-openapi-openapi-core-fb80538/openapi_core/deserializing/media_types/deserializers.py000066400000000000000000000214511512231463400324260ustar00rootroot00000000000000from 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, 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.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.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"] 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( prop_style, prop_explode, prop_schema, 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.getkey("type", "") 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-fb80538/openapi_core/deserializing/media_types/exceptions.py000066400000000000000000000007021512231463400317360ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/media_types/factories.py000066400000000000000000000072471512231463400315470ustar00rootroot00000000000000from 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, 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( schema ) ) return MediaTypeDeserializer( 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-fb80538/openapi_core/deserializing/media_types/util.py000066400000000000000000000044121512231463400305340ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/styles/000077500000000000000000000000001512231463400262245ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/deserializing/styles/__init__.py000066400000000000000000000017271512231463400303440ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/styles/datatypes.py000066400000000000000000000003521512231463400305740ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/styles/deserializers.py000066400000000000000000000030531512231463400314440ustar00rootroot00000000000000import warnings from typing import Any from typing import Mapping from typing import Optional from jsonschema_path import SchemaPath 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: SchemaPath, caster: SchemaCaster, deserializer_callable: Optional[DeserializerCallable] = None, ): self.style = style self.explode = explode self.name = name self.schema = schema self.schema_type = schema.getkey("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-fb80538/openapi_core/deserializing/styles/exceptions.py000066400000000000000000000016131512231463400307600ustar00rootroot00000000000000from 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-fb80538/openapi_core/deserializing/styles/factories.py000066400000000000000000000020701512231463400305540ustar00rootroot00000000000000from 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, style: str, explode: bool, schema: SchemaPath, name: str, ) -> StyleDeserializer: deserialize_callable = self.style_deserializers.get(style) caster = self.schema_casters_factory.create(schema) return StyleDeserializer( style, explode, name, schema, caster, deserialize_callable ) python-openapi-openapi-core-fb80538/openapi_core/deserializing/styles/util.py000066400000000000000000000133361512231463400275610ustar00rootroot00000000000000import 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-fb80538/openapi_core/exceptions.py000066400000000000000000000001711512231463400246020ustar00rootroot00000000000000"""OpenAPI core exceptions module""" class OpenAPIError(Exception): pass class SpecError(OpenAPIError): pass python-openapi-openapi-core-fb80538/openapi_core/extensions/000077500000000000000000000000001512231463400242475ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/extensions/__init__.py000066400000000000000000000000411512231463400263530ustar00rootroot00000000000000"""OpenAPI extensions package""" python-openapi-openapi-core-fb80538/openapi_core/extensions/models/000077500000000000000000000000001512231463400255325ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/extensions/models/__init__.py000066400000000000000000000000501512231463400276360ustar00rootroot00000000000000"""OpenAPI X-Model extension package""" python-openapi-openapi-core-fb80538/openapi_core/extensions/models/factories.py000066400000000000000000000021621512231463400300640ustar00rootroot00000000000000"""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.getkey("x-model") 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.getkey("x-model-path") if model_class_path is None: return super().create(schema, fields) return locate(model_class_path) python-openapi-openapi-core-fb80538/openapi_core/extensions/models/types.py000066400000000000000000000001561512231463400272520ustar00rootroot00000000000000from typing import Any from typing import Tuple from typing import Union Field = Union[str, Tuple[str, Any]] python-openapi-openapi-core-fb80538/openapi_core/protocols.py000066400000000000000000000070761512231463400244600ustar00rootroot00000000000000"""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-fb80538/openapi_core/py.typed000066400000000000000000000000001512231463400235350ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/schema/000077500000000000000000000000001512231463400233105ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/schema/__init__.py000066400000000000000000000000001512231463400254070ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/schema/encodings.py000066400000000000000000000021521512231463400256330ustar00rootroot00000000000000from 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.getkey("type") if prop_type is None: return "text/plain" if encoding else "application/octet-stream" prop_format = prop_schema.getkey("format") 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-fb80538/openapi_core/schema/parameters.py000066400000000000000000000024151512231463400260270ustar00rootroot00000000000000from 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.getkey("in", 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-fb80538/openapi_core/schema/protocols.py000066400000000000000000000004761512231463400257150ustar00rootroot00000000000000from 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-fb80538/openapi_core/schema/schemas.py000066400000000000000000000004211512231463400253020ustar00rootroot00000000000000from typing import Any from typing import Dict from jsonschema_path import SchemaPath def get_properties(schema: SchemaPath) -> Dict[str, Any]: properties = schema.get("properties", {}) properties_dict = dict(list(properties.items())) return properties_dict python-openapi-openapi-core-fb80538/openapi_core/schema/servers.py000066400000000000000000000012771512231463400253620ustar00rootroot00000000000000from 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.items()): defaults[name] = variable["default"] return defaults def get_server_url(server: SchemaPath, **variables: Any) -> str: if not variables: variables = get_server_default_variables(server) assert isinstance(server["url"], str) return server["url"].format(**variables) python-openapi-openapi-core-fb80538/openapi_core/schema/specs.py000066400000000000000000000003421512231463400247760ustar00rootroot00000000000000from 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-fb80538/openapi_core/security/000077500000000000000000000000001512231463400237175ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/security/__init__.py000066400000000000000000000002441512231463400260300ustar00rootroot00000000000000from openapi_core.security.factories import SecurityProviderFactory __all__ = ["security_provider_factory"] security_provider_factory = SecurityProviderFactory() python-openapi-openapi-core-fb80538/openapi_core/security/exceptions.py000066400000000000000000000001471512231463400264540ustar00rootroot00000000000000from openapi_core.exceptions import OpenAPIError class SecurityProviderError(OpenAPIError): pass python-openapi-openapi-core-fb80538/openapi_core/security/factories.py000066400000000000000000000013741512231463400262550ustar00rootroot00000000000000from 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"] provider_class = self.PROVIDERS[scheme_type] return provider_class(scheme) python-openapi-openapi-core-fb80538/openapi_core/security/providers.py000066400000000000000000000031341512231463400263070ustar00rootroot00000000000000import 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"] location = self.scheme["in"] 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-fb80538/openapi_core/shortcuts.py000066400000000000000000000166071512231463400244720ustar00rootroot00000000000000"""OpenAPI core shortcuts module""" from typing import Any 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 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 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 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 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 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 ) python-openapi-openapi-core-fb80538/openapi_core/spec/000077500000000000000000000000001512231463400230025ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/spec/__init__.py000066400000000000000000000000001512231463400251010ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/spec/paths.py000066400000000000000000000005271512231463400244770ustar00rootroot00000000000000import warnings from typing import Any from jsonschema_path import SchemaPath class Spec(SchemaPath): def __init__(self, *args: Any, **kwargs: Any): warnings.warn( "Spec is deprecated. Use SchemaPath from jsonschema-path package.", DeprecationWarning, ) super().__init__(*args, **kwargs) python-openapi-openapi-core-fb80538/openapi_core/templating/000077500000000000000000000000001512231463400242145ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/__init__.py000066400000000000000000000000001512231463400263130ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/datatypes.py000066400000000000000000000005331512231463400265650ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/media_types/000077500000000000000000000000001512231463400265175ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/media_types/__init__.py000066400000000000000000000000001512231463400306160ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/media_types/datatypes.py000066400000000000000000000001631512231463400310670ustar00rootroot00000000000000from collections import namedtuple MediaType = namedtuple("MediaType", ["mime_type", "parameters", "media_type"]) python-openapi-openapi-core-fb80538/openapi_core/templating/media_types/exceptions.py000066400000000000000000000007551512231463400312610ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/media_types/finders.py000066400000000000000000000046361512231463400305340ustar00rootroot00000000000000"""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.items(): if fnmatch.fnmatch(mime_type, key): return MediaType(key, parameters, value) raise MediaTypeNotFound(mimetype, list(self.content.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-fb80538/openapi_core/templating/paths/000077500000000000000000000000001512231463400253335ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/paths/__init__.py000066400000000000000000000003111512231463400274370ustar00rootroot00000000000000from openapi_core.templating.paths.finders import APICallPathFinder from openapi_core.templating.paths.finders import WebhookPathFinder __all__ = [ "APICallPathFinder", "WebhookPathFinder", ] python-openapi-openapi-core-fb80538/openapi_core/templating/paths/datatypes.py000066400000000000000000000005511512231463400277040ustar00rootroot00000000000000"""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-fb80538/openapi_core/templating/paths/exceptions.py000066400000000000000000000015111512231463400300640ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/paths/finders.py000066400000000000000000000050651512231463400273450ustar00rootroot00000000000000"""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-fb80538/openapi_core/templating/paths/iterators.py000066400000000000000000000161611512231463400277260ustar00rootroot00000000000000from 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.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.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-fb80538/openapi_core/templating/paths/parsers.py000066400000000000000000000044261512231463400273720ustar00rootroot00000000000000# Allow writing union types as X | Y in Python 3.9 from __future__ import annotations 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-fb80538/openapi_core/templating/paths/protocols.py000066400000000000000000000020251512231463400277300ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/paths/types.py000066400000000000000000000002011512231463400270420ustar00rootroot00000000000000from typing import Type from openapi_core.templating.paths.finders import BasePathFinder PathFinderType = Type[BasePathFinder] python-openapi-openapi-core-fb80538/openapi_core/templating/paths/util.py000066400000000000000000000002311512231463400266560ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/responses/000077500000000000000000000000001512231463400262355ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/responses/__init__.py000066400000000000000000000000001512231463400303340ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/responses/exceptions.py000066400000000000000000000006561512231463400307770ustar00rootroot00000000000000from 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-fb80538/openapi_core/templating/responses/finders.py000066400000000000000000000013111512231463400302350ustar00rootroot00000000000000from 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.keys())) return self.responses / "default" python-openapi-openapi-core-fb80538/openapi_core/templating/security/000077500000000000000000000000001512231463400260635ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/security/__init__.py000066400000000000000000000000001512231463400301620ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/templating/security/exceptions.py000066400000000000000000000006551512231463400306240ustar00rootroot00000000000000from 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-fb80538/openapi_core/testing/000077500000000000000000000000001512231463400235255ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/testing/__init__.py000066400000000000000000000003071512231463400256360ustar00rootroot00000000000000"""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-fb80538/openapi_core/testing/datatypes.py000066400000000000000000000010621512231463400260740ustar00rootroot00000000000000from 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-fb80538/openapi_core/testing/requests.py000066400000000000000000000024411512231463400257530ustar00rootroot00000000000000"""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-fb80538/openapi_core/testing/responses.py000066400000000000000000000010201512231463400261110ustar00rootroot00000000000000"""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-fb80538/openapi_core/types.py000066400000000000000000000002761512231463400235730ustar00rootroot00000000000000"""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-fb80538/openapi_core/typing.py000066400000000000000000000003051512231463400237320ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/000077500000000000000000000000001512231463400247145ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/__init__.py000066400000000000000000000000001512231463400270130ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/configurations.py000066400000000000000000000013511512231463400303200ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/datatypes.py000066400000000000000000000005161512231463400272660ustar00rootroot00000000000000"""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-fb80538/openapi_core/unmarshalling/integrations.py000066400000000000000000000043111512231463400277730ustar00rootroot00000000000000"""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-fb80538/openapi_core/unmarshalling/processors.py000066400000000000000000000047441512231463400275010ustar00rootroot00000000000000"""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-fb80538/openapi_core/unmarshalling/request/000077500000000000000000000000001512231463400264045ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/request/__init__.py000066400000000000000000000025131512231463400305160ustar00rootroot00000000000000"""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, ) __all__ = [ "UNMARSHALLERS", "WEBHOOK_UNMARSHALLERS", "V3RequestUnmarshaller", "V3WebhookRequestUnmarshaller", "V30RequestUnmarshaller", "V31RequestUnmarshaller", "V31WebhookRequestUnmarshaller", ] # versions mapping UNMARSHALLERS: Mapping[SpecVersion, RequestUnmarshallerType] = { versions.OPENAPIV30: V30RequestUnmarshaller, versions.OPENAPIV31: V31RequestUnmarshaller, } WEBHOOK_UNMARSHALLERS: Mapping[SpecVersion, WebhookRequestUnmarshallerType] = { versions.OPENAPIV31: V31WebhookRequestUnmarshaller, } # alias to the latest v3 version V3RequestUnmarshaller = V31RequestUnmarshaller V3WebhookRequestUnmarshaller = V31WebhookRequestUnmarshaller python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/request/datatypes.py000066400000000000000000000007601512231463400307570ustar00rootroot00000000000000"""OpenAPI core unmarshalling request datatypes module""" from __future__ import annotations from dataclasses import dataclass from dataclasses import field from typing import Any from openapi_core.datatypes import Parameters from openapi_core.unmarshalling.datatypes import BaseUnmarshalResult @dataclass class RequestUnmarshalResult(BaseUnmarshalResult): body: Any | None = None parameters: Parameters = field(default_factory=Parameters) security: dict[str, str] | None = None python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/request/processors.py000066400000000000000000000023451512231463400311640ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/request/protocols.py000066400000000000000000000076501512231463400310120ustar00rootroot00000000000000"""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, 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, 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-fb80538/openapi_core/unmarshalling/request/types.py000066400000000000000000000006651512231463400301310ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/request/unmarshallers.py000066400000000000000000000363551512231463400316520ustar00rootroot00000000000000from 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.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 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, 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, 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, ) 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, path, operation) 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 python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/response/000077500000000000000000000000001512231463400265525ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/response/__init__.py000066400000000000000000000025521512231463400306670ustar00rootroot00000000000000"""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, ) __all__ = [ "UNMARSHALLERS", "WEBHOOK_UNMARSHALLERS", "V3ResponseUnmarshaller", "V3WebhookResponseUnmarshaller", "V30ResponseUnmarshaller", "V31ResponseUnmarshaller", "V31WebhookResponseUnmarshaller", ] # versions mapping UNMARSHALLERS: Mapping[SpecVersion, ResponseUnmarshallerType] = { versions.OPENAPIV30: V30ResponseUnmarshaller, versions.OPENAPIV31: V31ResponseUnmarshaller, } WEBHOOK_UNMARSHALLERS: Mapping[ SpecVersion, WebhookResponseUnmarshallerType ] = { versions.OPENAPIV31: V31WebhookResponseUnmarshaller, } # alias to the latest v3 version V3ResponseUnmarshaller = V31ResponseUnmarshaller V3WebhookResponseUnmarshaller = V31WebhookResponseUnmarshaller python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/response/datatypes.py000066400000000000000000000006471512231463400311310ustar00rootroot00000000000000"""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-fb80538/openapi_core/unmarshalling/response/processors.py000066400000000000000000000026331512231463400313320ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/response/protocols.py000066400000000000000000000073541512231463400311610ustar00rootroot00000000000000"""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, 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, 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-fb80538/openapi_core/unmarshalling/response/types.py000066400000000000000000000007001512231463400302650ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/response/unmarshallers.py000066400000000000000000000222011512231463400320010ustar00rootroot00000000000000from 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.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 ( 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 python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/schemas/000077500000000000000000000000001512231463400263375ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/schemas/__init__.py000066400000000000000000000060421512231463400304520ustar00rootroot00000000000000from 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 __all__ = [ "oas30_format_unmarshallers", "oas31_format_unmarshallers", "oas30_write_schema_unmarshallers_factory", "oas30_read_schema_unmarshallers_factory", "oas31_schema_unmarshallers_factory", ] oas30_unmarshallers_dict = OrderedDict( [ ("object", ObjectUnmarshaller), ("array", ArrayUnmarshaller), ("boolean", PrimitiveUnmarshaller), ("integer", PrimitiveUnmarshaller), ("number", PrimitiveUnmarshaller), ("string", PrimitiveUnmarshaller), ] ) oas31_unmarshallers_dict = oas30_unmarshallers_dict.copy() oas31_unmarshallers_dict.update( { "null": PrimitiveUnmarshaller, } ) oas30_types_unmarshaller = TypesUnmarshaller( oas30_unmarshallers_dict, AnyUnmarshaller, ) oas31_types_unmarshaller = TypesUnmarshaller( oas31_unmarshallers_dict, AnyUnmarshaller, multi=MultiTypeUnmarshaller, ) oas30_format_unmarshallers = { # string compatible "date": format_date, "date-time": parse_datetime, "binary": bytes, "uuid": format_uuid, "byte": format_byte, } oas31_format_unmarshallers = oas30_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, ) # 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 ) python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/schemas/datatypes.py000066400000000000000000000002561512231463400307120ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict FormatUnmarshaller = Callable[[Any], Any] FormatUnmarshallersDict = Dict[str, FormatUnmarshaller] python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/schemas/exceptions.py000066400000000000000000000007171512231463400310770ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/schemas/factories.py000066400000000000000000000054051512231463400306740ustar00rootroot00000000000000import 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, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, format_unmarshallers: Optional[FormatUnmarshallersDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, extra_format_unmarshallers: Optional[FormatUnmarshallersDict] = None, ) -> SchemaUnmarshaller: """Create unmarshaller from the schema.""" if schema is None: raise TypeError("Invalid schema") if schema.getkey("deprecated", False): warnings.warn("The schema is deprecated", DeprecationWarning) if extra_format_validators is None: extra_format_validators = {} schema_validator = self.schema_validators_factory.create( schema, format_validators=format_validators, extra_format_validators=extra_format_validators, ) schema_format = schema.getkey("format") 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-fb80538/openapi_core/unmarshalling/schemas/unmarshallers.py000066400000000000000000000243501512231463400315750ustar00rootroot00000000000000import 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"] properties[prop_name] = self.schema_unmarshaller.evolve( prop_schema ).unmarshal(prop_value) if schema_only: return properties additional_properties = self.schema.getkey( "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.getkey("nullable", False): return value schema_type = self.schema.getkey("type") 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 str(schema.getkey("format")) return None python-openapi-openapi-core-fb80538/openapi_core/unmarshalling/schemas/util.py000066400000000000000000000012111512231463400276610ustar00rootroot00000000000000"""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-fb80538/openapi_core/unmarshalling/typing.py000066400000000000000000000007161512231463400266040ustar00rootroot00000000000000from 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-fb80538/openapi_core/unmarshalling/unmarshallers.py000066400000000000000000000113611512231463400301500ustar00rootroot00000000000000from 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, 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, ) 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( schema, format_validators=self.format_validators, extra_format_validators=self.extra_format_validators, 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-fb80538/openapi_core/util.py000066400000000000000000000012561512231463400234030ustar00rootroot00000000000000"""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-fb80538/openapi_core/validation/000077500000000000000000000000001512231463400242025ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/validation/__init__.py000066400000000000000000000000451512231463400263120ustar00rootroot00000000000000"""OpenAPI core validation module""" python-openapi-openapi-core-fb80538/openapi_core/validation/configurations.py000066400000000000000000000043721512231463400276140ustar00rootroot00000000000000from dataclasses import dataclass 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. """ 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 ) python-openapi-openapi-core-fb80538/openapi_core/validation/decorators.py000066400000000000000000000033301512231463400267200ustar00rootroot00000000000000from 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-fb80538/openapi_core/validation/exceptions.py000066400000000000000000000004231512231463400267340ustar00rootroot00000000000000"""OpenAPI core validation exceptions module""" from dataclasses import dataclass from openapi_core.exceptions import OpenAPIError @dataclass class ValidationError(OpenAPIError): def __str__(self) -> str: return f"{self.__class__.__name__}: {self.__cause__}" python-openapi-openapi-core-fb80538/openapi_core/validation/integrations.py000066400000000000000000000022371512231463400272660ustar00rootroot00000000000000"""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-fb80538/openapi_core/validation/processors.py000066400000000000000000000010311512231463400267510ustar00rootroot00000000000000"""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-fb80538/openapi_core/validation/request/000077500000000000000000000000001512231463400256725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/validation/request/__init__.py000066400000000000000000000045231512231463400300070ustar00rootroot00000000000000"""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, ) __all__ = [ "VALIDATORS", "WEBHOOK_VALIDATORS", "V30RequestBodyValidator", "V30RequestParametersValidator", "V30RequestSecurityValidator", "V30RequestValidator", "V31RequestBodyValidator", "V31RequestParametersValidator", "V31RequestSecurityValidator", "V31RequestValidator", "V31WebhookRequestBodyValidator", "V31WebhookRequestParametersValidator", "V31WebhookRequestSecurityValidator", "V31WebhookRequestValidator", "V3RequestValidator", "V3WebhookRequestValidator", ] # versions mapping VALIDATORS: Mapping[SpecVersion, RequestValidatorType] = { versions.OPENAPIV30: V30RequestValidator, versions.OPENAPIV31: V31RequestValidator, } WEBHOOK_VALIDATORS: Mapping[SpecVersion, WebhookRequestValidatorType] = { versions.OPENAPIV31: V31WebhookRequestValidator, } # alias to the latest v3 version V3RequestValidator = V31RequestValidator V3WebhookRequestValidator = V31WebhookRequestValidator python-openapi-openapi-core-fb80538/openapi_core/validation/request/datatypes.py000066400000000000000000000002661512231463400302460ustar00rootroot00000000000000from openapi_core.datatypes import Parameters from openapi_core.datatypes import RequestParameters # Backward compatibility __all__ = [ "Parameters", "RequestParameters", ] python-openapi-openapi-core-fb80538/openapi_core/validation/request/exceptions.py000066400000000000000000000041751512231463400304340ustar00rootroot00000000000000from 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: 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": return cls(spec["name"], spec["in"]) 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-fb80538/openapi_core/validation/request/protocols.py000066400000000000000000000065321512231463400302760ustar00rootroot00000000000000"""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, ): ... 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, ): ... def iter_errors( self, request: WebhookRequest, ) -> Iterator[Exception]: ... def validate( self, request: WebhookRequest, ) -> None: ... python-openapi-openapi-core-fb80538/openapi_core/validation/request/types.py000066400000000000000000000006131512231463400274100ustar00rootroot00000000000000from 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-fb80538/openapi_core/validation/request/validators.py000066400000000000000000000422471512231463400304250ustar00rootroot00000000000000"""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.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.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.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, ): 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, ) 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, path, operation) 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 = operation.get("parameters", []) path_params = path.get("parameters", []) errors = [] seen = set() validated = Parameters() params_iter = chainiters(operation_params, path_params) for param in params_iter: param_name = param["name"] param_location = param["in"] 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"] deprecated = param.getkey("deprecated", False) if deprecated: warnings.warn( f"{name} parameter is deprecated", DeprecationWarning, ) param_location = param["in"] location = parameters[param_location] try: value, _ = self._get_param_or_header_and_schema(param, location) except KeyError: required = param.getkey("required", False) if required: 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.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.getkey("required", 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 python-openapi-openapi-core-fb80538/openapi_core/validation/response/000077500000000000000000000000001512231463400260405ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/validation/response/__init__.py000066400000000000000000000037631512231463400301620ustar00rootroot00000000000000"""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, ) __all__ = [ "VALIDATORS", "WEBHOOK_VALIDATORS", "V30ResponseDataValidator", "V30ResponseHeadersValidator", "V30ResponseValidator", "V31ResponseDataValidator", "V31ResponseHeadersValidator", "V31ResponseValidator", "V31WebhookResponseDataValidator", "V31WebhookResponseHeadersValidator", "V31WebhookResponseValidator", "V3ResponseValidator", "V3WebhookResponseValidator", ] # versions mapping VALIDATORS: Mapping[SpecVersion, ResponseValidatorType] = { versions.OPENAPIV30: V30ResponseValidator, versions.OPENAPIV31: V31ResponseValidator, } WEBHOOK_VALIDATORS: Mapping[SpecVersion, WebhookResponseValidatorType] = { versions.OPENAPIV31: V31WebhookResponseValidator, } # alias to the latest v3 version V3ResponseValidator = V31ResponseValidator V3WebhookResponseValidator = V31WebhookResponseValidator python-openapi-openapi-core-fb80538/openapi_core/validation/response/exceptions.py000066400000000000000000000023641512231463400306000ustar00rootroot00000000000000from 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-fb80538/openapi_core/validation/response/protocols.py000066400000000000000000000063111512231463400304370ustar00rootroot00000000000000"""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, ): ... 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, ): ... def iter_errors( self, request: WebhookRequest, response: Response, ) -> Iterator[Exception]: ... def validate( self, request: WebhookRequest, response: Response, ) -> None: ... python-openapi-openapi-core-fb80538/openapi_core/validation/response/types.py000066400000000000000000000006261512231463400275620ustar00rootroot00000000000000from 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-fb80538/openapi_core/validation/response/validators.py000066400000000000000000000307771512231463400306000ustar00rootroot00000000000000"""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_core.casting.schemas import oas30_read_schema_casters_factory from openapi_core.casting.schemas import oas31_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.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 {} response_headers = operation_response / "headers" errors: List[OpenAPIError] = [] validated: Dict[str, Any] = {} for name, header in list(response_headers.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.getkey("deprecated", False) if deprecated: 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.getkey("required", 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 python-openapi-openapi-core-fb80538/openapi_core/validation/schemas/000077500000000000000000000000001512231463400256255ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/openapi_core/validation/schemas/__init__.py000066400000000000000000000015221512231463400277360ustar00rootroot00000000000000from openapi_schema_validator import OAS30ReadValidator from openapi_schema_validator import OAS30WriteValidator from openapi_schema_validator import OAS31Validator from openapi_core.validation.schemas.factories import SchemaValidatorsFactory __all__ = [ "oas30_write_schema_validators_factory", "oas30_read_schema_validators_factory", "oas31_schema_validators_factory", ] oas30_write_schema_validators_factory = SchemaValidatorsFactory( OAS30WriteValidator, ) oas30_read_schema_validators_factory = SchemaValidatorsFactory( OAS30ReadValidator, ) oas31_schema_validators_factory = SchemaValidatorsFactory( OAS31Validator, # FIXME: OpenAPI 3.1 schema validator uses OpenAPI 3.0 format checker. # See https://github.com/python-openapi/openapi-core/issues/506 format_checker=OAS30ReadValidator.FORMAT_CHECKER, ) python-openapi-openapi-core-fb80538/openapi_core/validation/schemas/datatypes.py000066400000000000000000000002471512231463400302000ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict FormatValidator = Callable[[Any], bool] FormatValidatorsDict = Dict[str, FormatValidator] python-openapi-openapi-core-fb80538/openapi_core/validation/schemas/exceptions.py000066400000000000000000000011431512231463400303570ustar00rootroot00000000000000from 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 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-fb80538/openapi_core/validation/schemas/factories.py000066400000000000000000000044771512231463400301720ustar00rootroot00000000000000from copy import deepcopy from typing import Optional from typing import Type from jsonschema._format import FormatChecker from jsonschema.protocols import Validator from jsonschema_path import SchemaPath from openapi_core.validation.schemas.datatypes import FormatValidatorsDict from openapi_core.validation.schemas.validators import SchemaValidator class SchemaValidatorsFactory: def __init__( self, schema_validator_class: Type[Validator], format_checker: Optional[FormatChecker] = None, ): self.schema_validator_class = schema_validator_class if format_checker is None: format_checker = self.schema_validator_class.FORMAT_CHECKER self.format_checker = format_checker def get_format_checker( self, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, ) -> FormatChecker: if format_validators is None: format_checker = deepcopy(self.format_checker) else: format_checker = FormatChecker([]) format_checker = self._add_validators( format_checker, format_validators ) format_checker = self._add_validators( 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, schema: SchemaPath, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, ) -> Validator: format_checker = self.get_format_checker( format_validators, extra_format_validators ) with schema.resolve() as resolved: jsonschema_validator = self.schema_validator_class( resolved.contents, _resolver=resolved.resolver, format_checker=format_checker, ) return SchemaValidator(schema, jsonschema_validator) python-openapi-openapi-core-fb80538/openapi_core/validation/schemas/validators.py000066400000000000000000000170641512231463400303570ustar00rootroot00000000000000import 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.getkey("type", "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.getkey("type") 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.getkey("format") 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.getkey("type") 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-fb80538/openapi_core/validation/validators.py000066400000000000000000000262371512231463400267360ustar00rootroot00000000000000"""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, ): 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 @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( schema, format_validators=self.format_validators, extra_format_validators=self.extra_format_validators, ) deserializer = self.media_type_deserializers_factory.create( 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"] style, explode = get_style_and_explode(param_or_header) schema = param_or_header / "schema" deserializer = self.style_deserializers_factory.create( style, explode, schema, name=name ) return deserializer.deserialize(location) def _validate_schema(self, schema: SchemaPath, value: Any) -> None: validator = self.schema_validators_factory.create( schema, format_validators=self.format_validators, extra_format_validators=self.extra_format_validators, ) 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.getkey("allowEmptyValue") 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"], 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.getkey("in", "header") if ( location_name == "query" and deserialised == "" and not allow_empty_values ): param_or_header_name = param_or_header["name"] 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"] 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-fb80538/poetry.lock000066400000000000000000010770671512231463400216230ustar00rootroot00000000000000# 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"}, ] [[package]] name = "aiohttp" version = "3.13.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"}, {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"}, {file = "aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6"}, {file = "aiohttp-3.13.2-cp310-cp310-win32.whl", hash = "sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251"}, {file = "aiohttp-3.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8"}, {file = "aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec"}, {file = "aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248"}, {file = "aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e"}, {file = "aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23"}, {file = "aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254"}, {file = "aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a"}, {file = "aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940"}, {file = "aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c"}, {file = "aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734"}, {file = "aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329"}, {file = "aiohttp-3.13.2-cp39-cp39-win32.whl", hash = "sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084"}, {file = "aiohttp-3.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5"}, {file = "aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca"}, ] [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 ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi ; 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 == \"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.dependencies] typing_extensions = {version = ">=4.0", markers = "python_version < \"3.10\""} [[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"}, ] [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 = false python-versions = ">=3.8" groups = ["main", "dev"] 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", "dev"] 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"}, ] [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 = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, ] [package.dependencies] typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" groups = ["main", "dev"] markers = "python_version < \"3.11\"" 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"}, ] [[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 = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" pytokens = ">=0.3.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)"] [[package]] name = "blinker" version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" groups = ["main", "dev"] 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"}, ] [[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"}, ] [[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"}, ] [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 = "platform_system == \"Windows\""} [[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 = "deptry" version = "0.23.1" description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "deptry-0.23.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f0b231d098fb5b48d8973c9f192c353ffdd395770063424969fa7f15ddfea7d8"}, {file = "deptry-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf057f514bb2fa18a2b192a7f7372bd14577ff46b11486933e8383dfef461983"}, {file = "deptry-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ee3f5663bb1c048e2aaf25a4d9e6d09cc1f3b3396ee248980878c6a6c9c0e21"}, {file = "deptry-0.23.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae0366dc5f50a5fb29cf90de1110c5e368513de6c1b2dac439f2817f3f752616"}, {file = "deptry-0.23.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ab156a90a9eda5819aeb1c1da585dd4d5ec509029399a38771a49e78f40db90f"}, {file = "deptry-0.23.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:651c7eb168233755152fcc468713c024d64a03069645187edb4a17ba61ce6133"}, {file = "deptry-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:8da1e8f70e7086ebc228f3a4a3cfb5aa127b09b5eef60d694503d6bb79809025"}, {file = "deptry-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:f589497a5809717db4dcf2aa840f2847c0a4c489331608e538850b6a9ab1c30b"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6af91d86380ef703adb6ae65f273d88e3cca7fd315c4c309da857a0cfa728244"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:42a249d317c3128c286035a1f7aaa41a0c3c967f17848817c2e07ca50d5ed450"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d988c7c75201997970bae1e8d564b4c7a14d350556c4f7c269fd33f3b081c314"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae13d8e65ae88b77632c45edb4038301a6f9efcac06715abfde9a029e5879698"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:40058a7a3fe9dacb745668897ee992e58daf5aac406b668ff2eaaf0f6f586550"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d111cf4261eeadbdb20051d8d542f04deb3cfced0cb280ece8d654f7f6055921"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9f9bbb92f95ada9ccfa5ecefee05ba3c39cfa0734b5483a3a1a3c4eeb9c99054"}, {file = "deptry-0.23.1.tar.gz", hash = "sha256:5d23e0ef25f3c56405c05383a476edda55944563c5c47a3e9249ed3ec860d382"}, ] [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 = "4.2.27" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "django-4.2.27-py3-none-any.whl", hash = "sha256:f393a394053713e7d213984555c5b7d3caeee78b2ccb729888a0774dff6c11a8"}, {file = "django-4.2.27.tar.gz", hash = "sha256:b865fbe0f4a3d1ee36594c5efa42b20db3c8bbb10dff0736face1c6e4bda5b92"}, ] [package.dependencies] asgiref = ">=3.6.0,<4" 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.16.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, ] [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"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "falcon" version = "4.2.0" description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." optional = false python-versions = ">=3.9" groups = ["main", "dev"] 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.124.4" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "fastapi-0.124.4-py3-none-any.whl", hash = "sha256:6d1e703698443ccb89e50abe4893f3c84d9d6689c0cf1ca4fad6d3c15cf69f15"}, {file = "fastapi-0.124.4.tar.gz", hash = "sha256:0e9422e8d6b797515f33f500309f6e1c98ee4e85563ba0f2debb282df6343763"}, ] [package.dependencies] annotated-doc = ">=0.0.2" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.40.0,<0.51.0" typing-extensions = ">=4.8.0" [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)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "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)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" version = "3.13.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[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.2" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c"}, {file = "flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87"}, ] [package.dependencies] blinker = ">=1.9.0" click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} 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"}, ] [[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.3.0" description = "Griffe extension for PEP 727 – Documentation Metadata in Typing." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "griffe_typingdoc-0.3.0-py3-none-any.whl", hash = "sha256:4f6483fff7733a679d1dce142fb029f314125f3caaf0d620eb82e7390c8564bb"}, {file = "griffe_typingdoc-0.3.0.tar.gz", hash = "sha256:59d9ef98d02caa7aed88d8df1119c9e48c02ed049ea50ce4018ace9331d20f8b"}, ] [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"}, ] [[package]] name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "docs"] markers = "python_version < \"3.10\"" files = [ {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[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 = "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 = "6.1.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" groups = ["dev"] files = [ {file = "isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784"}, {file = "isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481"}, ] [package.dependencies] importlib-metadata = {version = ">=4.6.0", markers = "python_version < \"3.10\""} [package.extras] colors = ["colorama"] plugins = ["setuptools"] [[package]] name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" groups = ["main", "dev"] 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 = "jinja2" version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "dev", "docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "jsonschema" version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, ] [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)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-path" version = "0.3.4" description = "JSONSchema Spec with object-oriented paths" optional = false python-versions = "<4.0.0,>=3.8.0" groups = ["main"] files = [ {file = "jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8"}, {file = "jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001"}, ] [package.dependencies] pathable = ">=0.4.1,<0.5.0" PyYAML = ">=5.1" referencing = "<0.37.0" requests = ">=2.31.0,<3.0.0" [[package]] name = "jsonschema-specifications" version = "2023.7.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, ] [package.dependencies] referencing = ">=0.28.0" [[package]] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] [[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.7" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" groups = ["dev", "docs"] files = [ {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "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", "dev", "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 = "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" importlib-metadata = {version = ">=4.4", markers = "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.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] importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} mergedeep = ">=1.3.4" platformdirs = ">=2.2.0" pyyaml = ">=5.1" [[package]] name = "mkdocs-material" version = "9.7.0" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887"}, {file = "mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec"}, ] [package.dependencies] babel = ">=2.10" backrefs = ">=5.7.post1" colorama = ">=0.4" jinja2 = ">=3.1" markdown = ">=3.2" mkdocs = ">=1.6" mkdocs-material-extensions = ">=1.3" paginate = ">=0.5" pygments = ">=2.16" pymdown-extensions = ">=10.2" requests = ">=2.26" [package.extras] git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<12.0)"] recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] [[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 = "0.30.1" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82"}, {file = "mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f"}, ] [package.dependencies] importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.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.0" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, ] [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 = "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.6.3" description = "OpenAPI schema validation for Python" optional = false python-versions = "<4.0.0,>=3.8.0" groups = ["main"] files = [ {file = "openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3"}, {file = "openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee"}, ] [package.dependencies] jsonschema = ">=4.19.1,<5.0.0" jsonschema-specifications = ">=2023.5.2" rfc3339-validator = "*" [[package]] name = "openapi-spec-validator" version = "0.7.2" description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator" optional = false python-versions = "<4.0.0,>=3.8.0" groups = ["main"] files = [ {file = "openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60"}, {file = "openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734"}, ] [package.dependencies] jsonschema = ">=4.18.0,<5.0.0" jsonschema-path = ">=0.3.1,<0.4.0" lazy-object-proxy = ">=1.7.1,<2.0.0" openapi-schema-validator = ">=0.6.0,<0.7.0" [[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 = "pathable" version = "0.4.3" description = "Object-oriented paths" optional = false python-versions = ">=3.7.0,<4.0.0" groups = ["main"] files = [ {file = "pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14"}, {file = "pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab"}, ] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" groups = ["dev", "docs"] files = [ {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[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.3.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[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"}, ] [[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 = "pydantic" version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main", "dev"] 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", "dev"] 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 = "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 = ["dev", "docs"] files = [ {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] markdown = ">=3.6" pyyaml = "*" [package.extras] extra = ["pygments (>=2.19.1)"] [[package]] name = "pytest" version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} iniconfig = ">=1" packaging = ">=20" 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.2.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99"}, {file = "pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57"}, ] [package.dependencies] backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} pytest = ">=8.2,<9" 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 = ["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-multipart" version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] [[package]] name = "pytokens" version = "0.3.0" 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.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, ] [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 = "referencing" version = "0.30.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, ] [package.dependencies] attrs = ">=22.2.0" rpds-py = ">=0.7.0" [[package]] name = "requests" version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" groups = ["main", "dev", "docs"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requirements-parser" version = "0.13.0" description = "This is a small Python module for parsing Pip requirement files." optional = false python-versions = "<4.0,>=3.8" groups = ["dev"] files = [ {file = "requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14"}, {file = "requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418"}, ] [package.dependencies] packaging = ">=23.2" [[package]] name = "responses" version = "0.25.8" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c"}, {file = "responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4"}, ] [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", "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"}, ] [[package]] name = "sqlparse" version = "0.5.0" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"}, {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"}, ] [package.extras] dev = ["build", "hatch"] doc = ["sphinx"] [[package]] name = "starlette" version = "0.49.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f"}, {file = "starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284"}, ] [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 = "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", "dev"] 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"] markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] [[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.0" 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.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, ] [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.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [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 == \"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 = "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.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, ] [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"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.0" [[package]] name = "zipp" version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" groups = ["main", "dev", "docs"] markers = "python_version < \"3.10\"" files = [ {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] aiohttp = ["aiohttp", "multidict"] django = ["django"] falcon = ["falcon"] fastapi = ["fastapi"] flask = ["flask"] requests = ["requests"] starlette = ["aioitertools", "starlette"] [metadata] lock-version = "2.1" python-versions = "^3.9.0" content-hash = "ea4094735224df4c89a25b25349455d36667be51c620c5a7438dcd2eea9d3292" python-openapi-openapi-core-fb80538/pyproject.toml000066400000000000000000000100601512231463400223160ustar00rootroot00000000000000[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.22.0" 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.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", "Typing :: Typed", ] include = [ {path = "tests", format = "sdist"}, ] [tool.poetry.dependencies] python = "^3.9.0" django = {version = ">=3.0", optional = true} falcon = {version = ">=3.0", optional = true} flask = {version = "*", optional = true} aiohttp = {version = ">=3.0", optional = true} starlette = {version = ">=0.26.1,<0.50.0", optional = true} isodate = "*" more-itertools = "*" openapi-schema-validator = "^0.6.0" openapi-spec-validator = "^0.7.1" requests = {version = "*", optional = true} werkzeug = ">=2.1.0" jsonschema-path = "^0.3.4" jsonschema = "^4.23.0" multidict = {version = "^6.0.4", optional = true} aioitertools = {version = ">=0.11,<0.14", optional = true} fastapi = {version = ">=0.111,<0.125", optional = true} typing-extensions = "^4.8.0" [tool.poetry.extras] django = ["django"] falcon = ["falcon"] fastapi = ["fastapi"] flask = ["flask"] requests = ["requests"] aiohttp = ["aiohttp", "multidict"] starlette = ["starlette", "aioitertools"] [tool.poetry.group.dev.dependencies] black = ">=23.3,<26.0" django = ">=3.0" djangorestframework = "^3.11.2" falcon = ">=3.0" flask = "*" isort = ">=5.11.5,<7.0.0" pre-commit = "*" pytest = "^8" pytest-flake8 = "*" pytest-cov = "*" python-multipart = "*" responses = "*" starlette = ">=0.26.1,<0.50.0" strict-rfc3339 = "^0.7" webob = "*" mypy = "^1.2" httpx = ">=0.24,<0.29" deptry = ">=0.11,<0.24" aiohttp = "^3.8.4" pytest-aiohttp = "^1.1.0" pyflakes = "^3.1.0" fastapi = ">=0.111,<0.125" tbump = "^6.11.0" [tool.poetry.group.docs.dependencies] mkdocs = "^1.6.1" mkdocstrings = {extras = ["python"], version = ">=0.26.1,<0.31.0"} mkdocs-material = "^9.5.34" griffe-typingdoc = ">=0.2.7,<0.4.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 = "auto" 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.22.0" regex = ''' (?P\d+) \. (?P\d+) \. (?P\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" python-openapi-openapi-core-fb80538/tests/000077500000000000000000000000001512231463400205475ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/benchmarks/000077500000000000000000000000001512231463400226645ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/benchmarks/bench_paths.py000066400000000000000000000105141512231463400255150ustar00rootroot00000000000000#!/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-fb80538/tests/integration/000077500000000000000000000000001512231463400230725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/conftest.py000066400000000000000000000044341512231463400252760ustar00rootroot00000000000000from 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 from openapi_core import Spec 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) def spec_from_file(spec_file): schema_path = schema_path_from_file(spec_file) return Spec(schema_path) def spec_from_url(base_uri): schema_path = schema_path_from_url(base_uri) return Spec(schema_path) @pytest.fixture(scope="session") def data_gif(): return b64decode( """ 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 spec_factory(schema_path_factory): return Factory( from_file=spec_from_file, from_url=spec_from_url, ) @pytest.fixture(scope="session") def v30_petstore_content(content_factory): content, _ = content_factory.from_file("data/v3.0/petstore.yaml") 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-fb80538/tests/integration/contrib/000077500000000000000000000000001512231463400245325ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/000077500000000000000000000000001512231463400262025ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/conftest.py000066400000000000000000000067371512231463400304160ustar00rootroot00000000000000import asyncio import pathlib from typing import Any from unittest import mock import pytest 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.fixture async def client(app, aiohttp_client) -> TestClient: return await aiohttp_client(app) python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/000077500000000000000000000000001512231463400271135ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/000077500000000000000000000000001512231463400276015ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttp_factory.yaml000066400000000000000000000035351512231463400336720ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/000077500000000000000000000000001512231463400326405ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400346600ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject__main__.py000066400000000000000000000004121512231463400346500ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpprojectfrom aiohttp import web from aiohttpproject.pets.views import PetPhotoView routes = [ web.view("/v1/pets/{petId}/photo", PetPhotoView), ] def get_app(loop=None): app = web.Application(loop=loop) app.add_routes(routes) return app app = get_app() openapi.py000066400000000000000000000003701512231463400345660ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpprojectfrom 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-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/000077500000000000000000000000001512231463400336135ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400356330ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/petsviews.py000066400000000000000000000035621512231463400352510ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/petsfrom 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-fb80538/tests/integration/contrib/aiohttp/test_aiohttp_project.py000066400000000000000000000036611512231463400330170ustar00rootroot00000000000000import os import sys from base64 import b64encode from io import BytesIO import pytest @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.fixture async def client(app, aiohttp_client): return await aiohttp_client(app) 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-fb80538/tests/integration/contrib/aiohttp/test_aiohttp_validation.py000066400000000000000000000052241512231463400335000ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING from unittest import mock import pytest if TYPE_CHECKING: from aiohttp.test_utils import TestClient 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-fb80538/tests/integration/contrib/django/000077500000000000000000000000001512231463400257745ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/000077500000000000000000000000001512231463400267055ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/000077500000000000000000000000001512231463400273735ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/000077500000000000000000000000001512231463400322245ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400342440ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoprojectpython-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/auth.py000066400000000000000000000010131512231463400335320ustar00rootroot00000000000000from 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-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/pets/000077500000000000000000000000001512231463400331775ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400352170ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/petsmigrations/000077500000000000000000000000001512231463400352745ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/pets__init__.py000066400000000000000000000000001512231463400373730ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/pets/migrationsviews.py000066400000000000000000000066561512231463400346440ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/petsfrom 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.py000066400000000000000000000063551512231463400343700ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/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-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/status/000077500000000000000000000000001512231463400335475ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400355670ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/statusmigrations/000077500000000000000000000000001512231463400356445ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/status__init__.py000066400000000000000000000000001512231463400377430ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/status/migrationsviews.py000066400000000000000000000006531512231463400352030ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/statusfrom 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-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/tags/000077500000000000000000000000001512231463400331625ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400352020ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/tagsviews.py000066400000000000000000000004721512231463400346150ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/tagsfrom 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-fb80538/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py000066400000000000000000000031631512231463400335660ustar00rootroot00000000000000"""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-fb80538/tests/integration/contrib/django/test_django_project.py000066400000000000000000000334351512231463400324050ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/falcon/000077500000000000000000000000001512231463400257745ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/conftest.py000066400000000000000000000037171512231463400302030ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/falcon/data/000077500000000000000000000000001512231463400267055ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/000077500000000000000000000000001512231463400273735ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconproject/000077500000000000000000000000001512231463400322245ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400342440ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconproject__main__.py000066400000000000000000000014221512231463400342360ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconprojectfrom 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.py000066400000000000000000000006651512231463400341610ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconprojectfrom 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-fb80538/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/000077500000000000000000000000001512231463400331775ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400352170ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconproject/petsresources.py000066400000000000000000000067101512231463400355100ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/falcon/data/v3.0/falconproject/petsfrom 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-fb80538/tests/integration/contrib/falcon/test_falcon_project.py000066400000000000000000000333061512231463400324020ustar00rootroot00000000000000from 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-fb80538/tests/integration/contrib/fastapi/000077500000000000000000000000001512231463400261615ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/000077500000000000000000000000001512231463400270725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/000077500000000000000000000000001512231463400275605ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/000077500000000000000000000000001512231463400325765ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400346160ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiproject__main__.py000066400000000000000000000004551512231463400346150ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiprojectfrom 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.py000066400000000000000000000003701512231463400345240ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiprojectfrom 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/000077500000000000000000000000001512231463400342225ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiproject__init__.py000066400000000000000000000000001512231463400363210ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routerspets.py000066400000000000000000000056531512231463400355600ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routersfrom 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-fb80538/tests/integration/contrib/fastapi/test_fastapi_project.py000066400000000000000000000256711512231463400327620ustar00rootroot00000000000000import 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): return TestClient(app, base_url="http://petstore.swagger.io") 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-fb80538/tests/integration/contrib/flask/000077500000000000000000000000001512231463400256325ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/conftest.py000066400000000000000000000013541512231463400300340ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/flask/data/000077500000000000000000000000001512231463400265435ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/000077500000000000000000000000001512231463400272315ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flask_factory.yaml000066400000000000000000000060351512231463400327500ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/contrib/flask/data/v3.0/flaskproject/000077500000000000000000000000001512231463400317205ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400337400ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flaskproject__main__.py000066400000000000000000000004271512231463400337360ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flaskprojectfrom 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.py000066400000000000000000000003701512231463400336460ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flaskprojectfrom 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-fb80538/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/000077500000000000000000000000001512231463400326735ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400347130ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flaskproject/petsviews.py000066400000000000000000000015161512231463400343260ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/flask/data/v3.0/flaskproject/petsfrom 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-fb80538/tests/integration/contrib/flask/test_flask_decorator.py000066400000000000000000000215401512231463400324070ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/flask/test_flask_project.py000066400000000000000000000035151512231463400320750ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/flask/test_flask_validator.py000066400000000000000000000031121512231463400324050ustar00rootroot00000000000000from 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-fb80538/tests/integration/contrib/flask/test_flask_views.py000066400000000000000000000150211512231463400315570ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/requests/000077500000000000000000000000001512231463400264055ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/requests/conftest.py000066400000000000000000000004501512231463400306030ustar00rootroot00000000000000import 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-fb80538/tests/integration/contrib/requests/data/000077500000000000000000000000001512231463400273165ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/requests/data/v3.1/000077500000000000000000000000001512231463400300055ustar00rootroot00000000000000requests_factory.yaml000066400000000000000000000051471512231463400342230ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/requests/data/v3.1openapi: "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-fb80538/tests/integration/contrib/requests/test_requests_validation.py000066400000000000000000000161451512231463400341120ustar00rootroot00000000000000from 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-fb80538/tests/integration/contrib/starlette/000077500000000000000000000000001512231463400265415ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/000077500000000000000000000000001512231463400274525ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/000077500000000000000000000000001512231463400301405ustar00rootroot00000000000000starlette_factory.yaml000066400000000000000000000035061512231463400345070ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0openapi: "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-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject/000077500000000000000000000000001512231463400335365ustar00rootroot00000000000000__init__.py000066400000000000000000000000001512231463400355560ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject__main__.py000066400000000000000000000023551512231463400355560ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteprojectfrom 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.py000066400000000000000000000003701512231463400354640ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteprojectfrom 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/000077500000000000000000000000001512231463400344325ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject__init__.py000066400000000000000000000000001512231463400365310ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject/petsendpoints.py000066400000000000000000000060711512231463400370130ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject/petsfrom 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/000077500000000000000000000000001512231463400344155ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject__init__.py000066400000000000000000000000001512231463400365140ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject/tagsendpoints.py000066400000000000000000000004611512231463400367730ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/starlette/data/v3.0/starletteproject/tagsfrom 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-fb80538/tests/integration/contrib/starlette/test_starlette_project.py000066400000000000000000000303201512231463400337050ustar00rootroot00000000000000import 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): return TestClient(app, base_url="http://petstore.swagger.io") @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): return TestClient(app, base_url="http://petstore.swagger.io") 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-fb80538/tests/integration/contrib/starlette/test_starlette_validation.py000066400000000000000000000076071512231463400344050ustar00rootroot00000000000000from 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): return TestClient(app, base_url="http://localhost") 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"]), ], ) client = TestClient(app, base_url="http://localhost") query_string = { "q": "string", } headers = {"content-type": "application/json"} data = {"param1": 1} response = client.post( "/browse/12/", params=query_string, json=data, headers=headers, ) 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"]), ], ) client = TestClient(app, base_url="http://localhost") query_string = { "q": "string", } headers = {"content-type": "application/json"} data = {"param1": 1} response = client.post( "/browse/12/", params=query_string, json=data, headers=headers, ) assert response.status_code == 200 assert response.json() == response_data python-openapi-openapi-core-fb80538/tests/integration/contrib/werkzeug/000077500000000000000000000000001512231463400263755ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/contrib/werkzeug/test_werkzeug_validation.py000066400000000000000000000063351512231463400340720ustar00rootroot00000000000000from 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-fb80538/tests/integration/data/000077500000000000000000000000001512231463400240035ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/000077500000000000000000000000001512231463400244715ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/django_factory.yaml000066400000000000000000000007261512231463400303530ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/empty.yaml000066400000000000000000000000201512231463400265030ustar00rootroot00000000000000openapi: "3.0.0"python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/links.yaml000066400000000000000000000020751512231463400265010ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/minimal.yaml000066400000000000000000000003001512231463400267740ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/minimal_with_servers.yaml000066400000000000000000000003621512231463400316100ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/parent-reference/000077500000000000000000000000001512231463400277165ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/parent-reference/openapi.yaml000066400000000000000000000001471512231463400322370ustar00rootroot00000000000000openapi: "3.0.0" info: title: sample version: "0.1" paths: /books: $ref: "./paths/books.yaml"python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/parent-reference/paths/000077500000000000000000000000001512231463400310355ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/parent-reference/paths/books.yaml000066400000000000000000000003111512231463400330310ustar00rootroot00000000000000get: responses: "200": description: OK content: application/json: schema: type: array items: $ref: "../schemas/book.yaml#/Book"python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/parent-reference/schemas/000077500000000000000000000000001512231463400313415ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.0/parent-reference/schemas/book.yaml000066400000000000000000000001661512231463400331620ustar00rootroot00000000000000Book: type: object properties: id: $ref: "#/BookId" title: type: string BookId: type: stringpython-openapi-openapi-core-fb80538/tests/integration/data/v3.0/path_param.yaml000066400000000000000000000006021512231463400274670ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/petstore.yaml000066400000000000000000000340161512231463400272260ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.0/read_only_write_only.yaml000066400000000000000000000015711512231463400316100ustar00rootroot00000000000000openapi: "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: truepython-openapi-openapi-core-fb80538/tests/integration/data/v3.0/security_override.yaml000066400000000000000000000015141512231463400311240ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.1/000077500000000000000000000000001512231463400244725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/data/v3.1/empty.yaml000066400000000000000000000000211512231463400265050ustar00rootroot00000000000000openapi: "3.1.0" python-openapi-openapi-core-fb80538/tests/integration/data/v3.1/links.yaml000066400000000000000000000020741512231463400265010ustar00rootroot00000000000000openapi: "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: testpython-openapi-openapi-core-fb80538/tests/integration/data/v3.1/minimal.yaml000066400000000000000000000002771512231463400270120ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.1/minimal_with_servers.yaml000066400000000000000000000003611512231463400316100ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.1/path_param.yaml000066400000000000000000000006011512231463400274670ustar00rootroot00000000000000openapi: "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-fb80538/tests/integration/data/v3.1/security_override.yaml000066400000000000000000000015131512231463400311240ustar00rootroot00000000000000openapi: "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: basicpython-openapi-openapi-core-fb80538/tests/integration/data/v3.1/webhook-example.yaml000066400000000000000000000016411512231463400304470ustar00rootroot00000000000000openapi: 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-fb80538/tests/integration/schema/000077500000000000000000000000001512231463400243325ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/schema/test_link_spec.py000066400000000000000000000025561512231463400277220ustar00rootroot00000000000000import 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-fb80538/tests/integration/schema/test_path_params.py000066400000000000000000000011271512231463400302430ustar00rootroot00000000000000import 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-fb80538/tests/integration/schema/test_spec.py000066400000000000000000000320011512231463400266710ustar00rootroot00000000000000from base64 import b64encode import pytest from jsonschema_path import SchemaPath 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"] == variable_spec.get("enum") paths = schema_path / "paths" for path_name, path in paths.items(): path_spec = spec_dict["paths"][path_name] assert path.getkey("summary") == path_spec.get("summary") assert path.getkey("description") == path_spec.get("description") 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.getkey("enum") == 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"] == operation_spec["tags"] assert operation["summary"] == operation_spec.get("summary") assert operation.getkey("description") == operation_spec.get( "description" ) ext_docs = operation.get("externalDocs") ext_docs_spec = operation_spec.get("externalDocs") assert bool(ext_docs_spec) == bool(ext_docs) if ext_docs_spec: assert ext_docs["url"] == ext_docs_spec["url"] assert ext_docs.getkey("description") == ext_docs_spec.get( "description" ) 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.getkey("enum") == 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.getkey("description") == 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.getkey("format") == schema_spec.get( "format" ) assert schema.getkey("required") == 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.getkey("format") == schema_spec.get( "format" ) assert schema.getkey( "required" ) == 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.getkey("example") == 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.getkey("required") == 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.getkey("required") ) == 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.getkey("format") == schema_spec.get("format") assert schema.getkey("required") == 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.getkey("readOnly") == schema_spec.get("readOnly") assert schema.getkey("writeOnly") == 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"] == 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 python-openapi-openapi-core-fb80538/tests/integration/test_minimal.py000066400000000000000000000034361512231463400261370ustar00rootroot00000000000000import 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-fb80538/tests/integration/test_petstore.py000066400000000000000000001734131512231463400263610ustar00rootroot00000000000000import 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="limit parameter is deprecated" ): 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) with pytest.warns(DeprecationWarning): 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-fb80538/tests/integration/unmarshalling/000077500000000000000000000000001512231463400257365ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/unmarshalling/test_read_only_write_only.py000066400000000000000000000060421512231463400336000ustar00rootroot00000000000000import 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-fb80538/tests/integration/unmarshalling/test_request_unmarshaller.py000066400000000000000000000307411512231463400336210ustar00rootroot00000000000000import 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.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, } python-openapi-openapi-core-fb80538/tests/integration/unmarshalling/test_response_unmarshaller.py000066400000000000000000000156671512231463400340010ustar00rootroot00000000000000import 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.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_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 == {} python-openapi-openapi-core-fb80538/tests/integration/unmarshalling/test_security_override.py000066400000000000000000000053211512231463400331160ustar00rootroot00000000000000from 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-fb80538/tests/integration/unmarshalling/test_unmarshallers.py000066400000000000000000001672121512231463400322400ustar00rootroot00000000000000from 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: def test_create_no_schema(self, unmarshallers_factory): with pytest.raises(TypeError): unmarshallers_factory.create(None) def test_create_schema_deprecated(self, unmarshallers_factory): schema = { "deprecated": True, } spec = SchemaPath.from_dict(schema) with pytest.warns(DeprecationWarning): unmarshallers_factory.create(spec) def test_create_formatter_not_found(self, unmarshallers_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", ): unmarshallers_factory.create(spec) @pytest.mark.parametrize( "value", [ "test", 10, 10, 3.12, ["one", "two"], True, False, ], ) def test_no_type(self, unmarshallers_factory, value): schema = {} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, value): schema = { "type": type, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, value): schema = { "type": type, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, format, value, unmarshalled ): schema = { "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, format, value, unmarshalled ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, format, value ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, format, value ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value, expected): schema = { "type": "string", "format": "byte", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == expected def test_string_date(self, unmarshallers_factory): schema = { "type": "string", "format": "date", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value, expected): schema = { "type": "string", "format": "date-time", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == expected def test_string_datetime_invalid(self, unmarshallers_factory): schema = { "type": "string", "format": "date-time", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "string", "format": "password", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = "passwd" result = unmarshaller.unmarshal(value) assert result == value def test_string_uuid(self, unmarshallers_factory): schema = { "type": "string", "format": "uuid", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = str(uuid4()) result = unmarshaller.unmarshal(value) assert result == UUID(value) def test_string_uuid_invalid(self, unmarshallers_factory): schema = { "type": "string", "format": "uuid", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, format, value, expected ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == expected @pytest.mark.parametrize("value", ["bar", "foobar"]) def test_string_pattern(self, unmarshallers_factory, value): schema = { "type": "string", "pattern": "bar", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize( "value,pattern", [ ("foo", "baz"), ("bar", "baz"), ], ) def test_string_pattern_invalid( self, unmarshallers_factory, value, pattern ): schema = { "type": "string", "pattern": pattern, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "string", "minLength": 3, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize("value", ["", "a", "ab"]) def test_string_min_length_invalid(self, unmarshallers_factory, value): schema = { "type": "string", "minLength": 3, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "string", "maxLength": 1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize("value", ["ab", "abc"]) def test_string_max_length_invalid(self, unmarshallers_factory, value): schema = { "type": "string", "maxLength": 1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value ): schema = { "type": "string", "maxLength": -1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) def test_integer_enum(self, unmarshallers_factory): schema = { "type": "integer", "enum": [1, 2, 3], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = 2 result = unmarshaller.unmarshal(value) assert result == int(value) def test_integer_enum_invalid(self, unmarshallers_factory): enum = [1, 2, 3] schema = { "type": "integer", "enum": enum, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, value): schema = { "type": "array", "items": { "type": type, }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, value): schema = { "type": "array", "items": { "type": type, }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "array", "items": { "type": "number", }, "minItems": 3, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "array", "items": { "type": "number", }, "minItems": 0, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize( "value", [ [], ], ) def test_array_max_items_invalid_schema( self, unmarshallers_factory, value ): schema = { "type": "array", "items": { "type": "number", }, "maxItems": -1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize("value", [[1, 2], [2, 3, 4]]) def test_array_max_items_invalid(self, unmarshallers_factory, value): schema = { "type": "array", "items": { "type": "number", }, "maxItems": 1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "array", "items": { "type": "number", }, "uniqueItems": True, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "object", "anyOf": [ { "type": "object", "required": ["someint"], "properties": {"someint": {"type": "integer"}}, }, { "type": "object", "required": ["somestr"], "properties": {"somestr": {"type": "string"}}, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = {"someint": 1} result = unmarshaller.unmarshal(value) assert result == value def test_object_any_of_invalid(self, unmarshallers_factory): schema = { "type": "object", "anyOf": [ { "type": "object", "required": ["someint"], "properties": {"someint": {"type": "integer"}}, }, { "type": "object", "required": ["somestr"], "properties": {"somestr": {"type": "string"}}, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal({"someint": "1"}) def test_object_one_of_default(self, unmarshallers_factory): schema = { "type": "object", "oneOf": [ { "type": "object", "properties": { "somestr": { "type": "string", "default": "defaultstring", }, }, }, { "type": "object", "required": ["otherstr"], "properties": { "otherstr": { "type": "string", }, }, }, ], "properties": { "someint": { "type": "integer", }, }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) assert unmarshaller.unmarshal({"someint": 1}) == { "someint": 1, "somestr": "defaultstring", } def test_object_any_of_default(self, unmarshallers_factory): schema = { "type": "object", "anyOf": [ { "type": "object", "properties": { "someint": { "type": "integer", }, }, }, { "type": "object", "properties": { "somestr": { "type": "string", "default": "defaultstring", }, }, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) assert unmarshaller.unmarshal({"someint": "1"}) == { "someint": "1", "somestr": "defaultstring", } def test_object_all_of_default(self, unmarshallers_factory): schema = { "type": "object", "allOf": [ { "type": "object", "properties": { "somestr": { "type": "string", "default": "defaultstring", }, }, }, { "type": "object", "properties": { "someint": { "type": "integer", "default": 1, }, }, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", "properties": { "somestr": { "type": "string", }, "someint": { "type": "integer", }, }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value ): schema = { "type": "object", "properties": { "somestr": { "type": "string", }, "someint": { "type": "integer", }, }, "additionalProperties": False, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_object_default_property(self, unmarshallers_factory, value): schema = { "type": "object", "properties": { "prop": { "type": "string", "default": "value1", } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == {"prop": "value1"} @pytest.mark.parametrize( "value", [ {"additional": 1}, ], ) def test_object_additional_properties_false( self, unmarshallers_factory, value ): schema = { "type": "object", "additionalProperties": False, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory ): schema = { "type": "object", "additionalProperties": additional_properties, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value def test_object_additional_properties_list(self, unmarshallers_factory): schema = {"type": "object"} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize( "value", [ {"additional": 1}, ], ) def test_object_additional_properties_object( self, unmarshallers_factory, value ): additional_properties = { "type": "integer", } schema = { "type": "object", "additionalProperties": additional_properties, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "minProperties": 1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "minProperties": 4, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_object_min_properties_invalid_schema( self, unmarshallers_factory, value ): schema = { "type": "object", "minProperties": 2, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "maxProperties": 3, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): schema = { "type": "object", "properties": {k: {"type": "number"} for k in ["a", "b", "c"]}, "maxProperties": 0, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_object_max_properties_invalid_schema( self, unmarshallers_factory, value ): schema = { "type": "object", "maxProperties": -1, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) def test_any_one_of(self, unmarshallers_factory): schema = { "oneOf": [ { "type": "string", }, { "type": "array", "items": { "type": "string", }, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = ["hello"] result = unmarshaller.unmarshal(value) assert result == value def test_any_any_of(self, unmarshallers_factory): schema = { "anyOf": [ { "type": "string", }, { "type": "array", "items": { "type": "string", }, }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = ["hello"] result = unmarshaller.unmarshal(value) assert result == value def test_any_all_of(self, unmarshallers_factory): schema = { "allOf": [ { "type": "array", "items": { "type": "string", }, } ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "allOf": [ { "type": "object", "required": ["somestr"], "properties": { "somestr": { "type": "string", }, }, }, { "type": "object", "required": ["someint"], "properties": { "someint": { "type": "integer", }, }, }, ], "additionalProperties": False, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) def test_any_format_one_of(self, unmarshallers_factory): schema = { "format": "date", "oneOf": [ {"type": "integer"}, { "type": "string", }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) def test_any_one_of_any(self, unmarshallers_factory): schema = { "oneOf": [ {"type": "integer"}, { "type": "string", "format": "date", }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) def test_any_any_of_any(self, unmarshallers_factory): schema = { "anyOf": [ {}, { "type": "string", "format": "date", }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = "2018-01-02" result = unmarshaller.unmarshal(value) assert result == date(2018, 1, 2) def test_any_all_of_any(self, unmarshallers_factory): schema = { "allOf": [ {}, { "type": "string", "format": "date", }, ], } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, value): any_of = [ { "type": "object", "required": ["test1"], "properties": { "test1": { "type": "string", }, }, }, { "type": "object", "required": ["test2"], "properties": { "test2": { "type": "string", }, }, }, ] schema = { "anyOf": any_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_any_one_of_no_valid(self, unmarshallers_factory, value): one_of = [ { "type": "object", "required": [ "test1", ], "properties": { "test1": { "type": "string", }, }, }, { "type": "object", "required": [ "test2", ], "properties": { "test2": { "type": "string", }, }, }, ] schema = { "oneOf": one_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_any_any_of_different_type(self, unmarshallers_factory, value): any_of = [{"type": "integer"}, {"type": "string"}] schema = { "anyOf": any_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ {}, ], ) def test_any_one_of_different_type(self, unmarshallers_factory, value): one_of = [ { "type": "integer", }, { "type": "string", }, ] schema = { "oneOf": one_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ { "foo": "FOO", }, { "foo": "FOO", "bar": "BAR", }, ], ) def test_any_any_of_unambiguous(self, 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 = { "anyOf": any_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize( "value", [ {}, ], ) def test_object_multiple_any_of(self, unmarshallers_factory, value): any_of = [ { "type": "object", }, { "type": "object", }, ] schema = { "type": "object", "anyOf": any_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value @pytest.mark.parametrize( "value", [ dict(), ], ) def test_object_multiple_one_of(self, unmarshallers_factory, value): one_of = [ { "type": "object", }, { "type": "object", }, ] schema = { "type": "object", "oneOf": one_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(InvalidSchemaValue): unmarshaller.unmarshal(value) @pytest.mark.parametrize( "value", [ { "foo": "FOO", }, { "foo": "FOO", "bar": "BAR", }, ], ) def test_any_one_of_unambiguous(self, 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 = { "oneOf": one_of, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == value class BaseTestOASS30chemaUnmarshallersFactoryCall: def test_null_undefined(self, unmarshallers_factory): schema = {"type": "null"} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(UnknownType): unmarshaller.unmarshal(None) @pytest.mark.parametrize( "type", [ "boolean", "array", "integer", "number", "string", ], ) def test_nullable(self, unmarshallers_factory, type): schema = {"type": type, "nullable": True} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(None) assert result is None @pytest.mark.parametrize( "type", [ "boolean", "array", "integer", "number", "string", ], ) def test_not_nullable(self, unmarshallers_factory, type): schema = {"type": type} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, type, format, value, unmarshalled ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == unmarshalled @pytest.mark.parametrize( "type,format,value", [ ("string", "byte", "passwd"), ("string", "binary", "test"), ], ) def test_basic_type_oas30_formats_invalid( self, unmarshallers_factory, type, format, value ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "string", } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, types, value ): schema = {"type": types} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises(SchemaError): unmarshaller.unmarshal(value) def test_integer_default_nullable(self, unmarshallers_factory): default_value = 123 schema = { "type": "integer", "default": default_value, "nullable": True, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = None result = unmarshaller.unmarshal(value) assert result is None def test_array_nullable(self, unmarshallers_factory): schema = { "type": "array", "items": { "type": "integer", }, "nullable": True, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = None result = unmarshaller.unmarshal(value) assert result is None def test_object_property_nullable(self, unmarshallers_factory): schema = { "type": "object", "properties": { "foo": { "type": "object", "nullable": True, } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = {"foo": None} result = unmarshaller.unmarshal(value) assert result == value def test_subschema_nullable(self, unmarshallers_factory): schema = { "oneOf": [ { "type": "integer", }, { "nullable": True, }, ] } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "object", "required": ["id"], "properties": { "id": { "type": "integer", "writeOnly": True, } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "object", "required": ["id"], "properties": { "id": { "type": "integer", "readOnly": True, } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory): schema = { "type": "object", "required": ["id"], "properties": { "id": { "type": "integer", "readOnly": True, } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) # 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, unmarshallers_factory): schema = { "type": "object", "required": ["id"], "properties": { "id": { "type": "integer", "writeOnly": True, } }, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) # 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=( "OpenAPI 3.1 schema validator uses OpenAPI 3.0 format checker." "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, unmarshallers_factory, type, format ): schema = { "type": type, "format": format, } spec = SchemaPath.from_dict(schema) with pytest.raises(FormatterNotFoundError): unmarshallers_factory.create(spec) @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, unmarshallers_factory, type, value): schema = { "type": type, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) with pytest.raises( InvalidSchemaValue, match=f"not valid for schema of type {type}", ): unmarshaller.unmarshal(value) def test_null(self, unmarshallers_factory): schema = {"type": "null"} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(None) assert result is None @pytest.mark.parametrize("value", ["string", 2, 3.14, True, [1, 2], {}]) def test_null_invalid(self, unmarshallers_factory, value): schema = {"type": "null"} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, types, value): schema = {"type": types} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, types, value): schema = {"type": types} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) 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, unmarshallers_factory, types, format, value, expected ): schema = { "type": types, "format": format, } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(value) assert result == expected def test_any_null(self, unmarshallers_factory): schema = {} spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) result = unmarshaller.unmarshal(None) assert result is None def test_subschema_null(self, unmarshallers_factory): schema = { "oneOf": [ { "type": "integer", }, { "type": "null", }, ] } spec = SchemaPath.from_dict(schema) unmarshaller = unmarshallers_factory.create(spec) value = None result = unmarshaller.unmarshal(value) assert result is None python-openapi-openapi-core-fb80538/tests/integration/validation/000077500000000000000000000000001512231463400252245ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/integration/validation/test_parent_reference.py000066400000000000000000000027031512231463400321460ustar00rootroot00000000000000import 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-fb80538/tests/integration/validation/test_request_validators.py000066400000000000000000000100711512231463400325540ustar00rootroot00000000000000from base64 import b64encode import pytest 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_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 python-openapi-openapi-core-fb80538/tests/integration/validation/test_response_validators.py000066400000000000000000000127611512231463400327320ustar00rootroot00000000000000import 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.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_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-fb80538/tests/integration/validation/test_strict_json_validation.py000066400000000000000000000175101512231463400334140ustar00rootroot00000000000000import 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_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-fb80538/tests/unit/000077500000000000000000000000001512231463400215265ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/casting/000077500000000000000000000000001512231463400231565ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/casting/test_schema_casters.py000066400000000000000000000033461512231463400275610ustar00rootroot00000000000000import 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 caster_factory(self): def create_caster(schema): return oas31_schema_casters_factory.create(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) python-openapi-openapi-core-fb80538/tests/unit/conftest.py000066400000000000000000000024301512231463400237240ustar00rootroot00000000000000from 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_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-fb80538/tests/unit/contrib/000077500000000000000000000000001512231463400231665ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/contrib/aiohttp/000077500000000000000000000000001512231463400246365ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/contrib/aiohttp/test_aiohttp_requests.py000066400000000000000000000003631512231463400316540ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/aiohttp/test_aiohttp_responses.py000066400000000000000000000003671512231463400320260ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/django/000077500000000000000000000000001512231463400244305ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/contrib/django/test_django.py000066400000000000000000000160031512231463400273030ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/flask/000077500000000000000000000000001512231463400242665ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/contrib/flask/conftest.py000066400000000000000000000034321512231463400264670ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/flask/test_flask_requests.py000066400000000000000000000054341512231463400307400ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/flask/test_flask_responses.py000066400000000000000000000011611512231463400310770ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/requests/000077500000000000000000000000001512231463400250415ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/contrib/requests/conftest.py000066400000000000000000000024171512231463400272440ustar00rootroot00000000000000from 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-fb80538/tests/unit/contrib/requests/test_requests_requests.py000066400000000000000000000115711512231463400322650ustar00rootroot00000000000000import 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-fb80538/tests/unit/contrib/requests/test_requests_responses.py000066400000000000000000000012571512231463400324330ustar00rootroot00000000000000import 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-fb80538/tests/unit/deserializing/000077500000000000000000000000001512231463400243575ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/deserializing/test_media_types_deserializers.py000066400000000000000000000471041512231463400332260ustar00rootroot00000000000000from 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 deserializer_factory(self): 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( 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, 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(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, 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(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, 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(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, 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(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", } python-openapi-openapi-core-fb80538/tests/unit/deserializing/test_styles_deserializers.py000066400000000000000000000322341512231463400322440ustar00rootroot00000000000000import 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 deserializer_factory(self): style_deserializers_factory = StyleDeserializersFactory( oas31_schema_casters_factory, style_deserializers=style_deserializers, ) def create_deserializer(param, name=None): name = name or param["name"] style, explode = get_style_and_explode(param) schema = param / "schema" return style_deserializers_factory.create( style, explode, schema, name=name ) 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-fb80538/tests/unit/extensions/000077500000000000000000000000001512231463400237255ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/extensions/test_factories.py000066400000000000000000000025251512231463400273210ustar00rootroot00000000000000from 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-fb80538/tests/unit/schema/000077500000000000000000000000001512231463400227665ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/schema/test_schema_parameters.py000066400000000000000000000061071512231463400300660ustar00rootroot00000000000000import 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-fb80538/tests/unit/security/000077500000000000000000000000001512231463400233755ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/security/test_providers.py000066400000000000000000000020111512231463400270150ustar00rootroot00000000000000import 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-fb80538/tests/unit/templating/000077500000000000000000000000001512231463400236725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/templating/test_media_types_finders.py000066400000000000000000000035531512231463400313260ustar00rootroot00000000000000import 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-fb80538/tests/unit/templating/test_paths_finders.py000066400000000000000000000434231512231463400301420ustar00rootroot00000000000000import 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 [ SchemaPath.from_dict( {"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-fb80538/tests/unit/templating/test_paths_parsers.py000066400000000000000000000030751512231463400301660ustar00rootroot00000000000000import 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-fb80538/tests/unit/templating/test_responses_finders.py000066400000000000000000000020451512231463400310370ustar00rootroot00000000000000from 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-fb80538/tests/unit/test_app.py000066400000000000000000000036631512231463400237270ustar00rootroot00000000000000from pathlib import Path import pytest from openapi_core import Config from openapi_core import OpenAPI from openapi_core.exceptions import SpecError 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.contents() == 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.contents() == 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.contents() == 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.contents() == spec_dict python-openapi-openapi-core-fb80538/tests/unit/test_paths_spec.py000066400000000000000000000003151512231463400252670ustar00rootroot00000000000000import pytest from openapi_core import Spec class TestSpecFromDict: def test_deprecated(self): schema = {} with pytest.warns(DeprecationWarning): Spec.from_dict(schema) python-openapi-openapi-core-fb80538/tests/unit/test_shortcuts.py000066400000000000000000001010531512231463400251750ustar00rootroot00000000000000from unittest import mock import pytest from openapi_spec_validator import OpenAPIV31SpecValidator 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 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 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 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 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 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) python-openapi-openapi-core-fb80538/tests/unit/test_util.py000066400000000000000000000011431512231463400241130ustar00rootroot00000000000000import 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-fb80538/tests/unit/unmarshalling/000077500000000000000000000000001512231463400243725ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/unmarshalling/test_path_item_params_validator.py000066400000000000000000000133561512231463400333750ustar00rootroot00000000000000from 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.validation.request.exceptions import MissingRequiredParameter from openapi_core.validation.request.exceptions import ParameterValidationError class TestPathItemParamsValidator: @pytest.fixture(scope="session") 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(scope="session") def spec(self, spec_dict): return SchemaPath.from_dict(spec_dict) @pytest.fixture(scope="session") 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" python-openapi-openapi-core-fb80538/tests/unit/unmarshalling/test_request_unmarshallers.py000066400000000000000000000106701512231463400324370ustar00rootroot00000000000000import 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-fb80538/tests/unit/unmarshalling/test_schema_unmarshallers.py000066400000000000000000000164771512231463400322220ustar00rootroot00000000000000from 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 schema_unmarshaller_factory(): 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( 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-fb80538/tests/unit/validation/000077500000000000000000000000001512231463400236605ustar00rootroot00000000000000python-openapi-openapi-core-fb80538/tests/unit/validation/test_schema_validators.py000066400000000000000000000144141512231463400307650ustar00rootroot00000000000000import 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 validator_factory(self): def create_validator(schema): return oas30_write_schema_validators_factory.create(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