pax_global_header00006660000000000000000000000064146322407650014523gustar00rootroot0000000000000052 comment=bfbd7284c30e52d54e2af54841cac20fca7f1eea python-openapi-openapi-core-a5e0959/000077500000000000000000000000001463224076500174175ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/.bumpversion.cfg000066400000000000000000000005611463224076500225310ustar00rootroot00000000000000[bumpversion] current_version = 0.19.2 tag = True tag_name = {new_version} commit = True message = Version {new_version} parse = (?P\d+)\.(?P\d+)\.(?P\d+) serialize = {major}.{minor}.{patch} [bumpversion:file:openapi_core/__init__.py] [bumpversion:file:pyproject.toml] search = version = "{current_version}" replace = version = "{new_version}" python-openapi-openapi-core-a5e0959/.editorconfig000066400000000000000000000002531463224076500220740ustar00rootroot00000000000000root = 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-a5e0959/.github/000077500000000000000000000000001463224076500207575ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/.github/FUNDING.yml000066400000000000000000000000201463224076500225640ustar00rootroot00000000000000github: [p1c2u] python-openapi-openapi-core-a5e0959/.github/ISSUE_TEMPLATE/000077500000000000000000000000001463224076500231425ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/.github/ISSUE_TEMPLATE/00_bug_report.yml000066400000000000000000000046071463224076500263430ustar00rootroot00000000000000name: "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-a5e0959/.github/ISSUE_TEMPLATE/01_enhancement.yml000066400000000000000000000017021463224076500264520ustar00rootroot00000000000000name: "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-a5e0959/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003741463224076500251360ustar00rootroot00000000000000blank_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-a5e0959/.github/dependabot.yml000066400000000000000000000003151463224076500236060ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" python-openapi-openapi-core-a5e0959/.github/workflows/000077500000000000000000000000001463224076500230145ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/.github/workflows/build-docs.yml000066400000000000000000000026431463224076500255710ustar00rootroot00000000000000name: Build documentation on: push: pull_request: types: [opened, synchronize] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v5 with: python-version: 3.9 - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v4 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install --with docs - name: Build documentation run: | poetry run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs docs/_build/html -n -W - uses: actions/upload-artifact@v4 name: Upload docs as artifact with: name: docs-html path: './docs/_build/html' if-no-files-found: error python-openapi-openapi-core-a5e0959/.github/workflows/python-publish.yml000066400000000000000000000014541463224076500265300ustar00rootroot00000000000000# 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@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' - name: Set up poetry uses: Gr1N/setup-poetry@v9 - name: Build run: poetry build - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ python-openapi-openapi-core-a5e0959/.github/workflows/python-test.yml000066400000000000000000000056151463224076500260440ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Test python code on: push: pull_request: types: [opened, synchronize] jobs: test: name: "Tests" runs-on: ubuntu-latest strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v4 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install --all-extras - name: Test env: PYTEST_ADDOPTS: "--color=yes" run: poetry run pytest - name: Static type check run: poetry run mypy - name: Check dependencies run: poetry run deptry . - name: Upload coverage uses: codecov/codecov-action@v4 static-checks: name: "Static checks" runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 - name: "Setup Python" uses: actions/setup-python@v5 with: python-version: 3.9 - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v4 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install - name: Run static checks run: poetry run pre-commit run -a python-openapi-openapi-core-a5e0959/.gitignore000066400000000000000000000023711463224076500214120ustar00rootroot00000000000000# 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-a5e0959/.pre-commit-config.yaml000066400000000000000000000017631463224076500237070ustar00rootroot00000000000000--- 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-a5e0959/.readthedocs.yaml000066400000000000000000000011631463224076500226470ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Optionally build your docs in additional formats such as PDF and ePub formats: all build: os: ubuntu-20.04 tools: python: "3.9" jobs: post_create_environment: # Install poetry - pip install poetry # Tell poetry to not use a virtual environment - poetry config virtualenvs.create false post_install: # Install dependencies - poetry install --with docs python-openapi-openapi-core-a5e0959/.travis.yml000066400000000000000000000006321463224076500215310ustar00rootroot00000000000000language: python sudo: false matrix: include: - python: 3.8 - python: 3.9 - python: 3.10 - python: nightly - python: pypy3 allow_failures: - python: nightly before_install: - curl -sL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - -y - export PATH=$PATH:$HOME/.local/bin install: - poetry install script: - poetry run pytest after_success: - codecov python-openapi-openapi-core-a5e0959/CONTRIBUTING.rst000066400000000000000000000002111463224076500220520ustar00rootroot00000000000000Please read the `Contributing `__ guidelines in the documentation site. python-openapi-openapi-core-a5e0959/LICENSE000066400000000000000000000027351463224076500204330ustar00rootroot00000000000000BSD 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-a5e0959/Makefile000066400000000000000000000014061463224076500210600ustar00rootroot00000000000000.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: sphinx-build -b html docs docs/_build docs-cleanup: @rm -rf docs/_build cleanup: dist-cleanup test-cleanup release/patch: @bump2version patch release/minor: @bump2version minor release/major: @bump2version major python-openapi-openapi-core-a5e0959/README.rst000066400000000000000000000103401463224076500211040ustar00rootroot00000000000000************ openapi-core ************ .. image:: https://img.shields.io/pypi/v/openapi-core.svg :target: https://pypi.python.org/pypi/openapi-core .. image:: https://travis-ci.org/python-openapi/openapi-core.svg?branch=master :target: https://travis-ci.org/python-openapi/openapi-core .. image:: https://img.shields.io/codecov/c/github/python-openapi/openapi-core/master.svg?style=flat :target: https://codecov.io/github/python-openapi/openapi-core?branch=master .. image:: https://img.shields.io/pypi/pyversions/openapi-core.svg :target: https://pypi.python.org/pypi/openapi-core .. image:: https://img.shields.io/pypi/format/openapi-core.svg :target: https://pypi.python.org/pypi/openapi-core .. image:: https://img.shields.io/pypi/status/openapi-core.svg :target: https://pypi.python.org/pypi/openapi-core About ##### Openapi-core is a Python library that adds client-side and server-side support for the `OpenAPI v3.0 `__ and `OpenAPI v3.1 `__ specification. Key features ############ * **Validation** and **unmarshalling** of request and response data (including webhooks) * **Integration** with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) * Customization with media type **deserializers** and format **unmarshallers** * **Security** data providers (API keys, Cookie, Basic and Bearer HTTP authentications) Documentation ############# Check documentation to see more details about the features. All documentation is in the "docs" directory and online at `openapi-core.readthedocs.io `__ Installation ############ Recommended way (via pip): .. code-block:: console pip install openapi-core Alternatively you can download the code and install from the repository: .. code-block:: console pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core First steps ########### Firstly create your OpenAPI object. .. code-block:: python from openapi_core import OpenAPI openapi = OpenAPI.from_file_path('openapi.json') Now you can use it to validate and unmarshal against requests and/or responses. .. code-block:: python # raises error if request is invalid result = openapi.unmarshal_request(request) Retrieve validated and unmarshalled request data .. code-block:: python # get parameters path_params = result.parameters.path query_params = result.parameters.query cookies_params = result.parameters.cookies headers_params = result.parameters.headers # get body body = result.body # get security data security = result.security Request object should implement OpenAPI Request protocol. Check `Integrations `__ to find officially supported implementations. For more details read about `Unmarshalling `__ process. If you just want to validate your request/response data without unmarshalling, read about `Validation `__ instead. Related projects ################ * `openapi-spec-validator `__ Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. * `openapi-schema-validator `__ Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. * `bottle-openapi-3 `__ OpenAPI 3.0 Support for the Bottle Web Framework * `pyramid_openapi3 `__ Pyramid addon for OpenAPI3 validation of requests and responses. * `tornado-openapi3 `__ Tornado OpenAPI 3 request and response validation library. License ####### The project is under the terms of BSD 3-Clause License. python-openapi-openapi-core-a5e0959/SECURITY.md000066400000000000000000000017541463224076500212170ustar00rootroot00000000000000# 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-a5e0959/docs/000077500000000000000000000000001463224076500203475ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/docs/conf.py000066400000000000000000000064561463224076500216610ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import openapi_core # -- Project information ----------------------------------------------------- project = "openapi-core" copyright = "2021, Artur Maciag" author = "Artur Maciag" # The full version, including alpha/beta/rc tags release = openapi_core.__version__ # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx_immaterial", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_immaterial" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Set link name generated in the top bar. html_title = "openapi-core" # Material theme options (see theme.conf for more information) html_theme_options = { "analytics": { "provider": "google", "property": "G-J6T05Z51NY", }, "repo_url": "https://github.com/python-openapi/openapi-core/", "repo_name": "openapi-core", "icon": { "repo": "fontawesome/brands/github-alt", "edit": "material/file-edit-outline", }, "palette": [ { "media": "(prefers-color-scheme: dark)", "scheme": "slate", "primary": "lime", "accent": "amber", "scheme": "slate", "toggle": { "icon": "material/toggle-switch", "name": "Switch to light mode", }, }, { "media": "(prefers-color-scheme: light)", "scheme": "default", "primary": "lime", "accent": "amber", "toggle": { "icon": "material/toggle-switch-off-outline", "name": "Switch to dark mode", }, }, ], # If False, expand all TOC entries "globaltoc_collapse": False, } python-openapi-openapi-core-a5e0959/docs/contributing.rst000066400000000000000000000044011463224076500236070ustar00rootroot00000000000000Contributing ============ Firstly, thank you all for taking the time to contribute. The following section describes how you can contribute to the openapi-core project on GitHub. Reporting bugs -------------- Before you report ^^^^^^^^^^^^^^^^^ * Check whether your issue does not already exist in the `Issue tracker `__. * Make sure it is not a support request or question better suited for `Discussion board `__. How to submit a report ^^^^^^^^^^^^^^^^^^^^^^ * Include clear title. * Describe your runtime environment with exact versions you use. * Describe the exact steps which reproduce the problem, including minimal code snippets. * Describe the behavior you observed after following the steps, pasting console outputs. * Describe expected behavior to see and why, including links to documentations. Code contribution ----------------- Prerequisites ^^^^^^^^^^^^^ Install `Poetry `__ by following the `official installation instructions `__. Optionally (but recommended), configure Poetry to create a virtual environment in a folder named ``.venv`` within the root directory of the project: .. code-block:: console poetry config virtualenvs.in-project true Setup ^^^^^ To create a development environment and install the runtime and development dependencies, run: .. code-block:: console poetry install Then enter the virtual environment created by Poetry: .. code-block:: console poetry shell Static checks ^^^^^^^^^^^^^ The project uses static checks using fantastic `pre-commit `__. Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit. To turn on pre-commit checks for commit operations in git, enter: .. code-block:: console pre-commit install To run all checks on your staged files, enter: .. code-block:: console pre-commit run To run all checks on all files, enter: .. code-block:: console pre-commit run --all-files Pre-commit check results are also attached to your PR through integration with Github Action. python-openapi-openapi-core-a5e0959/docs/customizations/000077500000000000000000000000001463224076500234425ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/docs/customizations/extra_format_unmarshallers.rst000066400000000000000000000014271463224076500316330ustar00rootroot00000000000000Format unmarshallers ==================== Based on ``format`` keyword, openapi-core can also unmarshal values to specific formats. Openapi-core comes with a set of built-in format unmarshallers, but it's also possible to add custom ones. Here's an example with the ``usdate`` format that converts a value to date object: .. code-block:: python :emphasize-lines: 11 from datetime import datetime def unmarshal_usdate(value): return datetime.strptime(value, "%m/%d/%y").date extra_format_unmarshallers = { 'usdate': unmarshal_usdate, } config = Config( extra_format_unmarshallers=extra_format_unmarshallers, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) result = openapi.unmarshal_response(request, response) python-openapi-openapi-core-a5e0959/docs/customizations/extra_format_validators.rst000066400000000000000000000015131463224076500311170ustar00rootroot00000000000000Format validators ================= OpenAPI defines a ``format`` keyword that hints at how a value should be interpreted, e.g. a ``string`` with the type ``date`` should conform to the RFC 3339 date format. OpenAPI comes with a set of built-in format validators, but it's also possible to add custom ones. Here's how you could add support for a ``usdate`` format that handles dates of the form MM/DD/YYYY: .. code-block:: python :emphasize-lines: 11 import re def validate_usdate(value): return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) extra_format_validators = { 'usdate': validate_usdate, } config = Config( extra_format_validators=extra_format_validators, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) openapi.validate_response(request, response) python-openapi-openapi-core-a5e0959/docs/customizations/extra_media_type_deserializers.rst000066400000000000000000000016161463224076500324500ustar00rootroot00000000000000Media type deserializers ======================== OpenAPI comes with a set of built-in media type deserializers such as: ``application/json``, ``application/xml``, ``application/x-www-form-urlencoded`` or ``multipart/form-data``. You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function: .. code-block:: python :emphasize-lines: 11 def protobuf_deserializer(message): feature = route_guide_pb2.Feature() feature.ParseFromString(message) return feature extra_media_type_deserializers = { 'application/protobuf': protobuf_deserializer, } config = Config( extra_media_type_deserializers=extra_media_type_deserializers, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) result = openapi.unmarshal_response(request, response) python-openapi-openapi-core-a5e0959/docs/customizations/index.rst000066400000000000000000000006321463224076500253040ustar00rootroot00000000000000Customizations ============== OpenAPI accepts ``Config`` object that allows users to customize the behavior validation and unmarshalling processes. .. toctree:: :maxdepth: 1 spec_validator_cls request_validator_cls response_validator_cls request_unmarshaller_cls response_unmarshaller_cls extra_media_type_deserializers extra_format_validators extra_format_unmarshallers python-openapi-openapi-core-a5e0959/docs/customizations/request_unmarshaller_cls.rst000066400000000000000000000013421463224076500313020ustar00rootroot00000000000000Request unmarshaller ==================== By default, request unmarshaller is selected based on detected specification version. In order to explicitly validate and unmarshal a: * OpenAPI 3.0 spec, import ``V30RequestUnmarshaller`` * OpenAPI 3.1 spec, import ``V31RequestUnmarshaller`` or ``V31WebhookRequestUnmarshaller`` .. code-block:: python :emphasize-lines: 1,4 from openapi_core import V31RequestUnmarshaller config = Config( request_unmarshaller_cls=V31RequestUnmarshaller, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) result = openapi.unmarshal_request(request) You can also explicitly import ``V3RequestUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. python-openapi-openapi-core-a5e0959/docs/customizations/request_validator_cls.rst000066400000000000000000000012541463224076500305740ustar00rootroot00000000000000Request validator ================= By default, request validator is selected based on detected specification version. In order to explicitly validate a: * OpenAPI 3.0 spec, import ``V30RequestValidator`` * OpenAPI 3.1 spec, import ``V31RequestValidator`` or ``V31WebhookRequestValidator`` .. code-block:: python :emphasize-lines: 1,4 from openapi_core import V31RequestValidator config = Config( request_validator_cls=V31RequestValidator, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) openapi.validate_request(request) You can also explicitly import ``V3RequestValidator`` which is a shortcut to the latest OpenAPI v3 version. python-openapi-openapi-core-a5e0959/docs/customizations/response_unmarshaller_cls.rst000066400000000000000000000012421463224076500314470ustar00rootroot00000000000000Response unmarshaller ===================== In order to explicitly validate and unmarshal a: * OpenAPI 3.0 spec, import ``V30ResponseUnmarshaller`` * OpenAPI 3.1 spec, import ``V31ResponseUnmarshaller`` or ``V31WebhookResponseUnmarshaller`` .. code-block:: python :emphasize-lines: 1,4 from openapi_core import V31ResponseUnmarshaller config = Config( response_unmarshaller_cls=V31ResponseUnmarshaller, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) result = openapi.unmarshal_response(request, response) You can also explicitly import ``V3ResponseUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. python-openapi-openapi-core-a5e0959/docs/customizations/response_validator_cls.rst000066400000000000000000000013041463224076500307360ustar00rootroot00000000000000Response validator ================== By default, response validator is selected based on detected specification version. In order to explicitly validate a: * OpenAPI 3.0 spec, import ``V30ResponseValidator`` * OpenAPI 3.1 spec, import ``V31ResponseValidator`` or ``V31WebhookResponseValidator`` .. code-block:: python :emphasize-lines: 1,4 from openapi_core import V31ResponseValidator config = Config( response_validator_cls=V31ResponseValidator, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) openapi.validate_response(request, response) You can also explicitly import ``V3ResponseValidator`` which is a shortcut to the latest OpenAPI v3 version. python-openapi-openapi-core-a5e0959/docs/customizations/spec_validator_cls.rst000066400000000000000000000007101463224076500300320ustar00rootroot00000000000000Specification validation ======================== By default, on OpenAPI creation time, the provided specification is also validated. If you know you have a valid specification already, disabling the validator can improve the performance. .. code-block:: python :emphasize-lines: 1,4,6 from openapi_core import Config config = Config( spec_validator_cls=None, ) openapi = OpenAPI.from_file_path('openapi.json', config=config) python-openapi-openapi-core-a5e0959/docs/extensions.rst000066400000000000000000000030121463224076500232740ustar00rootroot00000000000000Extensions ========== x-model ------- By default, objects are unmarshalled to dictionaries. You can use dynamically created dataclasses by providing ``x-model-path`` property inside schema definition with name of the model. .. code-block:: yaml :emphasize-lines: 5 # ... components: schemas: Coordinates: x-model: Coordinates type: object required: - lat - lon properties: lat: type: number lon: type: number As a result of unmarshalling process, you will get ``Coordinates`` class instance with ``lat`` and ``lon`` attributes. x-model-path ------------ You can use your own dataclasses, pydantic models or models generated by third party generators (i.e. `datamodel-code-generator `__) by providing ``x-model-path`` property inside schema definition with location of your class. .. code-block:: yaml :emphasize-lines: 5 ... components: schemas: Coordinates: x-model-path: foo.bar.Coordinates type: object required: - lat - lon properties: lat: type: number lon: type: number .. code-block:: python # foo/bar.py from dataclasses import dataclass @dataclass class Coordinates: lat: float lon: float As a result of unmarshalling process, you will get instance of your own dataclasses or model. python-openapi-openapi-core-a5e0959/docs/index.rst000066400000000000000000000056321463224076500222160ustar00rootroot00000000000000openapi-core ============ .. toctree:: :hidden: :maxdepth: 3 unmarshalling validation integrations/index customizations/index security extensions contributing Openapi-core is a Python library that adds client-side and server-side support for the `OpenAPI v3.0 `__ and `OpenAPI v3.1 `__ specification. Key features ------------ * :doc:`validation` and :doc:`unmarshalling ` of request and response data (including webhooks) * :doc:`Integrations ` with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) * :doc:`Customization ` with **media type deserializers** and **format unmarshallers** * :doc:`Security ` data providers (API keys, Cookie, Basic and Bearer HTTP authentications) Installation ------------ .. md-tab-set:: .. md-tab-item:: Pip + PyPI (recommented) .. code-block:: console pip install openapi-core .. md-tab-item:: Pip + the source .. code-block:: console pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core First steps ----------- Firstly create your OpenAPI object. .. code-block:: python from openapi_core import OpenAPI openapi = OpenAPI.from_file_path('openapi.json') Now you can use it to validate and unmarshal your requests and/or responses. .. code-block:: python # raises error if request is invalid result = openapi.unmarshal_request(request) Retrieve validated and unmarshalled request data .. code-block:: python # get parameters path_params = result.parameters.path query_params = result.parameters.query cookies_params = result.parameters.cookies headers_params = result.parameters.headers # get body body = result.body # get security data security = result.security Request object should implement OpenAPI Request protocol. Check :doc:`integrations/index` to find oficially supported implementations. For more details read about :doc:`unmarshalling` process. If you just want to validate your request/response data without unmarshalling, read about :doc:`validation` instead. Related projects ---------------- * `openapi-spec-validator `__ Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. * `openapi-schema-validator `__ Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. License ------- The project is under the terms of BSD 3-Clause License. python-openapi-openapi-core-a5e0959/docs/integrations/000077500000000000000000000000001463224076500230555ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/docs/integrations/aiohttp.rst000066400000000000000000000023501463224076500252570ustar00rootroot00000000000000aiohttp.web =========== This section describes integration with `aiohttp.web `__ framework. Low level --------- The integration defines classes useful for low level integration. Request ^^^^^^^ Use ``AIOHTTPOpenAPIWebRequest`` to create OpenAPI request from aiohttp.web request: .. code-block:: python from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest async def hello(request): request_body = await request.text() openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) openapi.validate_request(openapi_request) return web.Response(text="Hello, world") Response ^^^^^^^^ Use ``AIOHTTPOpenAPIWebResponse`` to create OpenAPI response from aiohttp.web response: .. code-block:: python from openapi_core.contrib.starlette import AIOHTTPOpenAPIWebResponse async def hello(request): request_body = await request.text() response = web.Response(text="Hello, world") openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) openapi_response = AIOHTTPOpenAPIWebResponse(response) result = openapi.unmarshal_response(openapi_request, openapi_response) return response python-openapi-openapi-core-a5e0959/docs/integrations/bottle.rst000066400000000000000000000001431463224076500250760ustar00rootroot00000000000000Bottle ====== See `bottle-openapi-3 `_ project. python-openapi-openapi-core-a5e0959/docs/integrations/django.rst000066400000000000000000000054141463224076500250550ustar00rootroot00000000000000Django ====== This section describes integration with `Django `__ web framework. The integration supports Django from version 3.0 and above. Middleware ---------- Django can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. Add ``DjangoOpenAPIMiddleware`` to your ``MIDDLEWARE`` list and define ``OPENAPI``. .. code-block:: python :emphasize-lines: 6,9 # settings.py from openapi_core import OpenAPI MIDDLEWARE = [ # ... 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', ] OPENAPI = OpenAPI.from_dict(spec_dict) After that all your requests and responses will be validated. Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` attribute of request object. .. code-block:: python from django.views import View class MyView(View): def get(self, request): # get parameters object with path, query, cookies and headers parameters unmarshalled_params = request.openapi.parameters # or specific location parameters unmarshalled_path_params = request.openapi.parameters.path # get body unmarshalled_body = request.openapi.body # get security data unmarshalled_security = request.openapi.security Response validation ^^^^^^^^^^^^^^^^^^^ You can skip response validation process: by setting ``OPENAPI_RESPONSE_CLS`` to ``None`` .. code-block:: python :emphasize-lines: 10 # settings.py from openapi_core import OpenAPI MIDDLEWARE = [ # ... 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', ] OPENAPI = OpenAPI.from_dict(spec_dict) OPENAPI_RESPONSE_CLS = None Low level --------- The integration defines classes useful for low level integration. Request ^^^^^^^ Use ``DjangoOpenAPIRequest`` to create OpenAPI request from Django request: .. code-block:: python from openapi_core.contrib.django import DjangoOpenAPIRequest class MyView(View): def get(self, request): openapi_request = DjangoOpenAPIRequest(request) openapi.validate_request(openapi_request) Response ^^^^^^^^ Use ``DjangoOpenAPIResponse`` to create OpenAPI response from Django response: .. code-block:: python from openapi_core.contrib.django import DjangoOpenAPIResponse class MyView(View): def get(self, request): response = JsonResponse({'hello': 'world'}) openapi_request = DjangoOpenAPIRequest(request) openapi_response = DjangoOpenAPIResponse(response) openapi.validate_response(openapi_request, openapi_response) return response python-openapi-openapi-core-a5e0959/docs/integrations/falcon.rst000066400000000000000000000050571463224076500250600ustar00rootroot00000000000000Falcon ====== This section describes integration with `Falcon `__ web framework. The integration supports Falcon from version 3.0 and above. Middleware ---------- The Falcon API can be integrated by ``FalconOpenAPIMiddleware`` middleware. .. code-block:: python :emphasize-lines: 1,3,7 from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) app = falcon.App( # ... middleware=[openapi_middleware], ) Additional customization parameters can be passed to the middleware. .. code-block:: python :emphasize-lines: 5 from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware openapi_middleware = FalconOpenAPIMiddleware.from_spec( spec, extra_format_validators=extra_format_validators, ) app = falcon.App( # ... middleware=[openapi_middleware], ) You can skip response validation process: by setting ``response_cls`` to ``None`` .. code-block:: python :emphasize-lines: 5 from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware openapi_middleware = FalconOpenAPIMiddleware.from_spec( spec, response_cls=None, ) app = falcon.App( # ... middleware=[openapi_middleware], ) After that you will have access to validation result object with all validated request data from Falcon view through request context. .. code-block:: python class ThingsResource: def on_get(self, req, resp): # get parameters object with path, query, cookies and headers parameters validated_params = req.context.openapi.parameters # or specific location parameters validated_path_params = req.context.openapi.parameters.path # get body validated_body = req.context.openapi.body # get security data validated_security = req.context.openapi.security Low level --------- You can use ``FalconOpenAPIRequest`` as a Falcon request factory: .. code-block:: python from openapi_core.contrib.falcon import FalconOpenAPIRequest openapi_request = FalconOpenAPIRequest(falcon_request) result = openapi.unmarshal_request(openapi_request) You can use ``FalconOpenAPIResponse`` as a Falcon response factory: .. code-block:: python from openapi_core.contrib.falcon import FalconOpenAPIResponse openapi_response = FalconOpenAPIResponse(falcon_response) result = openapi.unmarshal_response(openapi_request, openapi_response) python-openapi-openapi-core-a5e0959/docs/integrations/fastapi.rst000066400000000000000000000040001463224076500252300ustar00rootroot00000000000000FastAPI ======= This section describes integration with `FastAPI `__ ASGI framework. .. note:: FastAPI also provides OpenAPI support. The main difference is that, unlike FastAPI's code-first approach, OpenAPI-core allows you to laverage your existing specification that alligns with API-First approach. You can read more about API-first vs. code-first in the `Guide to API-first `__. Middleware ---------- FastAPI can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. Add ``FastAPIOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. .. code-block:: python :emphasize-lines: 2,5 from fastapi import FastAPI from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware app = FastAPI() app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi) After that all your requests and responses will be validated. Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. .. code-block:: python async def homepage(request): # get parameters object with path, query, cookies and headers parameters unmarshalled_params = request.scope["openapi"].parameters # or specific location parameters unmarshalled_path_params = request.scope["openapi"].parameters.path # get body unmarshalled_body = request.scope["openapi"].body # get security data unmarshalled_security = request.scope["openapi"].security Response validation ^^^^^^^^^^^^^^^^^^^ You can skip response validation process: by setting ``response_cls`` to ``None`` .. code-block:: python :emphasize-lines: 5 app = FastAPI() app.add_middleware( FastAPIOpenAPIMiddleware, openapi=openapi, response_cls=None, ) Low level --------- For low level integration see `Starlette `_ integration. python-openapi-openapi-core-a5e0959/docs/integrations/flask.rst000066400000000000000000000057641463224076500247230ustar00rootroot00000000000000Flask ====== This section describes integration with `Flask `__ web framework. View decorator -------------- Flask can be integrated by `view decorator `__ to apply OpenAPI validation to your application's specific views. Use ``FlaskOpenAPIViewDecorator`` with OpenAPI object to create the decorator. .. code-block:: python :emphasize-lines: 1,3,6 from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator openapi_validated = FlaskOpenAPIViewDecorator(openapi) @app.route('/home') @openapi_validated def home(): return "Welcome home" You can skip response validation process: by setting ``response_cls`` to ``None`` .. code-block:: python :emphasize-lines: 5 from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator openapi_validated = FlaskOpenAPIViewDecorator( openapi, response_cls=None, ) If you want to decorate class based view you can use the decorators attribute: .. code-block:: python :emphasize-lines: 2 class MyView(View): decorators = [openapi_validated] def dispatch_request(self): return "Welcome home" app.add_url_rule('/home', view_func=MyView.as_view('home')) View ---- As an alternative to the decorator-based integration, a Flask method based views can be integrated by inheritance from ``FlaskOpenAPIView`` class. .. code-block:: python :emphasize-lines: 1,3,8 from openapi_core.contrib.flask.views import FlaskOpenAPIView class MyView(FlaskOpenAPIView): def get(self): return "Welcome home" app.add_url_rule( '/home', view_func=MyView.as_view('home', spec), ) Additional customization parameters can be passed to the view. .. code-block:: python :emphasize-lines: 10 from openapi_core.contrib.flask.views import FlaskOpenAPIView class MyView(FlaskOpenAPIView): def get(self): return "Welcome home" app.add_url_rule( '/home', view_func=MyView.as_view( 'home', spec, extra_format_validators=extra_format_validators, ), ) Request parameters ------------------ In Flask, all unmarshalled request data are provided as Flask request object's ``openapi.parameters`` attribute .. code-block:: python :emphasize-lines: 6,7 from flask.globals import request @app.route('/browse//') @openapi def browse(id): browse_id = request.openapi.parameters.path['id'] page = request.openapi.parameters.query.get('page', 1) return f"Browse {browse_id}, page {page}" Low level --------- You can use ``FlaskOpenAPIRequest`` as a Flask request factory: .. code-block:: python from openapi_core.contrib.flask import FlaskOpenAPIRequest openapi_request = FlaskOpenAPIRequest(flask_request) result = openapi.unmarshal_request(openapi_request) For response factory see `Werkzeug `_ integration. python-openapi-openapi-core-a5e0959/docs/integrations/index.rst000066400000000000000000000005731463224076500247230ustar00rootroot00000000000000Integrations ============ Openapi-core integrates with your popular libraries and frameworks. Each integration offers different levels of integration that help validate and unmarshal your request and response data. .. toctree:: :maxdepth: 1 aiohttp bottle django falcon fastapi flask pyramid requests starlette tornado werkzeug python-openapi-openapi-core-a5e0959/docs/integrations/pyramid.rst000066400000000000000000000001411463224076500252500ustar00rootroot00000000000000Pyramid ======= See `pyramid_openapi3 `_ project. python-openapi-openapi-core-a5e0959/docs/integrations/requests.rst000066400000000000000000000027741463224076500254740ustar00rootroot00000000000000Requests ======== This section describes integration with `Requests `__ library. Low level --------- The integration defines classes useful for low level integration. Request ^^^^^^^ Use ``RequestsOpenAPIRequest`` to create OpenAPI request from Requests request: .. code-block:: python from openapi_core.contrib.requests import RequestsOpenAPIRequest request = Request('POST', url, data=data, headers=headers) openapi_request = RequestsOpenAPIRequest(request) openapi.validate_request(openapi_request) Webhook request ^^^^^^^^^^^^^^^ Use ``RequestsOpenAPIWebhookRequest`` to create OpenAPI webhook request from Requests request: .. code-block:: python from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest request = Request('POST', url, data=data, headers=headers) openapi_webhook_request = RequestsOpenAPIWebhookRequest(request, "my_webhook") openapi.validate_request(openapi_webhook_request) Response ^^^^^^^^ Use ``RequestsOpenAPIResponse`` to create OpenAPI response from Requests response: .. code-block:: python from openapi_core.contrib.requests import RequestsOpenAPIResponse session = Session() request = Request('POST', url, data=data, headers=headers) prepped = session.prepare_request(req) response = session,send(prepped) openapi_request = RequestsOpenAPIRequest(request) openapi_response = RequestsOpenAPIResponse(response) openapi.validate_response(openapi_request, openapi_response) python-openapi-openapi-core-a5e0959/docs/integrations/starlette.rst000066400000000000000000000053411463224076500256210ustar00rootroot00000000000000Starlette ========= This section describes integration with `Starlette `__ ASGI framework. Middleware ---------- Starlette can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. Add ``StarletteOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. .. code-block:: python :emphasize-lines: 1,6 from openapi_core.contrib.starlette.middlewares import StarletteOpenAPIMiddleware from starlette.applications import Starlette from starlette.middleware import Middleware middleware = [ Middleware(StarletteOpenAPIMiddleware, openapi=openapi), ] app = Starlette( # ... middleware=middleware, ) After that all your requests and responses will be validated. Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. .. code-block:: python async def homepage(request): # get parameters object with path, query, cookies and headers parameters unmarshalled_params = request.scope["openapi"].parameters # or specific location parameters unmarshalled_path_params = request.scope["openapi"].parameters.path # get body unmarshalled_body = request.scope["openapi"].body # get security data unmarshalled_security = request.scope["openapi"].security Response validation ^^^^^^^^^^^^^^^^^^^ You can skip response validation process: by setting ``response_cls`` to ``None`` .. code-block:: python :emphasize-lines: 2 middleware = [ Middleware(StarletteOpenAPIMiddleware, openapi=openapi, response_cls=None), ] app = Starlette( # ... middleware=middleware, ) Low level --------- The integration defines classes useful for low level integration. Request ^^^^^^^ Use ``StarletteOpenAPIRequest`` to create OpenAPI request from Starlette request: .. code-block:: python from openapi_core.contrib.starlette import StarletteOpenAPIRequest async def homepage(request): openapi_request = StarletteOpenAPIRequest(request) result = openapi.unmarshal_request(openapi_request) return JSONResponse({'hello': 'world'}) Response ^^^^^^^^ Use ``StarletteOpenAPIResponse`` to create OpenAPI response from Starlette response: .. code-block:: python from openapi_core.contrib.starlette import StarletteOpenAPIResponse async def homepage(request): response = JSONResponse({'hello': 'world'}) openapi_request = StarletteOpenAPIRequest(request) openapi_response = StarletteOpenAPIResponse(response) openapi.validate_response(openapi_request, openapi_response) return response python-openapi-openapi-core-a5e0959/docs/integrations/tornado.rst000066400000000000000000000001371463224076500252560ustar00rootroot00000000000000Tornado ======= See `tornado-openapi3 `_ project. python-openapi-openapi-core-a5e0959/docs/integrations/werkzeug.rst000066400000000000000000000024371463224076500254600ustar00rootroot00000000000000Werkzeug ======== This section describes integration with `Werkzeug `__ a WSGI web application library. Low level --------- The integration defines classes useful for low level integration. Request ^^^^^^^ Use ``WerkzeugOpenAPIRequest`` to create OpenAPI request from Werkzeug request: .. code-block:: python from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest def application(environ, start_response): request = Request(environ) openapi_request = WerkzeugOpenAPIRequest(request) openapi.validate_request(openapi_request) response = Response("Hello world", mimetype='text/plain') return response(environ, start_response) Response ^^^^^^^^ Use ``WerkzeugOpenAPIResponse`` to create OpenAPI response from Werkzeug response: .. code-block:: python from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse def application(environ, start_response): request = Request(environ) response = Response("Hello world", mimetype='text/plain') openapi_request = WerkzeugOpenAPIRequest(request) openapi_response = WerkzeugOpenAPIResponse(response) openapi.validate_response(openapi_request, openapi_response) return response(environ, start_response) python-openapi-openapi-core-a5e0959/docs/make.bat000066400000000000000000000014331463224076500217550ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd python-openapi-openapi-core-a5e0959/docs/requirements.txt000066400000000000000000000000301463224076500236240ustar00rootroot00000000000000sphinx sphinx_rtd_theme python-openapi-openapi-core-a5e0959/docs/security.rst000066400000000000000000000015361463224076500227550ustar00rootroot00000000000000Security ======== Openapi-core provides you easy access to security data for authentication and authorization process. Supported security schemas: * http – for Basic and Bearer HTTP authentications schemes * apiKey – for API keys and cookie authentication Here's an example with scheme ``BasicAuth`` and ``ApiKeyAuth`` security schemes: .. code-block:: yaml security: - BasicAuth: [] - ApiKeyAuth: [] components: securitySchemes: BasicAuth: type: http scheme: basic ApiKeyAuth: type: apiKey in: header name: X-API-Key Security schemes data are accessible from `security` attribute of `RequestUnmarshalResult` object. .. code-block:: python # get basic auth decoded credentials result.security['BasicAuth'] # get api key result.security['ApiKeyAuth'] python-openapi-openapi-core-a5e0959/docs/unmarshalling.rst000066400000000000000000000056761463224076500237630ustar00rootroot00000000000000Unmarshalling ============= Unmarshalling is the process of converting a primitive schema type of value into a higher-level object based on a ``format`` keyword. All request/response data, that can be described by a schema in OpenAPI specification, can be unmarshalled. Unmarshallers firstly validate data against the provided schema (See :doc:`validation`). Openapi-core comes with a set of built-in format unmarshallers: * ``date`` - converts string into a date object, * ``date-time`` - converts string into a datetime object, * ``binary`` - converts string into a byte object, * ``uuid`` - converts string into an UUID object, * ``byte`` - decodes Base64-encoded string. You can also define your own format unmarshallers (See :doc:`customizations/extra_format_unmarshallers`). Request unmarshalling --------------------- Use ``unmarshal_request`` method to validate and unmarshal request data against a given spec. By default, OpenAPI spec version is detected: .. code-block:: python # raises error if request is invalid result = openapi.unmarshal_request(request) Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). .. note:: Webhooks feature is part of OpenAPI v3.1 only Use the same method to validate and unmarshal webhook request data against a given spec. .. code-block:: python # raises error if request is invalid result = openapi.unmarshal_request(webhook_request) Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). Retrieve validated and unmarshalled request data .. code-block:: python # get parameters path_params = result.parameters.path query_params = result.parameters.query cookies_params = result.parameters.cookies headers_params = result.parameters.headers # get body body = result.body # get security data security = result.security You can also define your own request unmarshaller (See :doc:`customizations/request_unmarshaller_cls`). Response unmarshalling ---------------------- Use ``unmarshal_response`` method to validate and unmarshal response data against a given spec. By default, OpenAPI spec version is detected: .. code-block:: python # raises error if response is invalid result = openapi.unmarshal_response(request, response) Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). .. note:: Webhooks feature is part of OpenAPI v3.1 only Use the same method to validate and unmarshal response data from webhook request against a given spec. .. code-block:: python # raises error if request is invalid result = openapi.unmarshal_response(webhook_request, response) Retrieve validated and unmarshalled response data .. code-block:: python # get headers headers = result.headers # get data data = result.data You can also define your own response unmarshaller (See :doc:`customizations/response_unmarshaller_cls`). python-openapi-openapi-core-a5e0959/docs/validation.rst000066400000000000000000000042401463224076500232330ustar00rootroot00000000000000Validation ========== Validation is a process to validate request/response data under a given schema defined in OpenAPI specification. Additionally, openapi-core uses the ``format`` keyword to check if primitive types conform to defined formats. Such valid formats can be forther unmarshalled (See :doc:`unmarshalling`). Depends on the OpenAPI version, openapi-core comes with a set of built-in format validators such as: ``date``, ``date-time``, ``binary``, ``uuid`` or ``byte``. You can also define your own format validators (See :doc:`customizations/extra_format_validators`). Request validation ------------------ Use ``validate_request`` method to validate request data against a given spec. By default, OpenAPI spec version is detected: .. code-block:: python # raises error if request is invalid openapi.validate_request(request) Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). .. note:: Webhooks feature is part of OpenAPI v3.1 only Use the same method to validate webhook request data against a given spec. .. code-block:: python # raises error if request is invalid openapi.validate_request(webhook_request) Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). You can also define your own request validator (See :doc:`customizations/request_validator_cls`). Response validation ------------------- Use ``validate_response`` function to validate response data against a given spec. By default, OpenAPI spec version is detected: .. code-block:: python from openapi_core import validate_response # raises error if response is invalid openapi.validate_response(request, response) Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). .. note:: Webhooks feature is part of OpenAPI v3.1 only Use the same function to validate response data from webhook request against a given spec. .. code-block:: python # raises error if request is invalid openapi.validate_response(webhook_request, response) You can also define your own response validator (See :doc:`customizations/response_validator_cls`). python-openapi-openapi-core-a5e0959/index.rst000066400000000000000000000007041463224076500212610ustar00rootroot00000000000000.. openapi-core documentation master file, created by sphinx-quickstart on Thu Nov 23 10:05:33 2023. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to openapi-core's documentation! ======================================== .. toctree:: :maxdepth: 2 :caption: Contents: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-openapi-openapi-core-a5e0959/openapi_core/000077500000000000000000000000001463224076500220625ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/__init__.py000066400000000000000000000066451463224076500242060ustar00rootroot00000000000000"""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.19.2" __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-a5e0959/openapi_core/app.py000066400000000000000000000467661463224076500232370ustar00rootroot00000000000000"""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 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 class.""" def __init__( self, spec: SchemaPath, config: Optional[Config] = 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: Schema, config: Optional[Config] = None ) -> "OpenAPI": sp = SchemaPath.from_dict(data) return cls(sp, config=config) @classmethod def from_path( cls, path: Path, config: Optional[Config] = None ) -> "OpenAPI": sp = SchemaPath.from_path(path) return cls(sp, config=config) @classmethod def from_file_path( cls, file_path: str, config: Optional[Config] = None ) -> "OpenAPI": sp = SchemaPath.from_file_path(file_path) return cls(sp, config=config) @classmethod def from_file( cls, fileobj: SupportsRead, config: Optional[Config] = None ) -> "OpenAPI": sp = SchemaPath.from_file(fileobj) 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, 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: AnyRequest) -> None: if isinstance(request, WebhookRequest): self.validate_webhook_request(request) else: self.validate_apicall_request(request) def validate_response( self, request: AnyRequest, response: Response ) -> None: if isinstance(request, WebhookRequest): self.validate_webhook_response(request, response) else: self.validate_apicall_response(request, response) def validate_apicall_request(self, request: Request) -> None: 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: Request, response: Response ) -> 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: WebhookRequest) -> 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: WebhookRequest, response: Response ) -> 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: AnyRequest) -> RequestUnmarshalResult: if isinstance(request, WebhookRequest): return self.unmarshal_webhook_request(request) else: return self.unmarshal_apicall_request(request) def unmarshal_response( self, request: AnyRequest, response: Response ) -> ResponseUnmarshalResult: if isinstance(request, WebhookRequest): return self.unmarshal_webhook_response(request, response) else: return self.unmarshal_apicall_response(request, response) def unmarshal_apicall_request( self, request: Request ) -> RequestUnmarshalResult: if not isinstance(request, Request): raise TypeError("'request' argument is not type of Request") return self.request_unmarshaller.unmarshal(request) def unmarshal_apicall_response( self, request: Request, response: Response ) -> 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: WebhookRequest ) -> RequestUnmarshalResult: if not isinstance(request, WebhookRequest): raise TypeError("'request' argument is not type of WebhookRequest") return self.webhook_request_unmarshaller.unmarshal(request) def unmarshal_webhook_response( self, request: WebhookRequest, response: Response ) -> 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-a5e0959/openapi_core/casting/000077500000000000000000000000001463224076500235125ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/casting/__init__.py000066400000000000000000000000001463224076500256110ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/casting/schemas/000077500000000000000000000000001463224076500251355ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/casting/schemas/__init__.py000066400000000000000000000035271463224076500272550ustar00rootroot00000000000000from 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-a5e0959/openapi_core/casting/schemas/casters.py000066400000000000000000000157441463224076500271660ustar00rootroot00000000000000from typing import Any from typing import Generic from typing import Iterable from typing import List from typing import Mapping from typing import Optional from typing import Type 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 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: return value PrimitiveType = TypeVar("PrimitiveType") class PrimitiveTypeCaster(Generic[PrimitiveType], PrimitiveCaster): primitive_type: Type[PrimitiveType] = NotImplemented def __call__(self, value: Union[str, bytes]) -> Any: self.validate(value) return self.primitive_type(value) # type: ignore [call-arg] def validate(self, value: Any) -> None: # FIXME: don't cast data from media type deserializer # See https://github.com/python-openapi/openapi-core/issues/706 # if not isinstance(value, (str, bytes)): # raise ValueError("should cast only from string or bytes") pass class IntegerCaster(PrimitiveTypeCaster[int]): primitive_type = int class NumberCaster(PrimitiveTypeCaster[float]): primitive_type = float class BooleanCaster(PrimitiveTypeCaster[bool]): primitive_type = bool def __call__(self, value: Union[str, bytes]) -> Any: self.validate(value) return self.primitive_type(forcebool(value)) def validate(self, value: Any) -> None: super().validate(value) # FIXME: don't cast data from media type deserializer # See https://github.com/python-openapi/openapi-core/issues/706 if isinstance(value, bool): return if value.lower() not in ["false", "true"]: raise ValueError("not a boolean format") 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 __call__(self, value: Any) -> List[Any]: # str and bytes are not arrays according to the OpenAPI spec if isinstance(value, (str, bytes)) or not isinstance(value, Iterable): raise CastError(value, self.schema["type"]) try: return list(map(self.items_caster.cast, value)) except (ValueError, TypeError): raise CastError(value, self.schema["type"]) class ObjectCaster(PrimitiveCaster): def __call__(self, value: Any) -> 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: Any, schema_only: bool = False) -> Any: if not isinstance(value, dict): raise CastError(value, self.schema["type"]) 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-a5e0959/openapi_core/casting/schemas/datatypes.py000066400000000000000000000001321463224076500275010ustar00rootroot00000000000000from typing import Any from typing import Callable CasterCallable = Callable[[Any], Any] python-openapi-openapi-core-a5e0959/openapi_core/casting/schemas/exceptions.py000066400000000000000000000005031463224076500276660ustar00rootroot00000000000000from dataclasses import dataclass from typing import Any from openapi_core.exceptions import OpenAPIError @dataclass class CastError(OpenAPIError): """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-a5e0959/openapi_core/casting/schemas/factories.py000066400000000000000000000021351463224076500274670ustar00rootroot00000000000000from 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-a5e0959/openapi_core/configurations.py000066400000000000000000000050631463224076500254720ustar00rootroot00000000000000from 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. Attributes: spec_validator_cls Specifincation validator class. spec_base_uri Specification base uri. request_validator_cls Request validator class. response_validator_cls Response validator class. webhook_request_validator_cls Webhook request validator class. webhook_response_validator_cls Webhook response validator class. request_unmarshaller_cls Request unmarshaller class. response_unmarshaller_cls Response unmarshaller class. webhook_request_unmarshaller_cls Webhook request unmarshaller class. webhook_response_unmarshaller_cls Webhook response unmarshaller class. """ spec_validator_cls: 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-a5e0959/openapi_core/contrib/000077500000000000000000000000001463224076500235225ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/__init__.py000066400000000000000000000000001463224076500256210ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/aiohttp/000077500000000000000000000000001463224076500251725ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/aiohttp/__init__.py000066400000000000000000000003501463224076500273010ustar00rootroot00000000000000from openapi_core.contrib.aiohttp.requests import AIOHTTPOpenAPIWebRequest from openapi_core.contrib.aiohttp.responses import AIOHTTPOpenAPIWebResponse __all__ = [ "AIOHTTPOpenAPIWebRequest", "AIOHTTPOpenAPIWebResponse", ] python-openapi-openapi-core-a5e0959/openapi_core/contrib/aiohttp/requests.py000066400000000000000000000023141463224076500274170ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/aiohttp/responses.py000066400000000000000000000017131463224076500275670ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/django/000077500000000000000000000000001463224076500247645ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/django/__init__.py000066400000000000000000000004001463224076500270670ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/django/handlers.py000066400000000000000000000042011463224076500271330ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/django/integrations.py000066400000000000000000000024071463224076500300470ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/django/middlewares.py000066400000000000000000000035111463224076500276360ustar00rootroot00000000000000"""OpenAPI core contrib django middlewares module""" import warnings from typing import Callable from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http.request import HttpRequest from django.http.response import HttpResponse from openapi_core import OpenAPI from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler from openapi_core.contrib.django.handlers import ( DjangoOpenAPIValidRequestHandler, ) from openapi_core.contrib.django.integrations import DjangoIntegration 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 if not hasattr(settings, "OPENAPI"): if not hasattr(settings, "OPENAPI_SPEC"): raise ImproperlyConfigured( "OPENAPI_SPEC not defined in settings" ) else: warnings.warn( "OPENAPI_SPEC is deprecated. Use OPENAPI instead.", DeprecationWarning, ) openapi = OpenAPI(settings.OPENAPI_SPEC) else: openapi = settings.OPENAPI 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-a5e0959/openapi_core/contrib/django/requests.py000066400000000000000000000055641463224076500272230ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/django/responses.py000066400000000000000000000026001463224076500273550ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/falcon/000077500000000000000000000000001463224076500247645ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/falcon/__init__.py000066400000000000000000000003261463224076500270760ustar00rootroot00000000000000from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse __all__ = [ "FalconOpenAPIRequest", "FalconOpenAPIResponse", ] python-openapi-openapi-core-a5e0959/openapi_core/contrib/falcon/handlers.py000066400000000000000000000046721463224076500271470ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/falcon/integrations.py000066400000000000000000000023071463224076500300460ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/falcon/middlewares.py000066400000000000000000000065071463224076500276460ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/falcon/requests.py000066400000000000000000000056271463224076500272230ustar00rootroot00000000000000"""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.request.content_type ) # multipart form serialization is not supported except NotImplementedError: warnings.warn( f"body serialization for {self.request.content_type} not supported" ) return None else: assert isinstance(body, bytes) return body @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-a5e0959/openapi_core/contrib/falcon/responses.py000066400000000000000000000024201463224076500273550ustar00rootroot00000000000000"""OpenAPI core contrib falcon responses module""" from itertools import tee 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"" resp_iter1, resp_iter2 = tee(self.response.stream) self.response.stream = resp_iter1 content = b"".join(resp_iter2) return content assert isinstance(self.response.text, str) return self.response.text.encode("utf-8") @property def status_code(self) -> int: return int(self.response.status[:3]) @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-a5e0959/openapi_core/contrib/falcon/util.py000066400000000000000000000005401463224076500263120ustar00rootroot00000000000000from typing import Any from typing import Dict from typing import Generator from typing import Tuple def unpack_params( params: Dict[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-a5e0959/openapi_core/contrib/falcon/views.py000066400000000000000000000000001463224076500264610ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/fastapi/000077500000000000000000000000001463224076500251515ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/fastapi/__init__.py000066400000000000000000000005121463224076500272600ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/fastapi/middlewares.py000066400000000000000000000002371463224076500300250ustar00rootroot00000000000000from openapi_core.contrib.starlette.middlewares import ( StarletteOpenAPIMiddleware as FastAPIOpenAPIMiddleware, ) __all__ = ["FastAPIOpenAPIMiddleware"] python-openapi-openapi-core-a5e0959/openapi_core/contrib/fastapi/requests.py000066400000000000000000000003551463224076500274010ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/fastapi/responses.py000066400000000000000000000004731463224076500275500ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/flask/000077500000000000000000000000001463224076500246225ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/flask/__init__.py000066400000000000000000000004751463224076500267410ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/flask/decorators.py000066400000000000000000000056221463224076500273460ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/flask/handlers.py000066400000000000000000000047231463224076500270020ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/flask/integrations.py000066400000000000000000000022571463224076500277100ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/flask/providers.py000066400000000000000000000004231463224076500272100ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/flask/requests.py000066400000000000000000000020301463224076500270420ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/flask/responses.py000066400000000000000000000002211463224076500272100ustar00rootroot00000000000000from openapi_core.contrib.werkzeug.responses import ( WerkzeugOpenAPIResponse as FlaskOpenAPIResponse, ) __all__ = ["FlaskOpenAPIResponse"] python-openapi-openapi-core-a5e0959/openapi_core/contrib/flask/views.py000066400000000000000000000015161463224076500263340ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/requests/000077500000000000000000000000001463224076500253755ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/requests/__init__.py000066400000000000000000000005411463224076500275060ustar00rootroot00000000000000from 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-a5e0959/openapi_core/contrib/requests/protocols.py000066400000000000000000000003111463224076500277660ustar00rootroot00000000000000from typing import Protocol from typing import runtime_checkable from requests.cookies import RequestsCookieJar @runtime_checkable class SupportsCookieJar(Protocol): _cookies: RequestsCookieJar python-openapi-openapi-core-a5e0959/openapi_core/contrib/requests/requests.py000066400000000000000000000062131463224076500276240ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/requests/responses.py000066400000000000000000000014541463224076500277740ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/starlette/000077500000000000000000000000001463224076500255315ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/starlette/__init__.py000066400000000000000000000003501463224076500276400ustar00rootroot00000000000000from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse __all__ = [ "StarletteOpenAPIRequest", "StarletteOpenAPIResponse", ] python-openapi-openapi-core-a5e0959/openapi_core/contrib/starlette/handlers.py000066400000000000000000000043361463224076500277110ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/starlette/integrations.py000066400000000000000000000036511463224076500306160ustar00rootroot00000000000000from aioitertools.itertools import tee as atee from starlette.requests import Request from starlette.responses import Response from starlette.responses import StreamingResponse from openapi_core.contrib.starlette.requests import StarletteOpenAPIRequest from openapi_core.contrib.starlette.responses import StarletteOpenAPIResponse 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 isinstance(response, StreamingResponse): 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-a5e0959/openapi_core/contrib/starlette/middlewares.py000066400000000000000000000025301463224076500304030ustar00rootroot00000000000000"""OpenAPI core contrib starlette middlewares module""" 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 class StarletteOpenAPIMiddleware(StarletteIntegration, BaseHTTPMiddleware): valid_request_handler_cls = StarletteOpenAPIValidRequestHandler errors_handler = StarletteOpenAPIErrorsHandler() def __init__(self, app: ASGIApp, openapi: OpenAPI): super().__init__(openapi) 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-a5e0959/openapi_core/contrib/starlette/requests.py000066400000000000000000000022531463224076500277600ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/starlette/responses.py000066400000000000000000000024061463224076500301260ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/werkzeug/000077500000000000000000000000001463224076500253655ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/contrib/werkzeug/__init__.py000066400000000000000000000003421463224076500274750ustar00rootroot00000000000000from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest from openapi_core.contrib.werkzeug.responses import WerkzeugOpenAPIResponse __all__ = [ "WerkzeugOpenAPIRequest", "WerkzeugOpenAPIResponse", ] python-openapi-openapi-core-a5e0959/openapi_core/contrib/werkzeug/requests.py000066400000000000000000000027731463224076500276230ustar00rootroot00000000000000"""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-a5e0959/openapi_core/contrib/werkzeug/responses.py000066400000000000000000000017201463224076500277600ustar00rootroot00000000000000"""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-a5e0959/openapi_core/datatypes.py000066400000000000000000000024641463224076500244400ustar00rootroot00000000000000"""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 werkzeug.datastructures import Headers from werkzeug.datastructures import ImmutableMultiDict @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: Mapping[str, Any] = 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-a5e0959/openapi_core/deserializing/000077500000000000000000000000001463224076500247135ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/deserializing/__init__.py000066400000000000000000000000001463224076500270120ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/deserializing/exceptions.py000066400000000000000000000001771463224076500274530ustar00rootroot00000000000000from openapi_core.exceptions import OpenAPIError class DeserializeError(OpenAPIError): """Deserialize operation error""" python-openapi-openapi-core-a5e0959/openapi_core/deserializing/media_types/000077500000000000000000000000001463224076500272165ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/deserializing/media_types/__init__.py000066400000000000000000000026511463224076500313330ustar00rootroot00000000000000from 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 from openapi_core.deserializing.styles import style_deserializers_factory __all__ = ["media_type_deserializers_factory"] 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, } ) media_type_deserializers_factory = MediaTypeDeserializersFactory( style_deserializers_factory, media_type_deserializers=media_type_deserializers, ) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/media_types/datatypes.py000066400000000000000000000002671463224076500315730ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict DeserializerCallable = Callable[[bytes], Any] MediaTypeDeserializersDict = Dict[str, DeserializerCallable] python-openapi-openapi-core-a5e0959/openapi_core/deserializing/media_types/deserializers.py000066400000000000000000000151221463224076500324360ustar00rootroot00000000000000from 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 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, 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.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 return self.decode(deserialized) def evolve( self, mimetype: str, schema: Optional[SchemaPath] ) -> "MediaTypeDeserializer": cls = self.__class__ return cls( self.style_deserializers_factory, self.media_types_deserializer, mimetype, schema=schema, ) def decode(self, location: Mapping[str, Any]) -> Mapping[str, Any]: # schema is required for multipart assert self.schema is not None 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"] 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_content_type, prop_schema, ) 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-a5e0959/openapi_core/deserializing/media_types/exceptions.py000066400000000000000000000007021463224076500317500ustar00rootroot00000000000000from 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-a5e0959/openapi_core/deserializing/media_types/factories.py000066400000000000000000000034271463224076500315550ustar00rootroot00000000000000from typing import Mapping from typing import Optional from jsonschema_path import SchemaPath 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.factories import ( StyleDeserializersFactory, ) 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 def create( self, mimetype: str, schema: Optional[SchemaPath] = 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, ) return MediaTypeDeserializer( self.style_deserializers_factory, media_types_deserializer, mimetype, schema=schema, encoding=encoding, **parameters, ) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/media_types/util.py000066400000000000000000000037301463224076500305500ustar00rootroot00000000000000from email.parser import Parser from json import loads from typing import Any from typing import Mapping 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"))) 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( [ ( part.get_param("name", header="content-disposition"), part.get_payload(decode=True), ) for part in parts.get_payload() ] ) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/styles/000077500000000000000000000000001463224076500262365ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/deserializing/styles/__init__.py000066400000000000000000000020531463224076500303470ustar00rootroot00000000000000from 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_factory"] 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, } style_deserializers_factory = StyleDeserializersFactory( style_deserializers=style_deserializers, ) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/styles/datatypes.py000066400000000000000000000003521463224076500306060ustar00rootroot00000000000000from 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-a5e0959/openapi_core/deserializing/styles/deserializers.py000066400000000000000000000021301463224076500314510ustar00rootroot00000000000000import warnings from typing import Any from typing import Mapping from typing import Optional from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.styles.datatypes import DeserializerCallable class StyleDeserializer: def __init__( self, style: str, explode: bool, name: str, schema_type: str, deserializer_callable: Optional[DeserializerCallable] = None, ): self.style = style self.explode = explode self.name = name self.schema_type = schema_type 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: return self.deserializer_callable( self.explode, self.name, self.schema_type, location ) except (ValueError, TypeError, AttributeError): raise DeserializeError(self.style, self.name) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/styles/exceptions.py000066400000000000000000000016131463224076500307720ustar00rootroot00000000000000from 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-a5e0959/openapi_core/deserializing/styles/factories.py000066400000000000000000000015571463224076500305770ustar00rootroot00000000000000from typing import Optional from jsonschema_path import SchemaPath from openapi_core.deserializing.styles.datatypes import StyleDeserializersDict from openapi_core.deserializing.styles.deserializers import StyleDeserializer class StyleDeserializersFactory: def __init__( self, style_deserializers: Optional[StyleDeserializersDict] = None, ): if style_deserializers is None: style_deserializers = {} self.style_deserializers = style_deserializers def create( self, style: str, explode: bool, schema: SchemaPath, name: str, ) -> StyleDeserializer: schema_type = schema.getkey("type", "") deserialize_callable = self.style_deserializers.get(style) return StyleDeserializer( style, explode, name, schema_type, deserialize_callable ) python-openapi-openapi-core-a5e0959/openapi_core/deserializing/styles/util.py000066400000000000000000000133361463224076500275730ustar00rootroot00000000000000import 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-a5e0959/openapi_core/exceptions.py000066400000000000000000000001711463224076500246140ustar00rootroot00000000000000"""OpenAPI core exceptions module""" class OpenAPIError(Exception): pass class SpecError(OpenAPIError): pass python-openapi-openapi-core-a5e0959/openapi_core/extensions/000077500000000000000000000000001463224076500242615ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/extensions/__init__.py000066400000000000000000000000411463224076500263650ustar00rootroot00000000000000"""OpenAPI extensions package""" python-openapi-openapi-core-a5e0959/openapi_core/extensions/models/000077500000000000000000000000001463224076500255445ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/extensions/models/__init__.py000066400000000000000000000000501463224076500276500ustar00rootroot00000000000000"""OpenAPI X-Model extension package""" python-openapi-openapi-core-a5e0959/openapi_core/extensions/models/factories.py000066400000000000000000000021621463224076500300760ustar00rootroot00000000000000"""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-a5e0959/openapi_core/extensions/models/types.py000066400000000000000000000001561463224076500272640ustar00rootroot00000000000000from typing import Any from typing import Tuple from typing import Union Field = Union[str, Tuple[str, Any]] python-openapi-openapi-core-a5e0959/openapi_core/protocols.py000066400000000000000000000060331463224076500244620ustar00rootroot00000000000000"""OpenAPI core protocols module""" from typing import Any from typing import Mapping from typing import Optional from typing import Protocol from typing import runtime_checkable from openapi_core.datatypes import RequestParameters @runtime_checkable class BaseRequest(Protocol): parameters: RequestParameters @property def method(self) -> str: ... @property def body(self) -> Optional[bytes]: ... @property def content_type(self) -> str: ... @runtime_checkable class Request(BaseRequest, Protocol): """Request attributes protocol. Attributes: host_url Url with scheme and host For example: https://localhost:8000 path Request path full_url_pattern The matched url with scheme, host and path pattern. For example: https://localhost:8000/api/v1/pets https://localhost:8000/api/v1/pets/{pet_id} method The request method, as lowercase string. parameters A RequestParameters object. Needs to supports path attribute setter to write resolved path parameters. content_type The content type with parameters (eg, charset, boundary etc.) and always lowercase. body The request body, as bytes (None if not provided). """ @property def host_url(self) -> str: ... @property def path(self) -> str: ... @runtime_checkable class WebhookRequest(BaseRequest, Protocol): """Webhook request attributes protocol. Attributes: name Webhook name method The request method, as lowercase string. parameters A RequestParameters object. Needs to supports path attribute setter to write resolved path parameters. content_type The content type with parameters (eg, charset, boundary etc.) and always lowercase. body The request body, as bytes (None if not provided). """ @property def name(self) -> str: ... @runtime_checkable class SupportsPathPattern(Protocol): """Supports path_pattern attribute 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: ... @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: ... @property def content_type(self) -> str: ... @property def headers(self) -> Mapping[str, Any]: ... @property def data(self) -> Optional[bytes]: ... python-openapi-openapi-core-a5e0959/openapi_core/py.typed000066400000000000000000000000001463224076500235470ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/schema/000077500000000000000000000000001463224076500233225ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/schema/__init__.py000066400000000000000000000000001463224076500254210ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/schema/encodings.py000066400000000000000000000021521463224076500256450ustar00rootroot00000000000000from 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-a5e0959/openapi_core/schema/parameters.py000066400000000000000000000024151463224076500260410ustar00rootroot00000000000000from 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-a5e0959/openapi_core/schema/protocols.py000066400000000000000000000004761463224076500257270ustar00rootroot00000000000000from 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-a5e0959/openapi_core/schema/schemas.py000066400000000000000000000004211463224076500253140ustar00rootroot00000000000000from 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-a5e0959/openapi_core/schema/servers.py000066400000000000000000000012771463224076500253740ustar00rootroot00000000000000from 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-a5e0959/openapi_core/schema/specs.py000066400000000000000000000003421463224076500250100ustar00rootroot00000000000000from 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-a5e0959/openapi_core/security/000077500000000000000000000000001463224076500237315ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/security/__init__.py000066400000000000000000000002441463224076500260420ustar00rootroot00000000000000from openapi_core.security.factories import SecurityProviderFactory __all__ = ["security_provider_factory"] security_provider_factory = SecurityProviderFactory() python-openapi-openapi-core-a5e0959/openapi_core/security/exceptions.py000066400000000000000000000001471463224076500264660ustar00rootroot00000000000000from openapi_core.exceptions import OpenAPIError class SecurityProviderError(OpenAPIError): pass python-openapi-openapi-core-a5e0959/openapi_core/security/factories.py000066400000000000000000000013741463224076500262670ustar00rootroot00000000000000from 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-a5e0959/openapi_core/security/providers.py000066400000000000000000000031341463224076500263210ustar00rootroot00000000000000import 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-a5e0959/openapi_core/shortcuts.py000066400000000000000000000166071463224076500245040ustar00rootroot00000000000000"""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-a5e0959/openapi_core/spec/000077500000000000000000000000001463224076500230145ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/spec/__init__.py000066400000000000000000000000001463224076500251130ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/spec/paths.py000066400000000000000000000005271463224076500245110ustar00rootroot00000000000000import 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-a5e0959/openapi_core/templating/000077500000000000000000000000001463224076500242265ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/__init__.py000066400000000000000000000000001463224076500263250ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/datatypes.py000066400000000000000000000005331463224076500265770ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/media_types/000077500000000000000000000000001463224076500265315ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/media_types/__init__.py000066400000000000000000000000001463224076500306300ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/media_types/datatypes.py000066400000000000000000000001631463224076500311010ustar00rootroot00000000000000from collections import namedtuple MediaType = namedtuple("MediaType", ["mime_type", "parameters", "media_type"]) python-openapi-openapi-core-a5e0959/openapi_core/templating/media_types/exceptions.py000066400000000000000000000007551463224076500312730ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/media_types/finders.py000066400000000000000000000046361463224076500305460ustar00rootroot00000000000000"""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-a5e0959/openapi_core/templating/paths/000077500000000000000000000000001463224076500253455ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/paths/__init__.py000066400000000000000000000003111463224076500274510ustar00rootroot00000000000000from openapi_core.templating.paths.finders import APICallPathFinder from openapi_core.templating.paths.finders import WebhookPathFinder __all__ = [ "APICallPathFinder", "WebhookPathFinder", ] python-openapi-openapi-core-a5e0959/openapi_core/templating/paths/datatypes.py000066400000000000000000000005511463224076500277160ustar00rootroot00000000000000"""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-a5e0959/openapi_core/templating/paths/exceptions.py000066400000000000000000000015111463224076500300760ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/paths/finders.py000066400000000000000000000050651463224076500273570ustar00rootroot00000000000000"""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-a5e0959/openapi_core/templating/paths/iterators.py000066400000000000000000000151631463224076500277410ustar00rootroot00000000000000from 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.util import template_path_len from openapi_core.templating.util import parse from openapi_core.templating.util import search 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: result = search(path_pattern, 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) 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: result = parse(server["url"], 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 = parse(server["url"], server_url_pattern) if result: server_result = TemplateResult( server["url"], result.named ) yield PathOperationServer( path, operation, server, path_result, server_result, ) python-openapi-openapi-core-a5e0959/openapi_core/templating/paths/protocols.py000066400000000000000000000020251463224076500277420ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/paths/types.py000066400000000000000000000002011463224076500270540ustar00rootroot00000000000000from typing import Type from openapi_core.templating.paths.finders import BasePathFinder PathFinderType = Type[BasePathFinder] python-openapi-openapi-core-a5e0959/openapi_core/templating/paths/util.py000066400000000000000000000002311463224076500266700ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/responses/000077500000000000000000000000001463224076500262475ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/responses/__init__.py000066400000000000000000000000001463224076500303460ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/responses/exceptions.py000066400000000000000000000006561463224076500310110ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/responses/finders.py000066400000000000000000000013111463224076500302470ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/security/000077500000000000000000000000001463224076500260755ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/security/__init__.py000066400000000000000000000000001463224076500301740ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/templating/security/exceptions.py000066400000000000000000000006551463224076500306360ustar00rootroot00000000000000from 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-a5e0959/openapi_core/templating/util.py000066400000000000000000000021051463224076500255530ustar00rootroot00000000000000from typing import Any from typing import Optional from parse import Match from parse import Parser class ExtendedParser(Parser): # type: ignore def _handle_field(self, field: str) -> Any: # handle as path parameter field field = field[1:-1] path_parameter_field = "{%s:PathParameter}" % field return super()._handle_field(path_parameter_field) class PathParameter: name = "PathParameter" pattern = r"[^\/]*" def __call__(self, text: str) -> str: return text parse_path_parameter = PathParameter() def search(path_pattern: str, full_url_pattern: str) -> Optional[Match]: extra_types = {parse_path_parameter.name: parse_path_parameter} p = ExtendedParser(path_pattern, extra_types) p._expression = p._expression + "$" return p.search(full_url_pattern) def parse(server_url: str, server_url_pattern: str) -> Match: extra_types = {parse_path_parameter.name: parse_path_parameter} p = ExtendedParser(server_url, extra_types) p._expression = "^" + p._expression return p.parse(server_url_pattern) python-openapi-openapi-core-a5e0959/openapi_core/testing/000077500000000000000000000000001463224076500235375ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/testing/__init__.py000066400000000000000000000003071463224076500256500ustar00rootroot00000000000000"""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-a5e0959/openapi_core/testing/datatypes.py000066400000000000000000000010621463224076500261060ustar00rootroot00000000000000from 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-a5e0959/openapi_core/testing/requests.py000066400000000000000000000024411463224076500257650ustar00rootroot00000000000000"""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-a5e0959/openapi_core/testing/responses.py000066400000000000000000000010201463224076500261230ustar00rootroot00000000000000"""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-a5e0959/openapi_core/types.py000066400000000000000000000002441463224076500236000ustar00rootroot00000000000000from typing import Union from openapi_core.protocols import Request from openapi_core.protocols import WebhookRequest AnyRequest = Union[Request, WebhookRequest] python-openapi-openapi-core-a5e0959/openapi_core/typing.py000066400000000000000000000003051463224076500237440ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/000077500000000000000000000000001463224076500247265ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/__init__.py000066400000000000000000000000001463224076500270250ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/configurations.py000066400000000000000000000013511463224076500303320ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/datatypes.py000066400000000000000000000005161463224076500273000ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/integrations.py000066400000000000000000000043111463224076500300050ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/processors.py000066400000000000000000000047441463224076500275130ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/request/000077500000000000000000000000001463224076500264165ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/request/__init__.py000066400000000000000000000025131463224076500305300ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/request/datatypes.py000066400000000000000000000007311463224076500307670ustar00rootroot00000000000000"""OpenAPI core unmarshalling request datatypes module""" from __future__ import annotations from dataclasses import dataclass from dataclasses import field from openapi_core.datatypes import Parameters from openapi_core.unmarshalling.datatypes import BaseUnmarshalResult @dataclass class RequestUnmarshalResult(BaseUnmarshalResult): body: str | None = None parameters: Parameters = field(default_factory=Parameters) security: dict[str, str] | None = None python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/request/processors.py000066400000000000000000000023451463224076500311760ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/request/protocols.py000066400000000000000000000100651463224076500310160ustar00rootroot00000000000000"""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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/unmarshalling/request/types.py000066400000000000000000000006651463224076500301430ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/request/unmarshallers.py000066400000000000000000000366071463224076500316640ustar00rootroot00000000000000from 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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/unmarshalling/response/000077500000000000000000000000001463224076500265645ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/response/__init__.py000066400000000000000000000025521463224076500307010ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/response/datatypes.py000066400000000000000000000006471463224076500311430ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/response/processors.py000066400000000000000000000026331463224076500313440ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/response/protocols.py000066400000000000000000000075711463224076500311740ustar00rootroot00000000000000"""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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/unmarshalling/response/types.py000066400000000000000000000007001463224076500302770ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/response/unmarshallers.py000066400000000000000000000222011463224076500320130ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/schemas/000077500000000000000000000000001463224076500263515ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/schemas/__init__.py000066400000000000000000000060421463224076500304640ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/schemas/datatypes.py000066400000000000000000000002561463224076500307240ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict FormatUnmarshaller = Callable[[Any], Any] FormatUnmarshallersDict = Dict[str, FormatUnmarshaller] python-openapi-openapi-core-a5e0959/openapi_core/unmarshalling/schemas/exceptions.py000066400000000000000000000007171463224076500311110ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/schemas/factories.py000066400000000000000000000054051463224076500307060ustar00rootroot00000000000000import 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-a5e0959/openapi_core/unmarshalling/schemas/unmarshallers.py000066400000000000000000000240451463224076500316100ustar00rootroot00000000000000import 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) 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) 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-a5e0959/openapi_core/unmarshalling/schemas/util.py000066400000000000000000000012111463224076500276730ustar00rootroot00000000000000"""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-a5e0959/openapi_core/unmarshalling/typing.py000066400000000000000000000007161463224076500266160ustar00rootroot00000000000000from 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-a5e0959/openapi_core/unmarshalling/unmarshallers.py000066400000000000000000000116131463224076500301620ustar00rootroot00000000000000from 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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/util.py000066400000000000000000000011271463224076500234120ustar00rootroot00000000000000"""OpenAPI core util module""" from itertools import chain from typing import Any from typing import Iterable def forcebool(val: Any) -> bool: if isinstance(val, str): val = val.lower() if val in ("y", "yes", "t", "true", "on", "1"): return True elif val in ("n", "no", "f", "false", "off", "0"): 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-a5e0959/openapi_core/validation/000077500000000000000000000000001463224076500242145ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/validation/__init__.py000066400000000000000000000000451463224076500263240ustar00rootroot00000000000000"""OpenAPI core validation module""" python-openapi-openapi-core-a5e0959/openapi_core/validation/configurations.py000066400000000000000000000047221463224076500276250ustar00rootroot00000000000000from dataclasses import dataclass from typing import Optional from openapi_core.casting.schemas.factories import SchemaCastersFactory from openapi_core.deserializing.media_types import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = ( style_deserializers_factory ) media_type_deserializers_factory: MediaTypeDeserializersFactory = ( media_type_deserializers_factory ) 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-a5e0959/openapi_core/validation/decorators.py000066400000000000000000000033301463224076500267320ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/exceptions.py000066400000000000000000000004231463224076500267460ustar00rootroot00000000000000"""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-a5e0959/openapi_core/validation/integrations.py000066400000000000000000000022371463224076500273000ustar00rootroot00000000000000"""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-a5e0959/openapi_core/validation/processors.py000066400000000000000000000010311463224076500267630ustar00rootroot00000000000000"""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-a5e0959/openapi_core/validation/request/000077500000000000000000000000001463224076500257045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/validation/request/__init__.py000066400000000000000000000045231463224076500300210ustar00rootroot00000000000000"""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-a5e0959/openapi_core/validation/request/datatypes.py000066400000000000000000000002661463224076500302600ustar00rootroot00000000000000from openapi_core.datatypes import Parameters from openapi_core.datatypes import RequestParameters # Backward compatibility __all__ = [ "Parameters", "RequestParameters", ] python-openapi-openapi-core-a5e0959/openapi_core/validation/request/exceptions.py000066400000000000000000000041751463224076500304460ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/request/protocols.py000066400000000000000000000067471463224076500303200ustar00rootroot00000000000000"""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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/validation/request/types.py000066400000000000000000000006131463224076500274220ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/request/validators.py000066400000000000000000000425011463224076500304300ustar00rootroot00000000000000"""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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/validation/response/000077500000000000000000000000001463224076500260525ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/validation/response/__init__.py000066400000000000000000000037631463224076500301740ustar00rootroot00000000000000"""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-a5e0959/openapi_core/validation/response/exceptions.py000066400000000000000000000023641463224076500306120ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/response/protocols.py000066400000000000000000000065261463224076500304610ustar00rootroot00000000000000"""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 import ( media_type_deserializers_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.factories import ( StyleDeserializersFactory, ) 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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-a5e0959/openapi_core/validation/response/types.py000066400000000000000000000006261463224076500275740ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/response/validators.py000066400000000000000000000307421463224076500306020ustar00rootroot00000000000000"""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 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: Mapping[str, Any], 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: Mapping[str, Any], 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: Mapping[str, Any], 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-a5e0959/openapi_core/validation/schemas/000077500000000000000000000000001463224076500256375ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/openapi_core/validation/schemas/__init__.py000066400000000000000000000015221463224076500277500ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/schemas/datatypes.py000066400000000000000000000002471463224076500302120ustar00rootroot00000000000000from typing import Any from typing import Callable from typing import Dict FormatValidator = Callable[[Any], bool] FormatValidatorsDict = Dict[str, FormatValidator] python-openapi-openapi-core-a5e0959/openapi_core/validation/schemas/exceptions.py000066400000000000000000000011431463224076500303710ustar00rootroot00000000000000from 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-a5e0959/openapi_core/validation/schemas/factories.py000066400000000000000000000045541463224076500302000ustar00rootroot00000000000000from 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 ) resolver = schema.accessor.resolver # type: ignore with schema.open() as schema_dict: jsonschema_validator = self.schema_validator_class( schema_dict, _resolver=resolver, format_checker=format_checker, ) return SchemaValidator(schema, jsonschema_validator) python-openapi-openapi-core-a5e0959/openapi_core/validation/schemas/validators.py000066400000000000000000000120311463224076500303560ustar00rootroot00000000000000import logging from functools import cached_property from functools import partial 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 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.open() as schema_dict: return cls(schema, self.validator.evolve(schema=schema_dict)) 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 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, ) -> Optional[SchemaPath]: 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: validator.validate(value) except ValidateError: continue else: return subschema log.warning("valid oneOf schema not found") return None def iter_any_of_schemas( self, value: Any, ) -> Iterator[SchemaPath]: if "anyOf" not in self.schema: return any_of_schemas = self.schema / "anyOf" for subschema in any_of_schemas: validator = self.evolve(subschema) try: validator.validate(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-a5e0959/openapi_core/validation/validators.py000066400000000000000000000256251463224076500267500ustar00rootroot00000000000000"""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_factory, ) from openapi_core.deserializing.media_types.datatypes import ( MediaTypeDeserializersDict, ) from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.deserializing.styles.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: StyleDeserializersFactory = style_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, 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 self.media_type_deserializers_factory = ( media_type_deserializers_factory ) 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: # type: ignore[comparison-overlap] 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") deserializer = self.media_type_deserializers_factory.create( mimetype, schema=schema, 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 _cast(self, schema: SchemaPath, value: Any) -> Any: caster = self.schema_casters_factory.create(schema) return caster.cast(value) def _validate_schema(self, schema: SchemaPath, value: Any) -> None: validator = self.schema_validators_factory.create( 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) casted = self._cast(schema, deserialised) return casted, 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" # cast for urlencoded content # FIXME: don't cast data from media type deserializer # See https://github.com/python-openapi/openapi-core/issues/706 casted = self._cast(schema, deserialised) return casted, schema def _get_content_and_schema( self, raw: bytes, content: SchemaPath, mimetype: Optional[str] = None ) -> Tuple[Any, Optional[SchemaPath]]: casted, schema = self._get_content_schema_value_and_schema( raw, content, mimetype ) if schema is None: return casted, None self._validate_schema(schema, casted) return casted, 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-a5e0959/poetry.lock000066400000000000000000006126271463224076500216310ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aioitertools" version = "0.11.0" description = "itertools and builtins for AsyncIO and mixed iterables" optional = true python-versions = ">=3.6" files = [ {file = "aioitertools-0.11.0-py3-none-any.whl", hash = "sha256:04b95e3dab25b449def24d7df809411c10e62aab0cbe31a50ca4e68748c43394"}, {file = "aioitertools-0.11.0.tar.gz", hash = "sha256:42c68b8dd3a69c2bf7f2233bf7df4bb58b557bca5252ac02ed5187bbc67d6831"}, ] [package.dependencies] typing_extensions = {version = ">=4.0", markers = "python_version < \"3.10\""} [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.7" 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)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = "*" files = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] [[package]] name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" 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" 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" 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", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "babel" version = "2.13.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} setuptools = {version = "*", markers = "python_version >= \"3.12\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = false python-versions = ">=3.6" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] [package.extras] tzdata = ["tzdata"] [[package]] name = "black" version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, ] [[package]] name = "bump2version" version = "1.0.1" description = "Version-bump your software with a single command!" optional = false python-versions = ">=3.5" files = [ {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, ] [[package]] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" 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" 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 = "click" version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" 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" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "deptry" version = "0.16.1" description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." optional = false python-versions = ">=3.8" files = [ {file = "deptry-0.16.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:29ed8ae61b8f5664dd484717c79eef7ec66d965940efd828fca0d3c09220a1db"}, {file = "deptry-0.16.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:738a772b538f51e9a7bb8d5cb9a61cfea8794a79371d171919b01cff0dc895bf"}, {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56b78f7c860def8000e93f88345a24809f1b91e2f7836ac9a08285cb405e2762"}, {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e86a04ea87ddece0f68ba204feb950f588205808c8320e6628300f03ff66dc"}, {file = "deptry-0.16.1-cp38-abi3-win_amd64.whl", hash = "sha256:01b5098739a56c93f3e1e40efec5f20452f22a9a8436a59809d46201fcb94bcf"}, {file = "deptry-0.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e29dc4c1bbb933c9482e8cef85fafe2be7f46aeb90a8a07ba5f2b22af60876f"}, {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8dfab68c247566c87a40f55f405be8549ffe4cea0b9b5384b7ae73a6f1d5cd1"}, {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1228493926b6e59cd2df7cb6016e10c255553cc31db24edcf7fc8d5474b81be6"}, {file = "deptry-0.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99c3ac60b78ad1b8fb9844c25393e7ebc969cc950601ce3c050f56d196da5a79"}, {file = "deptry-0.16.1.tar.gz", hash = "sha256:39fb62da4a8f4d17ed282310f7bcaadec55a95a8c471b01e0fcdf5351a7ac323"}, ] [package.dependencies] click = ">=8.0.0,<9" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "distlib" version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" 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.13" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, ] [package.dependencies] asgiref = ">=3.6.0,<4" "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} 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.15.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.6" files = [ {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, ] [package.dependencies] "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} django = ">=3.0" [[package]] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" 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 = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.7" files = [ {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, ] [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "falcon" version = "3.1.3" description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." optional = false python-versions = ">=3.5" files = [ {file = "falcon-3.1.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:094d295a767e2aa84f07bec6b23e9ebe2e43cde81d9d583bef037168bd775ad6"}, {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b203408040e87e8323e1c1921b106353fa5fe5dc05c9b3f4881acb3af03f556"}, {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d56d9a9886387585ce4547354c9929bf5743394df04a17df6ed51ad6bb58a4cc"}, {file = "falcon-3.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c335f1118a6e42f08cf30d56914a0bc0d470aa6db7619fdc4c546b184f38248"}, {file = "falcon-3.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:cb6b6a79d096b3a1f2f37f66f46a2cf18deb575db6dee9935057e6036d98d01f"}, {file = "falcon-3.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:508fdf30617cf1fa5c9d3058c14124dc8e5f7e316e26dca22d974f916493fd0e"}, {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3c6cbcba90e272f60581fb3c4561cdcd0ac6d19672f5a11a04309b1d23fa66"}, {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7471aab646875d4478377065246a4115aaf3c0801a6eb4b6871f9836c8ef60b1"}, {file = "falcon-3.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bbbfa1ecb1d50bed9f8ae940b0f1049d958e945f1a08891769d40cfabe6fb2"}, {file = "falcon-3.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:24aa51ba4145f05649976c33664971ef36f92846208bd9d4d4158ceb51bc753f"}, {file = "falcon-3.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7a1ee54bf19d9c7f998edd8ac21ab8ead1e2f73c24822237eb5485890979a25d"}, {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db78171113a3920f0f33d8dd26364527a362db2d1c3376a95778653ff87dea24"}, {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:656e738e0e220f4503e4f07747b564f4459da159a1f32ec6d2478efb651278dd"}, {file = "falcon-3.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e19a0a3827821bcf754a9b24217e3b8b4750f7eb437c4a8c461135a86ca9b1c5"}, {file = "falcon-3.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:d52a05be5c2ef364853cdc6d97056dd880a534016db73b95f5a6ebc652577533"}, {file = "falcon-3.1.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d78a6cfe2d135632673def489a19474e2508d83475c7662c4fa63be0ba82dd81"}, {file = "falcon-3.1.3-cp36-cp36m-win_amd64.whl", hash = "sha256:adc23ced91c4690042a11a0515c5cfe93eeeb7d063940900aee85f8eae7460ec"}, {file = "falcon-3.1.3-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:d6b7131e85dff13abaacb4ff479c456256f0d57b262b1fb1771180f7535cc902"}, {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d51f556ece73766f07ede57f17fa65dbbc2cc5e1c7075fb606f727464ad71e"}, {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b210c05b38a8d655e16aa3ae2befaa70ecfb49bef73c0c1995566b22afcfdd1"}, {file = "falcon-3.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04a92f159d392098a11d14b8ca71d17129d8b1ef37b7a3577f1f8bcb7b3aecba"}, {file = "falcon-3.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9c82cb54bbf67861febe80d394c9b7bfa0d2e16cc998b69bfff4e8b003c721a2"}, {file = "falcon-3.1.3-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:56e8a4728fb0193e2ccd5301d864fd9743a989cc228e709e5c49ff1025cc1a4f"}, {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12432c3f6bce46fe4eec3db6db8d2df1abe43a7531219356f1ba859db207e57b"}, {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1f622d73111912021b8311d1e5d1eabef484217d2d30abe3d237533cb225ce9"}, {file = "falcon-3.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b2ce8a613a29a9eaf8243ca285ebf80464e8a6489dff60425f850fb5548936"}, {file = "falcon-3.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:3cda76fb21568aa058ce454fa6272ca5b2582ebb0efcb7ae0090d3bf6d0db5af"}, {file = "falcon-3.1.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:cbd40435e99255e40ccfa849e4809cd1638fd8eccc08931fc9d355a6840a7332"}, {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6319883789ee3abcbde2dc10fed8016cc3d9a05018ae59944838b892101111a"}, {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:796a57046b0717bff5ac488235c37ea63834a5cfc2c9291c5eeaa43c53e5e24c"}, {file = "falcon-3.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2fe54081f1cedc71462eff8dca074045d14380a4bca163882c6c4353f65af2"}, {file = "falcon-3.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:ad37c46322122f34e228be4fe7ae5fcfedb630eef788a198fbdff5971091d5dc"}, {file = "falcon-3.1.3.tar.gz", hash = "sha256:23335dbccd44f29e85ec55f2f35d5a0bc12bd7a509f641ab81f5c64b65626263"}, ] [[package]] name = "fastapi" version = "0.108.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ {file = "fastapi-0.108.0-py3-none-any.whl", hash = "sha256:8c7bc6d315da963ee4cdb605557827071a9a7f95aeb8fcdd3bde48cdc8764dd7"}, {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.29.0,<0.33.0" typing-extensions = ">=4.8.0" [package.extras] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" version = "3.13.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" 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)"] [[package]] name = "flake8" version = "2.3.0" description = "the modular source code checker: pep8, pyflakes and co" optional = false python-versions = "*" files = [ {file = "flake8-2.3.0-py2.py3-none-any.whl", hash = "sha256:c99cc9716d6655d9c8bcb1e77632b8615bf0abd282d7abd9f5c2148cad7fc669"}, {file = "flake8-2.3.0.tar.gz", hash = "sha256:5ee1a43ccd0716d6061521eec6937c983efa027793013e572712c4da55c7c83e"}, ] [package.dependencies] mccabe = ">=0.2.1" pep8 = ">=1.5.7" pyflakes = ">=0.8.1" [[package]] name = "flask" version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=3.0.0" [package.extras] async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, ] [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "httpcore" version = "1.0.1" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpcore-1.0.1-py3-none-any.whl", hash = "sha256:c5e97ef177dca2023d0b9aad98e49507ef5423e9f1d94ffe2cfe250aa28e63b0"}, {file = "httpcore-1.0.1.tar.gz", hash = "sha256:fce1ddf9b606cfb98132ab58865c3728c52c8e4c3c46e2aabb3674464a186e92"}, ] [package.dependencies] certifi = "*" h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.26.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, ] [package.dependencies] anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] [[package]] name = "identify" version = "2.5.31" description = "File identification library for Python" optional = false python-versions = ">=3.8" 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" files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" 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)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" version = "6.1.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" optional = false python-versions = "*" files = [ {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, ] [package.dependencies] six = "*" [[package]] name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] colors = ["colorama (>=0.4.6)"] [[package]] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.7" files = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] [[package]] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "jsonschema" version = "4.22.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, ] [package.dependencies] attrs = ">=22.2.0" importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} jsonschema-specifications = ">=2023.03.6" pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} referencing = ">=0.28.4" rpds-py = ">=0.7.1" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] [[package]] name = "jsonschema-path" version = "0.3.2" description = "JSONSchema Spec with object-oriented paths" optional = false python-versions = ">=3.8.0,<4.0.0" files = [ {file = "jsonschema_path-0.3.2-py3-none-any.whl", hash = "sha256:271aedfefcd161a0f467bdf23e1d9183691a61eaabf4b761046a914e369336c7"}, {file = "jsonschema_path-0.3.2.tar.gz", hash = "sha256:4d0dababf341e36e9b91a5fb2a3e3fd300b0150e7fe88df4e55cc8253c5a3989"}, ] [package.dependencies] pathable = ">=0.4.1,<0.5.0" PyYAML = ">=5.1" referencing = ">=0.28.0,<0.32.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" 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] importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} 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" 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 = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" 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" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "more-itertools" version = "10.2.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, ] [[package]] name = "multidict" version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] name = "mypy" version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] 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" 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.*" 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.2" description = "OpenAPI schema validation for Python" optional = false python-versions = ">=3.8.0,<4.0.0" files = [ {file = "openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8"}, {file = "openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804"}, ] [package.dependencies] jsonschema = ">=4.19.1,<5.0.0" jsonschema-specifications = ">=2023.5.2,<2024.0.0" rfc3339-validator = "*" [[package]] name = "openapi-spec-validator" version = "0.7.1" description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator" optional = false python-versions = ">=3.8.0,<4.0.0" files = [ {file = "openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959"}, {file = "openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7"}, ] [package.dependencies] importlib-resources = {version = ">=5.8,<7.0", markers = "python_version < \"3.9\""} jsonschema = ">=4.18.0,<5.0.0" jsonschema-path = ">=0.3.1,<0.4.0" lazy-object-proxy = ">=1.7.1,<2.0.0" openapi-schema-validator = ">=0.6.0,<0.7.0" [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "parse" version = "1.20.2" description = "parse() is the opposite of format()" optional = false python-versions = "*" files = [ {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, ] [[package]] name = "pathable" version = "0.4.3" description = "Object-oriented paths" optional = false python-versions = ">=3.7.0,<4.0.0" 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" 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 = "pep8" version = "1.7.1" description = "Python style guide checker" optional = false python-versions = "*" files = [ {file = "pep8-1.7.1-py2.py3-none-any.whl", hash = "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee"}, {file = "pep8-1.7.1.tar.gz", hash = "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"}, ] [[package]] name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." optional = false python-versions = ">=3.6" files = [ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, ] [[package]] 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" 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.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "pydantic" version = "2.4.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, ] [package.dependencies] annotated-types = ">=0.4.0" pydantic-core = "2.10.1" typing-extensions = ">=4.6.1" [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" version = "2.10.1" description = "" optional = false python-versions = ">=3.7" files = [ {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-extra-types" version = "2.1.0" description = "Extra Pydantic types." optional = false python-versions = ">=3.7" files = [ {file = "pydantic_extra_types-2.1.0-py3-none-any.whl", hash = "sha256:1b8aa83a2986b0bc6a7179834fdb423c5e0bcef6b2b4cd9261bf753ad7dcc483"}, {file = "pydantic_extra_types-2.1.0.tar.gz", hash = "sha256:d07b869e733d33712b07d6b8cd7b0223077c23ae5a1e23bd0699a00401259ec7"}, ] [package.dependencies] pydantic = ">=2.0.3" [package.extras] all = ["phonenumbers (>=8,<9)", "pycountry (>=22,<23)"] [[package]] name = "pyflakes" version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" 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"] [[package]] name = "pytest" version = "8.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.4,<2.0" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-aiohttp" version = "1.0.5" description = "Pytest plugin for aiohttp support" optional = false python-versions = ">=3.7" files = [ {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, ] [package.dependencies] aiohttp = ">=3.8.1" pytest = ">=6.1.0" pytest-asyncio = ">=0.17.2" [package.extras] testing = ["coverage (==6.2)", "mypy (==0.931)"] [[package]] name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" files = [ {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, ] [package.dependencies] pytest = ">=7.0.0" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cache" version = "1.0" description = "pytest plugin with mechanisms for caching across test runs" optional = false python-versions = "*" files = [ {file = "pytest-cache-1.0.tar.gz", hash = "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9"}, ] [package.dependencies] execnet = ">=1.1.dev1" pytest = ">=2.2" [[package]] name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-flake8" version = "0.1" description = "pytest plugin to check FLAKE8 requirements" optional = false python-versions = "*" files = [ {file = "pytest-flake8-0.1.tar.gz", hash = "sha256:6b30619538937f274a373ace5fe2895def15066f0d3bad5784458ae0bce61a60"}, {file = "pytest_flake8-0.1-py2.py3-none-any.whl", hash = "sha256:d2ecd5343ae56b4ac27ffa09d88111cc97dd7fdbc881231dfcdbc852f9ea5121"}, ] [package.dependencies] flake8 = ">=2.3" pytest = ">=2.4.2" pytest-cache = "*" [[package]] name = "python-multipart" version = "0.0.9" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, ] [package.extras] dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] [[package]] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" 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 = "referencing" version = "0.30.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" 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.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [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 = "responses" version = "0.25.0" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" files = [ {file = "responses-0.25.0-py3-none-any.whl", hash = "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a"}, {file = "responses-0.25.0.tar.gz", hash = "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66"}, ] [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", "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.*" 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.12.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ {file = "rpds_py-0.12.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46"}, {file = "rpds_py-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de"}, {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274"}, {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f"}, {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50"}, {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e"}, {file = "rpds_py-0.12.0-cp310-none-win32.whl", hash = "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29"}, {file = "rpds_py-0.12.0-cp310-none-win_amd64.whl", hash = "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963"}, {file = "rpds_py-0.12.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624"}, {file = "rpds_py-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31"}, {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c"}, {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff"}, {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2"}, {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c"}, {file = "rpds_py-0.12.0-cp311-none-win32.whl", hash = "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10"}, {file = "rpds_py-0.12.0-cp311-none-win_amd64.whl", hash = "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa"}, {file = "rpds_py-0.12.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534"}, {file = "rpds_py-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7"}, {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34"}, {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206"}, {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1"}, {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b"}, {file = "rpds_py-0.12.0-cp312-none-win32.whl", hash = "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978"}, {file = "rpds_py-0.12.0-cp312-none-win_amd64.whl", hash = "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f"}, {file = "rpds_py-0.12.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2"}, {file = "rpds_py-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4"}, {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5"}, {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523"}, {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56"}, {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c"}, {file = "rpds_py-0.12.0-cp38-none-win32.whl", hash = "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595"}, {file = "rpds_py-0.12.0-cp38-none-win_amd64.whl", hash = "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543"}, {file = "rpds_py-0.12.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a"}, {file = "rpds_py-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81"}, {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63"}, {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4"}, {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e"}, {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0"}, {file = "rpds_py-0.12.0-cp39-none-win32.whl", hash = "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31"}, {file = "rpds_py-0.12.0-cp39-none-win_amd64.whl", hash = "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d"}, {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa"}, {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76"}, {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4"}, {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, ] [[package]] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[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.*" 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" 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 = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sphinx" version = "7.1.2" description = "Python documentation generator" optional = false python-versions = ">=3.8" files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.13" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-immaterial" version = "0.11.11" description = "Adaptation of mkdocs-material theme for the Sphinx documentation system" optional = false python-versions = ">=3.8" files = [ {file = "sphinx_immaterial-0.11.11-py3-none-any.whl", hash = "sha256:174c7ce0f39c87413af0fbff17d0821e2f1fbae0d578b7074c9a6e17f1ef295c"}, {file = "sphinx_immaterial-0.11.11.tar.gz", hash = "sha256:f98f3019cbb1d77b259c7ab09dc977ca220dff931dd0f2fabee76e9f9c77b3b4"}, ] [package.dependencies] appdirs = "*" markupsafe = "*" pydantic = ">=2.4" pydantic-extra-types = "*" requests = "*" sphinx = ">=4.5" typing-extensions = "*" [package.extras] clang-format = ["clang-format"] cpp = ["libclang"] json = ["pyyaml"] jsonschema-validation = ["jsonschema"] keys = ["pymdown-extensions"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sqlparse" version = "0.5.0" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" 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.32.0.post1" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ {file = "starlette-0.32.0.post1-py3-none-any.whl", hash = "sha256:cd0cb10ddb49313f609cedfac62c8c12e56c7314b66d89bb077ba228bada1b09"}, {file = "starlette-0.32.0.post1.tar.gz", hash = "sha256:e54e2b7e2fb06dff9eac40133583f10dfa05913f5a85bf26f427c7a40a9a3d02"}, ] [package.dependencies] anyio = ">=3.4.0,<5" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] [[package]] name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" optional = false python-versions = "*" files = [ {file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" 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 = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.24.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "webob" version = "1.8.7" description = "WSGI request and response object" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" files = [ {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"}, {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"}, ] [package.extras] docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "werkzeug" version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] [[package]] name = "yarl" version = "1.9.2" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" [[package]] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] aiohttp = ["aiohttp", "multidict"] django = ["django"] falcon = ["falcon"] fastapi = ["fastapi"] flask = ["flask"] requests = ["requests"] starlette = ["aioitertools", "starlette"] [metadata] lock-version = "2.0" python-versions = "^3.8.0" content-hash = "71044a8cb0eff35c69c9b4e38849687d8b11814d2df86f5b547e786e7defa070" python-openapi-openapi-core-a5e0959/pyproject.toml000066400000000000000000000070041463224076500223340ustar00rootroot00000000000000[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.*", "parse.*", "requests.*", "werkzeug.*", ] ignore_missing_imports = true [[tool.mypy.overrides]] module = "lazy_object_proxy.*" ignore_missing_imports = true [tool.poetry] name = "openapi-core" version = "0.19.2" description = "client-side and server-side support for the OpenAPI Specification v3" authors = ["Artur Maciag "] license = "BSD-3-Clause" readme = "README.rst" 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.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries", "Typing :: Typed", ] include = [ {path = "tests", format = "sdist"}, ] [tool.poetry.dependencies] python = "^3.8.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.38.0", optional = true} isodate = "*" more-itertools = "*" parse = "*" openapi-schema-validator = "^0.6.0" openapi-spec-validator = "^0.7.1" requests = {version = "*", optional = true} werkzeug = "*" jsonschema-path = "^0.3.1" jsonschema = "^4.18.0" multidict = {version = "^6.0.4", optional = true} aioitertools = {version = "^0.11.0", optional = true} fastapi = {version = "^0.108.0", optional = true} [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,<25.0" django = ">=3.0" djangorestframework = "^3.11.2" falcon = ">=3.0" flask = "*" isort = "^5.11.5" pre-commit = "*" pytest = "^8" pytest-flake8 = "*" pytest-cov = "*" python-multipart = "*" responses = "*" starlette = ">=0.26.1,<0.38.0" strict-rfc3339 = "^0.7" webob = "*" mypy = "^1.2" httpx = ">=0.24,<0.27" deptry = ">=0.11,<0.17" aiohttp = "^3.8.4" pytest-aiohttp = "^1.0.4" bump2version = "^1.0.1" pyflakes = "^3.1.0" fastapi = "^0.108.0" [tool.poetry.group.docs.dependencies] sphinx = ">=5.3,<8.0" sphinx-immaterial = "^0.11.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 python-openapi-openapi-core-a5e0959/tests/000077500000000000000000000000001463224076500205615ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/000077500000000000000000000000001463224076500231045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/conftest.py000066400000000000000000000044341463224076500253100ustar00rootroot00000000000000from 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-a5e0959/tests/integration/contrib/000077500000000000000000000000001463224076500245445ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/000077500000000000000000000000001463224076500262145ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/conftest.py000066400000000000000000000067371463224076500304300ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/aiohttp/data/000077500000000000000000000000001463224076500271255ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/000077500000000000000000000000001463224076500276135ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/aiohttp_factory.yaml000066400000000000000000000035351463224076500337040ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/000077500000000000000000000000001463224076500326525ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500346720ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject__main__.py000066400000000000000000000004121463224076500346620ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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.py000066400000000000000000000003701463224076500346000ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/000077500000000000000000000000001463224076500336255ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500356450ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/petsviews.py000066400000000000000000000035621463224076500352630ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/aiohttp/test_aiohttp_project.py000066400000000000000000000036611463224076500330310ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/aiohttp/test_aiohttp_validation.py000066400000000000000000000052241463224076500335120ustar00rootroot00000000000000from __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-a5e0959/tests/integration/contrib/django/000077500000000000000000000000001463224076500260065ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/000077500000000000000000000000001463224076500267175ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/000077500000000000000000000000001463224076500274055ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/000077500000000000000000000000001463224076500322365ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500342560ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoprojectpython-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/auth.py000066400000000000000000000010131463224076500335440ustar00rootroot00000000000000from 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-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/pets/000077500000000000000000000000001463224076500332115ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500352310ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/petsmigrations/000077500000000000000000000000001463224076500353065ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/pets__init__.py000066400000000000000000000000001463224076500374050ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/pets/migrationsviews.py000066400000000000000000000066561463224076500346560ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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.py000066400000000000000000000063551463224076500344020ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/tags/000077500000000000000000000000001463224076500331745ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500352140ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/tagsviews.py000066400000000000000000000004721463224076500346270ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/django/data/v3.0/djangoproject/urls.py000066400000000000000000000027521463224076500336030ustar00rootroot00000000000000"""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.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", ), ] python-openapi-openapi-core-a5e0959/tests/integration/contrib/django/test_django_project.py000066400000000000000000000311141463224076500324070ustar00rootroot00000000000000import 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', '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 python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/000077500000000000000000000000001463224076500260065ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/conftest.py000066400000000000000000000037171463224076500302150ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/falcon/data/000077500000000000000000000000001463224076500267175ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/data/v3.0/000077500000000000000000000000001463224076500274055ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/data/v3.0/falconproject/000077500000000000000000000000001463224076500322365ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500342560ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/data/v3.0/falconproject__main__.py000066400000000000000000000014221463224076500342500ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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.py000066400000000000000000000006651463224076500341730ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/000077500000000000000000000000001463224076500332115ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500352310ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/falcon/data/v3.0/falconproject/petsresources.py000066400000000000000000000067101463224076500355220ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/falcon/test_falcon_project.py000066400000000000000000000311621463224076500324120ustar00rootroot00000000000000from base64 import b64encode from json import dumps import pytest 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', '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 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-a5e0959/tests/integration/contrib/fastapi/000077500000000000000000000000001463224076500261735ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/000077500000000000000000000000001463224076500271045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/v3.0/000077500000000000000000000000001463224076500275725ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/000077500000000000000000000000001463224076500326105ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500346300ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/v3.0/fastapiproject__main__.py000066400000000000000000000004551463224076500346270ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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.py000066400000000000000000000003701463224076500345360ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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/000077500000000000000000000000001463224076500342345ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/v3.0/fastapiproject__init__.py000066400000000000000000000000001463224076500363330ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/fastapi/data/v3.0/fastapiproject/routerspets.py000066400000000000000000000056531463224076500355720ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/fastapi/test_fastapi_project.py000066400000000000000000000256421463224076500327720ustar00rootroot00000000000000import 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', '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-a5e0959/tests/integration/contrib/flask/000077500000000000000000000000001463224076500256445ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/flask/conftest.py000066400000000000000000000013541463224076500300460ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/flask/data/000077500000000000000000000000001463224076500265555ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/flask/data/v3.0/000077500000000000000000000000001463224076500272435ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/flask/data/v3.0/flask_factory.yaml000066400000000000000000000060351463224076500327620ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/contrib/flask/data/v3.0/flaskproject/000077500000000000000000000000001463224076500317325ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500337520ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/flask/data/v3.0/flaskproject__main__.py000066400000000000000000000004271463224076500337500ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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.py000066400000000000000000000003701463224076500336600ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/flask/data/v3.0/flaskproject/pets/000077500000000000000000000000001463224076500327055ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500347250ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/flask/data/v3.0/flaskproject/petsviews.py000066400000000000000000000015161463224076500343400ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/flask/test_flask_decorator.py000066400000000000000000000215401463224076500324210ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/flask/test_flask_project.py000066400000000000000000000035151463224076500321070ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/flask/test_flask_validator.py000066400000000000000000000031121463224076500324170ustar00rootroot00000000000000from 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-a5e0959/tests/integration/contrib/flask/test_flask_views.py000066400000000000000000000150211463224076500315710ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/requests/000077500000000000000000000000001463224076500264175ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/requests/conftest.py000066400000000000000000000004501463224076500306150ustar00rootroot00000000000000import 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-a5e0959/tests/integration/contrib/requests/data/000077500000000000000000000000001463224076500273305ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/requests/data/v3.1/000077500000000000000000000000001463224076500300175ustar00rootroot00000000000000requests_factory.yaml000066400000000000000000000051471463224076500342350ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/requests/test_requests_validation.py000066400000000000000000000161451463224076500341240ustar00rootroot00000000000000from 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-a5e0959/tests/integration/contrib/starlette/000077500000000000000000000000001463224076500265535ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/data/000077500000000000000000000000001463224076500274645ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/data/v3.0/000077500000000000000000000000001463224076500301525ustar00rootroot00000000000000starlette_factory.yaml000066400000000000000000000035061463224076500345210ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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-a5e0959/tests/integration/contrib/starlette/data/v3.0/starletteproject/000077500000000000000000000000001463224076500335505ustar00rootroot00000000000000__init__.py000066400000000000000000000000001463224076500355700ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/data/v3.0/starletteproject__main__.py000066400000000000000000000015711463224076500355670ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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 openapi_core.contrib.starlette.middlewares import ( StarletteOpenAPIMiddleware, ) middleware = [ Middleware( StarletteOpenAPIMiddleware, openapi=openapi, ), ] 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"] ), ] app = Starlette( debug=True, middleware=middleware, routes=routes, ) openapi.py000066400000000000000000000003701463224076500354760ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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/000077500000000000000000000000001463224076500344445ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/data/v3.0/starletteproject__init__.py000066400000000000000000000000001463224076500365430ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/data/v3.0/starletteproject/petsendpoints.py000066400000000000000000000060711463224076500370250ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/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) python-openapi-openapi-core-a5e0959/tests/integration/contrib/starlette/test_starlette_project.py000066400000000000000000000256461463224076500337360ustar00rootroot00000000000000import 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) @pytest.fixture def app(): from starletteproject.__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', '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-a5e0959/tests/integration/contrib/starlette/test_starlette_validation.py000066400000000000000000000076071463224076500344170ustar00rootroot00000000000000from 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-a5e0959/tests/integration/contrib/werkzeug/000077500000000000000000000000001463224076500264075ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/contrib/werkzeug/test_werkzeug_validation.py000066400000000000000000000063351463224076500341040ustar00rootroot00000000000000from 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-a5e0959/tests/integration/data/000077500000000000000000000000001463224076500240155ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/data/v3.0/000077500000000000000000000000001463224076500245035ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/data/v3.0/django_factory.yaml000066400000000000000000000007261463224076500303650ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/empty.yaml000066400000000000000000000000201463224076500265150ustar00rootroot00000000000000openapi: "3.0.0"python-openapi-openapi-core-a5e0959/tests/integration/data/v3.0/links.yaml000066400000000000000000000020751463224076500265130ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/minimal.yaml000066400000000000000000000003001463224076500270060ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/minimal_with_servers.yaml000066400000000000000000000003621463224076500316220ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/path_param.yaml000066400000000000000000000006021463224076500275010ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/petstore.yaml000066400000000000000000000323251463224076500272410ustar00rootroot00000000000000openapi: "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' 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 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" 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 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-a5e0959/tests/integration/data/v3.0/read_only_write_only.yaml000066400000000000000000000015711463224076500316220ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.0/security_override.yaml000066400000000000000000000015141463224076500311360ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/000077500000000000000000000000001463224076500245045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/data/v3.1/empty.yaml000066400000000000000000000000211463224076500265170ustar00rootroot00000000000000openapi: "3.1.0" python-openapi-openapi-core-a5e0959/tests/integration/data/v3.1/links.yaml000066400000000000000000000020741463224076500265130ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/minimal.yaml000066400000000000000000000002771463224076500270240ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/minimal_with_servers.yaml000066400000000000000000000003611463224076500316220ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/path_param.yaml000066400000000000000000000006011463224076500275010ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/security_override.yaml000066400000000000000000000015131463224076500311360ustar00rootroot00000000000000openapi: "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-a5e0959/tests/integration/data/v3.1/webhook-example.yaml000066400000000000000000000016411463224076500304610ustar00rootroot00000000000000openapi: 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-a5e0959/tests/integration/schema/000077500000000000000000000000001463224076500243445ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/schema/test_link_spec.py000066400000000000000000000025561463224076500277340ustar00rootroot00000000000000import 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-a5e0959/tests/integration/schema/test_path_params.py000066400000000000000000000011271463224076500302550ustar00rootroot00000000000000import 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-a5e0959/tests/integration/schema/test_spec.py000066400000000000000000000320011463224076500267030ustar00rootroot00000000000000from 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-a5e0959/tests/integration/test_minimal.py000066400000000000000000000034361463224076500261510ustar00rootroot00000000000000import 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-a5e0959/tests/integration/test_petstore.py000066400000000000000000001711761463224076500263770ustar00rootroot00000000000000import 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 CastError 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 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-a5e0959/tests/integration/unmarshalling/000077500000000000000000000000001463224076500257505ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/unmarshalling/test_read_only_write_only.py000066400000000000000000000060421463224076500336120ustar00rootroot00000000000000import 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-a5e0959/tests/integration/unmarshalling/test_request_unmarshaller.py000066400000000000000000000306721463224076500336360ustar00rootroot00000000000000import 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", "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-a5e0959/tests/integration/unmarshalling/test_response_unmarshaller.py000066400000000000000000000156671463224076500340130ustar00rootroot00000000000000import 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-a5e0959/tests/integration/unmarshalling/test_security_override.py000066400000000000000000000053211463224076500331300ustar00rootroot00000000000000from 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-a5e0959/tests/integration/unmarshalling/test_unmarshallers.py000066400000000000000000001653141463224076500322530ustar00rootroot00000000000000from 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 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 python-openapi-openapi-core-a5e0959/tests/integration/validation/000077500000000000000000000000001463224076500252365ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/integration/validation/test_request_validators.py000066400000000000000000000100221463224076500325620ustar00rootroot00000000000000from 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", "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-a5e0959/tests/integration/validation/test_response_validators.py000066400000000000000000000127611463224076500327440ustar00rootroot00000000000000import 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-a5e0959/tests/unit/000077500000000000000000000000001463224076500215405ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/casting/000077500000000000000000000000001463224076500231705ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/casting/test_schema_casters.py000066400000000000000000000033461463224076500275730ustar00rootroot00000000000000import 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-a5e0959/tests/unit/conftest.py000066400000000000000000000024301463224076500237360ustar00rootroot00000000000000from 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-a5e0959/tests/unit/contrib/000077500000000000000000000000001463224076500232005ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/contrib/aiohttp/000077500000000000000000000000001463224076500246505ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/contrib/aiohttp/test_aiohttp_requests.py000066400000000000000000000003631463224076500316660ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/aiohttp/test_aiohttp_responses.py000066400000000000000000000003671463224076500320400ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/django/000077500000000000000000000000001463224076500244425ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/contrib/django/test_django.py000066400000000000000000000160031463224076500273150ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/flask/000077500000000000000000000000001463224076500243005ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/contrib/flask/conftest.py000066400000000000000000000034321463224076500265010ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/flask/test_flask_requests.py000066400000000000000000000054341463224076500307520ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/flask/test_flask_responses.py000066400000000000000000000011611463224076500311110ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/requests/000077500000000000000000000000001463224076500250535ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/contrib/requests/conftest.py000066400000000000000000000024171463224076500272560ustar00rootroot00000000000000from 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-a5e0959/tests/unit/contrib/requests/test_requests_requests.py000066400000000000000000000115711463224076500322770ustar00rootroot00000000000000import 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-a5e0959/tests/unit/contrib/requests/test_requests_responses.py000066400000000000000000000012571463224076500324450ustar00rootroot00000000000000import 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-a5e0959/tests/unit/deserializing/000077500000000000000000000000001463224076500243715ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/deserializing/test_media_types_deserializers.py000066400000000000000000000317541463224076500332440ustar00rootroot00000000000000from xml.etree.ElementTree import Element import pytest from jsonschema_path import SchemaPath from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.media_types import media_type_deserializers from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, ) from openapi_core.deserializing.styles import style_deserializers_factory class TestMediaTypeDeserializer: @pytest.fixture def deserializer_factory(self): def create_deserializer( mimetype, schema=None, encoding=None, parameters=None, media_type_deserializers=media_type_deserializers, extra_media_type_deserializers=None, ): return MediaTypeDeserializersFactory( style_deserializers_factory, media_type_deserializers, ).create( mimetype, schema=schema, 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_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(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" result = deserializer.deserialize(value) assert result == { "prop": ["a", "b", "c"], } 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 python-openapi-openapi-core-a5e0959/tests/unit/deserializing/test_styles_deserializers.py000066400000000000000000000315301463224076500322540ustar00rootroot00000000000000import pytest from jsonschema_path import SchemaPath from werkzeug.datastructures import ImmutableMultiDict from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.styles import style_deserializers_factory from openapi_core.schema.parameters import get_style_and_explode class TestParameterStyleDeserializer: @pytest.fixture def deserializer_factory(self): 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-a5e0959/tests/unit/extensions/000077500000000000000000000000001463224076500237375ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/extensions/test_factories.py000066400000000000000000000024721463224076500273340ustar00rootroot00000000000000from 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 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-a5e0959/tests/unit/schema/000077500000000000000000000000001463224076500230005ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/schema/test_schema_parameters.py000066400000000000000000000061071463224076500301000ustar00rootroot00000000000000import 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-a5e0959/tests/unit/security/000077500000000000000000000000001463224076500234075ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/security/test_providers.py000066400000000000000000000020111463224076500270270ustar00rootroot00000000000000import 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-a5e0959/tests/unit/templating/000077500000000000000000000000001463224076500237045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/templating/test_media_types_finders.py000066400000000000000000000035531463224076500313400ustar00rootroot00000000000000import 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-a5e0959/tests/unit/templating/test_paths_finders.py000066400000000000000000000434231463224076500301540ustar00rootroot00000000000000import 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-a5e0959/tests/unit/templating/test_responses_finders.py000066400000000000000000000020451463224076500310510ustar00rootroot00000000000000from 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-a5e0959/tests/unit/templating/test_templating_util.py000066400000000000000000000030451463224076500305200ustar00rootroot00000000000000import pytest from openapi_core.templating.util import search class TestSearch: def test_endswith(self): path_pattern = "/{test}/test" full_url_pattern = "/test1/test/test2/test" result = search(path_pattern, full_url_pattern) assert result.named == { "test": "test2", } def test_exact(self): path_pattern = "/{test}/test" full_url_pattern = "/test/test" result = search(path_pattern, 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): full_url_pattern = "/test/test" result = search(path_pattern, full_url_pattern) assert result.named == expected @pytest.mark.xfail( reason=( "Special characters of regex not supported. " "See https://github.com/python-openapi/openapi-core/issues/672" ), strict=True, ) @pytest.mark.parametrize( "path_pattern,expected", [ ("/{test~id}/test", {"test~id": "test"}), ], ) def test_special_chars_valid(self, path_pattern, expected): full_url_pattern = "/test/test" result = search(path_pattern, full_url_pattern) assert result.named == expected python-openapi-openapi-core-a5e0959/tests/unit/test_app.py000066400000000000000000000036631463224076500237410ustar00rootroot00000000000000from 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-a5e0959/tests/unit/test_paths_spec.py000066400000000000000000000003151463224076500253010ustar00rootroot00000000000000import 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-a5e0959/tests/unit/test_shortcuts.py000066400000000000000000001010531463224076500252070ustar00rootroot00000000000000from 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-a5e0959/tests/unit/test_util.py000066400000000000000000000011431463224076500241250ustar00rootroot00000000000000import 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-a5e0959/tests/unit/unmarshalling/000077500000000000000000000000001463224076500244045ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/unmarshalling/test_path_item_params_validator.py000066400000000000000000000133561463224076500334070ustar00rootroot00000000000000from 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-a5e0959/tests/unit/unmarshalling/test_request_unmarshallers.py000066400000000000000000000106701463224076500324510ustar00rootroot00000000000000import 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-a5e0959/tests/unit/unmarshalling/test_schema_unmarshallers.py000066400000000000000000000164771463224076500322340ustar00rootroot00000000000000from 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-a5e0959/tests/unit/validation/000077500000000000000000000000001463224076500236725ustar00rootroot00000000000000python-openapi-openapi-core-a5e0959/tests/unit/validation/test_schema_validators.py000066400000000000000000000144141463224076500307770ustar00rootroot00000000000000import 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