From 6aca2e4d8e7b3d69ca3b204877a7e187625c2386 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Tue, 3 Sep 2024 23:58:54 +0000 Subject: [PATCH] Markdown documentation --- .github/workflows/build-docs.yml | 4 +- .readthedocs.yaml | 6 +- docs/conf.py | 6 + docs/contributing.md | 73 ++ docs/contributing.rst | 76 -- .../extra_format_unmarshallers.md | 26 + .../extra_format_unmarshallers.rst | 27 - .../customizations/extra_format_validators.md | 26 + .../extra_format_validators.rst | 27 - .../extra_media_type_deserializers.md | 23 + .../extra_media_type_deserializers.rst | 25 - docs/customizations/index.md | 3 + docs/customizations/index.rst | 16 - .../request_unmarshaller_cls.md | 20 + .../request_unmarshaller_cls.rst | 22 - docs/customizations/request_validator_cls.md | 20 + docs/customizations/request_validator_cls.rst | 22 - .../response_unmarshaller_cls.md | 18 + .../response_unmarshaller_cls.rst | 20 - docs/customizations/response_validator_cls.md | 20 + .../customizations/response_validator_cls.rst | 22 - docs/customizations/spec_validator_cls.md | 14 + docs/customizations/spec_validator_cls.rst | 16 - docs/extensions.md | 61 ++ docs/extensions.rst | 63 -- docs/index.md | 79 +++ docs/index.rst | 94 --- docs/integrations/aiohttp.md | 37 + docs/integrations/aiohttp.rst | 41 -- docs/integrations/bottle.md | 3 + docs/integrations/bottle.rst | 4 - docs/integrations/django.md | 91 +++ docs/integrations/django.rst | 101 --- docs/integrations/falcon.md | 88 +++ docs/integrations/falcon.rst | 94 --- docs/integrations/fastapi.md | 56 ++ docs/integrations/fastapi.rst | 62 -- docs/integrations/flask.md | 107 +++ docs/integrations/flask.rst | 118 ---- docs/integrations/{index.rst => index.md} | 18 +- docs/integrations/pyramid.md | 3 + docs/integrations/pyramid.rst | 4 - docs/integrations/requests.md | 50 ++ docs/integrations/requests.rst | 55 -- docs/integrations/starlette.md | 89 +++ docs/integrations/starlette.rst | 97 --- docs/integrations/tornado.md | 3 + docs/integrations/tornado.rst | 4 - docs/integrations/werkzeug.md | 38 + docs/integrations/werkzeug.rst | 42 -- docs/security.md | 40 ++ docs/security.rst | 36 - docs/unmarshalling.md | 94 +++ docs/unmarshalling.rst | 91 --- docs/validation.md | 69 ++ docs/validation.rst | 66 -- mkdocs.yml | 84 +++ poetry.lock | 660 ++++++++++++------ pyproject.toml | 5 +- 59 files changed, 1686 insertions(+), 1493 deletions(-) create mode 100644 docs/contributing.md delete mode 100644 docs/contributing.rst create mode 100644 docs/customizations/extra_format_unmarshallers.md delete mode 100644 docs/customizations/extra_format_unmarshallers.rst create mode 100644 docs/customizations/extra_format_validators.md delete mode 100644 docs/customizations/extra_format_validators.rst create mode 100644 docs/customizations/extra_media_type_deserializers.md delete mode 100644 docs/customizations/extra_media_type_deserializers.rst create mode 100644 docs/customizations/index.md delete mode 100644 docs/customizations/index.rst create mode 100644 docs/customizations/request_unmarshaller_cls.md delete mode 100644 docs/customizations/request_unmarshaller_cls.rst create mode 100644 docs/customizations/request_validator_cls.md delete mode 100644 docs/customizations/request_validator_cls.rst create mode 100644 docs/customizations/response_unmarshaller_cls.md delete mode 100644 docs/customizations/response_unmarshaller_cls.rst create mode 100644 docs/customizations/response_validator_cls.md delete mode 100644 docs/customizations/response_validator_cls.rst create mode 100644 docs/customizations/spec_validator_cls.md delete mode 100644 docs/customizations/spec_validator_cls.rst create mode 100644 docs/extensions.md delete mode 100644 docs/extensions.rst create mode 100644 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/integrations/aiohttp.md delete mode 100644 docs/integrations/aiohttp.rst create mode 100644 docs/integrations/bottle.md delete mode 100644 docs/integrations/bottle.rst create mode 100644 docs/integrations/django.md delete mode 100644 docs/integrations/django.rst create mode 100644 docs/integrations/falcon.md delete mode 100644 docs/integrations/falcon.rst create mode 100644 docs/integrations/fastapi.md delete mode 100644 docs/integrations/fastapi.rst create mode 100644 docs/integrations/flask.md delete mode 100644 docs/integrations/flask.rst rename docs/integrations/{index.rst => index.md} (50%) create mode 100644 docs/integrations/pyramid.md delete mode 100644 docs/integrations/pyramid.rst create mode 100644 docs/integrations/requests.md delete mode 100644 docs/integrations/requests.rst create mode 100644 docs/integrations/starlette.md delete mode 100644 docs/integrations/starlette.rst create mode 100644 docs/integrations/tornado.md delete mode 100644 docs/integrations/tornado.rst create mode 100644 docs/integrations/werkzeug.md delete mode 100644 docs/integrations/werkzeug.rst create mode 100644 docs/security.md delete mode 100644 docs/security.rst create mode 100644 docs/unmarshalling.md delete mode 100644 docs/unmarshalling.rst create mode 100644 docs/validation.md delete mode 100644 docs/validation.rst create mode 100644 mkdocs.yml diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 528267a2..652f8694 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -42,11 +42,11 @@ jobs: - name: Build documentation run: | - poetry run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs docs/_build/html -n -W + poetry run python -m mkdocs build --clean --site-dir ./_build/html --config-file mkdocs.yml - uses: actions/upload-artifact@v4 name: Upload docs as artifact with: name: docs-html - path: './docs/_build/html' + path: './_build/html' if-no-files-found: error diff --git a/.readthedocs.yaml b/.readthedocs.yaml index bddf6315..347ba136 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -2,9 +2,9 @@ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py +# Build documentation with Mkdocs +mkdocs: + configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF and ePub formats: all diff --git a/docs/conf.py b/docs/conf.py index cb6623a2..5bc4fbb4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,6 +39,7 @@ "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx_immaterial", + "myst_parser", ] # Add any paths that contain templates here, relative to this directory. @@ -103,3 +104,8 @@ # If False, expand all TOC entries "globaltoc_collapse": False, } + +myst_enable_extensions = [ + "deflist", + "colon_fence", +] diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..1b82787e --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,73 @@ +--- +hide: + - navigation +--- + +# Contributing + +Firstly, thank you all for taking the time to contribute. + +The following section describes how you can contribute to the openapi-core project on GitHub. + +## Reporting bugs + +### Before you report + +- Check whether your issue does not already exist in the [Issue tracker](https://github.com/python-openapi/openapi-core/issues). +- Make sure it is not a support request or question better suited for [Discussion board](https://github.com/python-openapi/openapi-core/discussions). + +### 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](https://python-poetry.org) by following the [official installation instructions](https://python-poetry.org/docs/#installation). Optionally (but recommended), configure Poetry to create a virtual environment in a folder named `.venv` within the root directory of the project: + +```console +poetry config virtualenvs.in-project true +``` + +### Setup + +To create a development environment and install the runtime and development dependencies, run: + +```console +poetry install +``` + +Then enter the virtual environment created by Poetry: + +```console +poetry shell +``` + +### Static checks + +The project uses static checks using fantastic [pre-commit](https://pre-commit.com/). Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit. + +To turn on pre-commit checks for commit operations in git, enter: + +```console +pre-commit install +``` + +To run all checks on your staged files, enter: + +```console +pre-commit run +``` + +To run all checks on all files, enter: + +```console +pre-commit run --all-files +``` + +Pre-commit check results are also attached to your PR through integration with Github Action. diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index 938bd688..00000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,76 +0,0 @@ -Contributing -============ - -Firstly, thank you all for taking the time to contribute. - -The following section describes how you can contribute to the openapi-core project on GitHub. - -Reporting bugs --------------- - -Before you report -^^^^^^^^^^^^^^^^^ - -* Check whether your issue does not already exist in the `Issue tracker `__. -* Make sure it is not a support request or question better suited for `Discussion board `__. - -How to submit a report -^^^^^^^^^^^^^^^^^^^^^^ - -* Include clear title. -* Describe your runtime environment with exact versions you use. -* Describe the exact steps which reproduce the problem, including minimal code snippets. -* Describe the behavior you observed after following the steps, pasting console outputs. -* Describe expected behavior to see and why, including links to documentations. - -Code contribution ------------------ - -Prerequisites -^^^^^^^^^^^^^ - -Install `Poetry `__ by following the `official installation instructions `__. Optionally (but recommended), configure Poetry to create a virtual environment in a folder named ``.venv`` within the root directory of the project: - -.. code-block:: console - - poetry config virtualenvs.in-project true - -Setup -^^^^^ - -To create a development environment and install the runtime and development dependencies, run: - -.. code-block:: console - - poetry install - -Then enter the virtual environment created by Poetry: - -.. code-block:: console - - poetry shell - -Static checks -^^^^^^^^^^^^^ - -The project uses static checks using fantastic `pre-commit `__. Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit. - -To turn on pre-commit checks for commit operations in git, enter: - -.. code-block:: console - - pre-commit install - -To run all checks on your staged files, enter: - -.. code-block:: console - - pre-commit run - -To run all checks on all files, enter: - -.. code-block:: console - - pre-commit run --all-files - -Pre-commit check results are also attached to your PR through integration with Github Action. diff --git a/docs/customizations/extra_format_unmarshallers.md b/docs/customizations/extra_format_unmarshallers.md new file mode 100644 index 00000000..9c548a21 --- /dev/null +++ b/docs/customizations/extra_format_unmarshallers.md @@ -0,0 +1,26 @@ +# Format unmarshallers + +Based on `format` keyword, openapi-core can also unmarshal values to specific formats. + +Openapi-core comes with a set of built-in format unmarshallers, but it's also possible to add custom ones. + +Here's an example with the `usdate` format that converts a value to date object: + +``` python hl_lines="11" + + from datetime import datetime + + def unmarshal_usdate(value): + return datetime.strptime(value, "%m/%d/%y").date + + extra_format_unmarshallers = { + 'usdate': unmarshal_usdate, + } + + config = Config( + extra_format_unmarshallers=extra_format_unmarshallers, + ) + openapi = OpenAPI.from_file_path('openapi.json', config=config) + + result = openapi.unmarshal_response(request, response) +``` diff --git a/docs/customizations/extra_format_unmarshallers.rst b/docs/customizations/extra_format_unmarshallers.rst deleted file mode 100644 index b4d52cca..00000000 --- a/docs/customizations/extra_format_unmarshallers.rst +++ /dev/null @@ -1,27 +0,0 @@ -Format unmarshallers -==================== - -Based on ``format`` keyword, openapi-core can also unmarshal values to specific formats. - -Openapi-core comes with a set of built-in format unmarshallers, but it's also possible to add custom ones. - -Here's an example with the ``usdate`` format that converts a value to date object: - -.. code-block:: python - :emphasize-lines: 11 - - from datetime import datetime - - def unmarshal_usdate(value): - return datetime.strptime(value, "%m/%d/%y").date - - extra_format_unmarshallers = { - 'usdate': unmarshal_usdate, - } - - config = Config( - extra_format_unmarshallers=extra_format_unmarshallers, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - result = openapi.unmarshal_response(request, response) diff --git a/docs/customizations/extra_format_validators.md b/docs/customizations/extra_format_validators.md new file mode 100644 index 00000000..921e0298 --- /dev/null +++ b/docs/customizations/extra_format_validators.md @@ -0,0 +1,26 @@ +# Format validators + +OpenAPI defines a `format` keyword that hints at how a value should be interpreted, e.g. a `string` with the type `date` should conform to the RFC 3339 date format. + +OpenAPI comes with a set of built-in format validators, but it's also possible to add custom ones. + +Here's how you could add support for a `usdate` format that handles dates of the form MM/DD/YYYY: + +``` python hl_lines="11" + + import re + + def validate_usdate(value): + return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) + + extra_format_validators = { + 'usdate': validate_usdate, + } + + config = Config( + extra_format_validators=extra_format_validators, + ) + openapi = OpenAPI.from_file_path('openapi.json', config=config) + + openapi.validate_response(request, response) +``` diff --git a/docs/customizations/extra_format_validators.rst b/docs/customizations/extra_format_validators.rst deleted file mode 100644 index b984f39e..00000000 --- a/docs/customizations/extra_format_validators.rst +++ /dev/null @@ -1,27 +0,0 @@ -Format validators -================= - -OpenAPI defines a ``format`` keyword that hints at how a value should be interpreted, e.g. a ``string`` with the type ``date`` should conform to the RFC 3339 date format. - -OpenAPI comes with a set of built-in format validators, but it's also possible to add custom ones. - -Here's how you could add support for a ``usdate`` format that handles dates of the form MM/DD/YYYY: - -.. code-block:: python - :emphasize-lines: 11 - - import re - - def validate_usdate(value): - return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) - - extra_format_validators = { - 'usdate': validate_usdate, - } - - config = Config( - extra_format_validators=extra_format_validators, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - openapi.validate_response(request, response) diff --git a/docs/customizations/extra_media_type_deserializers.md b/docs/customizations/extra_media_type_deserializers.md new file mode 100644 index 00000000..71e404d2 --- /dev/null +++ b/docs/customizations/extra_media_type_deserializers.md @@ -0,0 +1,23 @@ +# Media type deserializers + +OpenAPI comes with a set of built-in media type deserializers such as: `application/json`, `application/xml`, `application/x-www-form-urlencoded` or `multipart/form-data`. + +You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function: + +``` python hl_lines="11" +def protobuf_deserializer(message): + feature = route_guide_pb2.Feature() + feature.ParseFromString(message) + return feature + +extra_media_type_deserializers = { + 'application/protobuf': protobuf_deserializer, +} + +config = Config( + extra_media_type_deserializers=extra_media_type_deserializers, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) + +result = openapi.unmarshal_response(request, response) +``` diff --git a/docs/customizations/extra_media_type_deserializers.rst b/docs/customizations/extra_media_type_deserializers.rst deleted file mode 100644 index 02940b5f..00000000 --- a/docs/customizations/extra_media_type_deserializers.rst +++ /dev/null @@ -1,25 +0,0 @@ -Media type deserializers -======================== - -OpenAPI comes with a set of built-in media type deserializers such as: ``application/json``, ``application/xml``, ``application/x-www-form-urlencoded`` or ``multipart/form-data``. - -You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function: - -.. code-block:: python - :emphasize-lines: 11 - - def protobuf_deserializer(message): - feature = route_guide_pb2.Feature() - feature.ParseFromString(message) - return feature - - extra_media_type_deserializers = { - 'application/protobuf': protobuf_deserializer, - } - - config = Config( - extra_media_type_deserializers=extra_media_type_deserializers, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - - result = openapi.unmarshal_response(request, response) diff --git a/docs/customizations/index.md b/docs/customizations/index.md new file mode 100644 index 00000000..085c59f0 --- /dev/null +++ b/docs/customizations/index.md @@ -0,0 +1,3 @@ +# Customizations + +OpenAPI accepts `Config` object that allows users to customize the behavior validation and unmarshalling processes. diff --git a/docs/customizations/index.rst b/docs/customizations/index.rst deleted file mode 100644 index b8393abe..00000000 --- a/docs/customizations/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -Customizations -============== - -OpenAPI accepts ``Config`` object that allows users to customize the behavior validation and unmarshalling processes. - -.. toctree:: - :maxdepth: 1 - - spec_validator_cls - request_validator_cls - response_validator_cls - request_unmarshaller_cls - response_unmarshaller_cls - extra_media_type_deserializers - extra_format_validators - extra_format_unmarshallers diff --git a/docs/customizations/request_unmarshaller_cls.md b/docs/customizations/request_unmarshaller_cls.md new file mode 100644 index 00000000..343bf67a --- /dev/null +++ b/docs/customizations/request_unmarshaller_cls.md @@ -0,0 +1,20 @@ +# Request unmarshaller + +By default, request unmarshaller is selected based on detected specification version. + +In order to explicitly validate and unmarshal a: + +- OpenAPI 3.0 spec, import `V30RequestUnmarshaller` +- OpenAPI 3.1 spec, import `V31RequestUnmarshaller` or `V31WebhookRequestUnmarshaller` + +``` python hl_lines="1 4" +from openapi_core import V31RequestUnmarshaller + +config = Config( + request_unmarshaller_cls=V31RequestUnmarshaller, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +result = openapi.unmarshal_request(request) +``` + +You can also explicitly import `V3RequestUnmarshaller` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/request_unmarshaller_cls.rst b/docs/customizations/request_unmarshaller_cls.rst deleted file mode 100644 index e09ab772..00000000 --- a/docs/customizations/request_unmarshaller_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Request unmarshaller -==================== - -By default, request unmarshaller is selected based on detected specification version. - -In order to explicitly validate and unmarshal a: - -* OpenAPI 3.0 spec, import ``V30RequestUnmarshaller`` -* OpenAPI 3.1 spec, import ``V31RequestUnmarshaller`` or ``V31WebhookRequestUnmarshaller`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31RequestUnmarshaller - - config = Config( - request_unmarshaller_cls=V31RequestUnmarshaller, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - result = openapi.unmarshal_request(request) - -You can also explicitly import ``V3RequestUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/request_validator_cls.md b/docs/customizations/request_validator_cls.md new file mode 100644 index 00000000..3730d85e --- /dev/null +++ b/docs/customizations/request_validator_cls.md @@ -0,0 +1,20 @@ +# Request validator + +By default, request validator is selected based on detected specification version. + +In order to explicitly validate a: + +- OpenAPI 3.0 spec, import `V30RequestValidator` +- OpenAPI 3.1 spec, import `V31RequestValidator` or `V31WebhookRequestValidator` + +``` python hl_lines="1 4" +from openapi_core import V31RequestValidator + +config = Config( + request_validator_cls=V31RequestValidator, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +openapi.validate_request(request) +``` + +You can also explicitly import `V3RequestValidator` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/request_validator_cls.rst b/docs/customizations/request_validator_cls.rst deleted file mode 100644 index d6dc48b9..00000000 --- a/docs/customizations/request_validator_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Request validator -================= - -By default, request validator is selected based on detected specification version. - -In order to explicitly validate a: - -* OpenAPI 3.0 spec, import ``V30RequestValidator`` -* OpenAPI 3.1 spec, import ``V31RequestValidator`` or ``V31WebhookRequestValidator`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31RequestValidator - - config = Config( - request_validator_cls=V31RequestValidator, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - openapi.validate_request(request) - -You can also explicitly import ``V3RequestValidator`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_unmarshaller_cls.md b/docs/customizations/response_unmarshaller_cls.md new file mode 100644 index 00000000..aafc5310 --- /dev/null +++ b/docs/customizations/response_unmarshaller_cls.md @@ -0,0 +1,18 @@ +# Response unmarshaller + +In order to explicitly validate and unmarshal a: + +- OpenAPI 3.0 spec, import `V30ResponseUnmarshaller` +- OpenAPI 3.1 spec, import `V31ResponseUnmarshaller` or `V31WebhookResponseUnmarshaller` + +``` python hl_lines="1 4" +from openapi_core import V31ResponseUnmarshaller + +config = Config( + response_unmarshaller_cls=V31ResponseUnmarshaller, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +result = openapi.unmarshal_response(request, response) +``` + +You can also explicitly import `V3ResponseUnmarshaller` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_unmarshaller_cls.rst b/docs/customizations/response_unmarshaller_cls.rst deleted file mode 100644 index 1ccf3997..00000000 --- a/docs/customizations/response_unmarshaller_cls.rst +++ /dev/null @@ -1,20 +0,0 @@ -Response unmarshaller -===================== - -In order to explicitly validate and unmarshal a: - -* OpenAPI 3.0 spec, import ``V30ResponseUnmarshaller`` -* OpenAPI 3.1 spec, import ``V31ResponseUnmarshaller`` or ``V31WebhookResponseUnmarshaller`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31ResponseUnmarshaller - - config = Config( - response_unmarshaller_cls=V31ResponseUnmarshaller, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - result = openapi.unmarshal_response(request, response) - -You can also explicitly import ``V3ResponseUnmarshaller`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_validator_cls.md b/docs/customizations/response_validator_cls.md new file mode 100644 index 00000000..2a0d6f78 --- /dev/null +++ b/docs/customizations/response_validator_cls.md @@ -0,0 +1,20 @@ +# Response validator + +By default, response validator is selected based on detected specification version. + +In order to explicitly validate a: + +- OpenAPI 3.0 spec, import `V30ResponseValidator` +- OpenAPI 3.1 spec, import `V31ResponseValidator` or `V31WebhookResponseValidator` + +``` python hl_lines="1 4" +from openapi_core import V31ResponseValidator + +config = Config( + response_validator_cls=V31ResponseValidator, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +openapi.validate_response(request, response) +``` + +You can also explicitly import `V3ResponseValidator` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/response_validator_cls.rst b/docs/customizations/response_validator_cls.rst deleted file mode 100644 index e9249f48..00000000 --- a/docs/customizations/response_validator_cls.rst +++ /dev/null @@ -1,22 +0,0 @@ -Response validator -================== - -By default, response validator is selected based on detected specification version. - -In order to explicitly validate a: - -* OpenAPI 3.0 spec, import ``V30ResponseValidator`` -* OpenAPI 3.1 spec, import ``V31ResponseValidator`` or ``V31WebhookResponseValidator`` - -.. code-block:: python - :emphasize-lines: 1,4 - - from openapi_core import V31ResponseValidator - - config = Config( - response_validator_cls=V31ResponseValidator, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) - openapi.validate_response(request, response) - -You can also explicitly import ``V3ResponseValidator`` which is a shortcut to the latest OpenAPI v3 version. diff --git a/docs/customizations/spec_validator_cls.md b/docs/customizations/spec_validator_cls.md new file mode 100644 index 00000000..7a9dbada --- /dev/null +++ b/docs/customizations/spec_validator_cls.md @@ -0,0 +1,14 @@ +# Specification validation + +By default, on OpenAPI creation time, the provided specification is also validated. + +If you know you have a valid specification already, disabling the validator can improve the performance. + +``` python hl_lines="1 4 6" +from openapi_core import Config + +config = Config( + spec_validator_cls=None, +) +openapi = OpenAPI.from_file_path('openapi.json', config=config) +``` diff --git a/docs/customizations/spec_validator_cls.rst b/docs/customizations/spec_validator_cls.rst deleted file mode 100644 index 0b912af7..00000000 --- a/docs/customizations/spec_validator_cls.rst +++ /dev/null @@ -1,16 +0,0 @@ -Specification validation -======================== - -By default, on OpenAPI creation time, the provided specification is also validated. - -If you know you have a valid specification already, disabling the validator can improve the performance. - -.. code-block:: python - :emphasize-lines: 1,4,6 - - from openapi_core import Config - - config = Config( - spec_validator_cls=None, - ) - openapi = OpenAPI.from_file_path('openapi.json', config=config) diff --git a/docs/extensions.md b/docs/extensions.md new file mode 100644 index 00000000..049237eb --- /dev/null +++ b/docs/extensions.md @@ -0,0 +1,61 @@ +--- +hide: + - navigation +--- + +# Extensions + +## x-model + +By default, objects are unmarshalled to dictionaries. You can use dynamically created dataclasses by providing `x-model-path` property inside schema definition with name of the model. + +``` yaml hl_lines="5" title="openapi.yaml" + # ... + components: + schemas: + Coordinates: + x-model: Coordinates + type: object + required: + - lat + - lon + properties: + lat: + type: number + lon: + type: number +``` + +As a result of 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](https://github.com/koxudaxi/datamodel-code-generator)) by providing `x-model-path` property inside schema definition with location of your class. + +``` yaml hl_lines="5" title="openapi.yaml" + # ... + components: + schemas: + Coordinates: + x-model-path: foo.bar.Coordinates + type: object + required: + - lat + - lon + properties: + lat: + type: number + lon: + type: number +``` + +``` python title="foo/bar.py" +from dataclasses import dataclass + +@dataclass +class Coordinates: + lat: float + lon: float +``` + +As a result of unmarshalling process, you will get instance of your own dataclasses or model. diff --git a/docs/extensions.rst b/docs/extensions.rst deleted file mode 100644 index b93e95c9..00000000 --- a/docs/extensions.rst +++ /dev/null @@ -1,63 +0,0 @@ -Extensions -========== - -x-model -------- - -By default, objects are unmarshalled to dictionaries. You can use dynamically created dataclasses by providing ``x-model-path`` property inside schema definition with name of the model. - -.. code-block:: yaml - :emphasize-lines: 5 - - # ... - components: - schemas: - Coordinates: - x-model: Coordinates - type: object - required: - - lat - - lon - properties: - lat: - type: number - lon: - type: number - -As a result of unmarshalling process, you will get ``Coordinates`` class instance with ``lat`` and ``lon`` attributes. - - -x-model-path ------------- - -You can use your own dataclasses, pydantic models or models generated by third party generators (i.e. `datamodel-code-generator `__) by providing ``x-model-path`` property inside schema definition with location of your class. - -.. code-block:: yaml - :emphasize-lines: 5 - - ... - components: - schemas: - Coordinates: - x-model-path: foo.bar.Coordinates - type: object - required: - - lat - - lon - properties: - lat: - type: number - lon: - type: number - -.. code-block:: python - - # foo/bar.py - from dataclasses import dataclass - - @dataclass - class Coordinates: - lat: float - lon: float - -As a result of unmarshalling process, you will get instance of your own dataclasses or model. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..3b0e9ac1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,79 @@ +--- +hide: + - navigation +--- + +# openapi-core + +Openapi-core is a Python library that adds client-side and server-side support +for the [OpenAPI v3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) +and [OpenAPI v3.1](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) specification. + +## Key features + +- [Validation](validation.md) and [Unmarshalling](unmarshalling.md) of request and response data (including webhooks) +- [Integrations](integrations/index.md) with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) +- [Customization](customizations/index.md) with **media type deserializers** and **format unmarshallers** +- [Security](security.md) data providers (API keys, Cookie, Basic and Bearer HTTP authentications) + +## Installation + +=== "Pip + PyPI (recommented)" + + ``` console + pip install openapi-core + ``` + +=== "Pip + the source" + + ``` console + pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core + ``` + +## First steps + +Firstly create your OpenAPI object. + +```python +from openapi_core import OpenAPI + +openapi = OpenAPI.from_file_path('openapi.json') +``` + +Now you can use it to validate and unmarshal your requests and/or responses. + +```python +# raises error if request is invalid +result = openapi.unmarshal_request(request) +``` + +Retrieve validated and unmarshalled request data + +```python +# get parameters +path_params = result.parameters.path +query_params = result.parameters.query +cookies_params = result.parameters.cookies +headers_params = result.parameters.headers +# get body +body = result.body +# get security data +security = result.security +``` + +Request object should implement OpenAPI Request protocol. Check [Integrations](integrations/index.md) to find oficially supported implementations. + +For more details read about [Unmarshalling](unmarshalling.md) process. + +If you just want to validate your request/response data without unmarshalling, read about [Validation](validation.md) instead. + +## Related projects + +- [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator) + : Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. +- [openapi-schema-validator](https://github.com/python-openapi/openapi-schema-validator) + : Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. + +## License + +The project is under the terms of BSD 3-Clause License. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 24eacced..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,94 +0,0 @@ -openapi-core -============ - -.. toctree:: - :hidden: - :maxdepth: 3 - - unmarshalling - validation - integrations/index - customizations/index - security - extensions - contributing - -Openapi-core is a Python library that adds client-side and server-side support -for the `OpenAPI v3.0 `__ -and `OpenAPI v3.1 `__ specification. - -Key features ------------- - -* :doc:`validation` and :doc:`unmarshalling ` of request and response data (including webhooks) -* :doc:`Integrations ` with popular libraries (Requests, Werkzeug) and frameworks (Django, Falcon, Flask, Starlette) -* :doc:`Customization ` with **media type deserializers** and **format unmarshallers** -* :doc:`Security ` data providers (API keys, Cookie, Basic and Bearer HTTP authentications) - -Installation ------------- - -.. md-tab-set:: - - .. md-tab-item:: Pip + PyPI (recommented) - - .. code-block:: console - - pip install openapi-core - - .. md-tab-item:: Pip + the source - - .. code-block:: console - - pip install -e git+https://github.com/python-openapi/openapi-core.git#egg=openapi_core - -First steps ------------ - -Firstly create your OpenAPI object. - -.. code-block:: python - - from openapi_core import OpenAPI - - openapi = OpenAPI.from_file_path('openapi.json') - -Now you can use it to validate and unmarshal your requests and/or responses. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(request) - -Retrieve validated and unmarshalled request data - -.. code-block:: python - - # get parameters - path_params = result.parameters.path - query_params = result.parameters.query - cookies_params = result.parameters.cookies - headers_params = result.parameters.headers - # get body - body = result.body - # get security data - security = result.security - -Request object should implement OpenAPI Request protocol. Check :doc:`integrations/index` to find oficially supported implementations. - -For more details read about :doc:`unmarshalling` process. - -If you just want to validate your request/response data without unmarshalling, read about :doc:`validation` instead. - -Related projects ----------------- - -* `openapi-spec-validator `__ - Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger), OpenAPI 3.0 and OpenAPI 3.1 specification. The validator aims to check for full compliance with the Specification. -* `openapi-schema-validator `__ - Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1. - -License -------- - -The project is under the terms of BSD 3-Clause License. diff --git a/docs/integrations/aiohttp.md b/docs/integrations/aiohttp.md new file mode 100644 index 00000000..33452f09 --- /dev/null +++ b/docs/integrations/aiohttp.md @@ -0,0 +1,37 @@ +# aiohttp.web + +This section describes integration with [aiohttp.web](https://docs.aiohttp.org/en/stable/web.html) framework. + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `AIOHTTPOpenAPIWebRequest` to create OpenAPI request from aiohttp.web request: + +``` python +from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest + +async def hello(request): + request_body = await request.text() + openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) + openapi.validate_request(openapi_request) + return web.Response(text="Hello, world") +``` + +### Response + +Use `AIOHTTPOpenAPIWebResponse` to create OpenAPI response from aiohttp.web response: + +``` python +from openapi_core.contrib.starlette import AIOHTTPOpenAPIWebResponse + +async def hello(request): + request_body = await request.text() + response = web.Response(text="Hello, world") + openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) + openapi_response = AIOHTTPOpenAPIWebResponse(response) + result = openapi.unmarshal_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/aiohttp.rst b/docs/integrations/aiohttp.rst deleted file mode 100644 index 97c2cf7b..00000000 --- a/docs/integrations/aiohttp.rst +++ /dev/null @@ -1,41 +0,0 @@ -aiohttp.web -=========== - -This section describes integration with `aiohttp.web `__ framework. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``AIOHTTPOpenAPIWebRequest`` to create OpenAPI request from aiohttp.web request: - -.. code-block:: python - - from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest - - async def hello(request): - request_body = await request.text() - openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) - openapi.validate_request(openapi_request) - return web.Response(text="Hello, world") - -Response -^^^^^^^^ - -Use ``AIOHTTPOpenAPIWebResponse`` to create OpenAPI response from aiohttp.web response: - -.. code-block:: python - - from openapi_core.contrib.starlette import AIOHTTPOpenAPIWebResponse - - async def hello(request): - request_body = await request.text() - response = web.Response(text="Hello, world") - openapi_request = AIOHTTPOpenAPIWebRequest(request, body=request_body) - openapi_response = AIOHTTPOpenAPIWebResponse(response) - result = openapi.unmarshal_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/bottle.md b/docs/integrations/bottle.md new file mode 100644 index 00000000..5d9f01ca --- /dev/null +++ b/docs/integrations/bottle.md @@ -0,0 +1,3 @@ +# Bottle + +See [bottle-openapi-3](https://github.com/cope-systems/bottle-openapi-3) project. diff --git a/docs/integrations/bottle.rst b/docs/integrations/bottle.rst deleted file mode 100644 index 5dd7f737..00000000 --- a/docs/integrations/bottle.rst +++ /dev/null @@ -1,4 +0,0 @@ -Bottle -====== - -See `bottle-openapi-3 `_ project. diff --git a/docs/integrations/django.md b/docs/integrations/django.md new file mode 100644 index 00000000..931b490c --- /dev/null +++ b/docs/integrations/django.md @@ -0,0 +1,91 @@ +# Django + +This section describes integration with [Django](https://www.djangoproject.com) web framework. +The integration supports Django from version 3.0 and above. + +## Middleware + +Django can be integrated by [middleware](https://docs.djangoproject.com/en/5.0/topics/http/middleware/) to apply OpenAPI validation to your entire application. + +Add `DjangoOpenAPIMiddleware` to your `MIDDLEWARE` list and define `OPENAPI`. + +``` python hl_lines="5 8" title="settings.py" +from openapi_core import OpenAPI + +MIDDLEWARE = [ + # ... + 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', +] + +OPENAPI = OpenAPI.from_dict(spec_dict) +``` + +After that all your requests and responses will be validated. + +Also you have access to unmarshal result object with all unmarshalled request data through `openapi` attribute of request object. + +``` python +from django.views import View + +class MyView(View): + def get(self, request): + # get parameters object with path, query, cookies and headers parameters + unmarshalled_params = request.openapi.parameters + # or specific location parameters + unmarshalled_path_params = request.openapi.parameters.path + + # get body + unmarshalled_body = request.openapi.body + + # get security data + unmarshalled_security = request.openapi.security +``` + +### Response validation + +You can skip response validation process: by setting `OPENAPI_RESPONSE_CLS` to `None` + +``` python hl_lines="9" title="settings.py" +from openapi_core import OpenAPI + +MIDDLEWARE = [ + # ... + 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', +] + +OPENAPI = OpenAPI.from_dict(spec_dict) +OPENAPI_RESPONSE_CLS = None +``` + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `DjangoOpenAPIRequest` to create OpenAPI request from Django request: + +``` python +from openapi_core.contrib.django import DjangoOpenAPIRequest + +class MyView(View): + def get(self, request): + openapi_request = DjangoOpenAPIRequest(request) + openapi.validate_request(openapi_request) +``` + +### Response + +Use `DjangoOpenAPIResponse` to create OpenAPI response from Django response: + +``` python +from openapi_core.contrib.django import DjangoOpenAPIResponse + +class MyView(View): + def get(self, request): + response = JsonResponse({'hello': 'world'}) + openapi_request = DjangoOpenAPIRequest(request) + openapi_response = DjangoOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/django.rst b/docs/integrations/django.rst deleted file mode 100644 index 80617bb6..00000000 --- a/docs/integrations/django.rst +++ /dev/null @@ -1,101 +0,0 @@ -Django -====== - -This section describes integration with `Django `__ web framework. -The integration supports Django from version 3.0 and above. - -Middleware ----------- - -Django can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``DjangoOpenAPIMiddleware`` to your ``MIDDLEWARE`` list and define ``OPENAPI``. - -.. code-block:: python - :emphasize-lines: 6,9 - - # settings.py - from openapi_core import OpenAPI - - MIDDLEWARE = [ - # ... - 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', - ] - - OPENAPI = OpenAPI.from_dict(spec_dict) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` attribute of request object. - -.. code-block:: python - - from django.views import View - - class MyView(View): - def get(self, request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.openapi.parameters - # or specific location parameters - unmarshalled_path_params = request.openapi.parameters.path - - # get body - unmarshalled_body = request.openapi.body - - # get security data - unmarshalled_security = request.openapi.security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``OPENAPI_RESPONSE_CLS`` to ``None`` - -.. code-block:: python - :emphasize-lines: 10 - - # settings.py - from openapi_core import OpenAPI - - MIDDLEWARE = [ - # ... - 'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware', - ] - - OPENAPI = OpenAPI.from_dict(spec_dict) - OPENAPI_RESPONSE_CLS = None - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``DjangoOpenAPIRequest`` to create OpenAPI request from Django request: - -.. code-block:: python - - from openapi_core.contrib.django import DjangoOpenAPIRequest - - class MyView(View): - def get(self, request): - openapi_request = DjangoOpenAPIRequest(request) - openapi.validate_request(openapi_request) - -Response -^^^^^^^^ - -Use ``DjangoOpenAPIResponse`` to create OpenAPI response from Django response: - -.. code-block:: python - - from openapi_core.contrib.django import DjangoOpenAPIResponse - - class MyView(View): - def get(self, request): - response = JsonResponse({'hello': 'world'}) - openapi_request = DjangoOpenAPIRequest(request) - openapi_response = DjangoOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/falcon.md b/docs/integrations/falcon.md new file mode 100644 index 00000000..de22f5f3 --- /dev/null +++ b/docs/integrations/falcon.md @@ -0,0 +1,88 @@ +# Falcon + +This section describes integration with [Falcon](https://falconframework.org) web framework. +The integration supports Falcon from version 3.0 and above. + +## Middleware + +The Falcon API can be integrated by `FalconOpenAPIMiddleware` middleware. + +``` python hl_lines="1 3 7" +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + +openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +Additional customization parameters can be passed to the middleware. + +``` python hl_lines="5" +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + +openapi_middleware = FalconOpenAPIMiddleware.from_spec( + spec, + extra_format_validators=extra_format_validators, +) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +You can skip response validation process: by setting `response_cls` to `None` + +``` python hl_lines="5" +from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + +openapi_middleware = FalconOpenAPIMiddleware.from_spec( + spec, + response_cls=None, +) + +app = falcon.App( + # ... + middleware=[openapi_middleware], +) +``` + +After that you will have access to validation result object with all validated request data from Falcon view through request context. + +``` 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: + +``` python +from openapi_core.contrib.falcon import FalconOpenAPIRequest + +openapi_request = FalconOpenAPIRequest(falcon_request) +result = openapi.unmarshal_request(openapi_request) +``` + +You can use `FalconOpenAPIResponse` as a Falcon response factory: + +``` python +from openapi_core.contrib.falcon import FalconOpenAPIResponse + +openapi_response = FalconOpenAPIResponse(falcon_response) +result = openapi.unmarshal_response(openapi_request, openapi_response) +``` diff --git a/docs/integrations/falcon.rst b/docs/integrations/falcon.rst deleted file mode 100644 index 78f95c0e..00000000 --- a/docs/integrations/falcon.rst +++ /dev/null @@ -1,94 +0,0 @@ -Falcon -====== - -This section describes integration with `Falcon `__ web framework. -The integration supports Falcon from version 3.0 and above. - -Middleware ----------- - -The Falcon API can be integrated by ``FalconOpenAPIMiddleware`` middleware. - -.. code-block:: python - :emphasize-lines: 1,3,7 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -Additional customization parameters can be passed to the middleware. - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec( - spec, - extra_format_validators=extra_format_validators, - ) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec( - spec, - response_cls=None, - ) - - app = falcon.App( - # ... - middleware=[openapi_middleware], - ) - -After that you will have access to validation result object with all validated request data from Falcon view through request context. - -.. code-block:: python - - class ThingsResource: - def on_get(self, req, resp): - # get parameters object with path, query, cookies and headers parameters - validated_params = req.context.openapi.parameters - # or specific location parameters - validated_path_params = req.context.openapi.parameters.path - - # get body - validated_body = req.context.openapi.body - - # get security data - validated_security = req.context.openapi.security - -Low level ---------- - -You can use ``FalconOpenAPIRequest`` as a Falcon request factory: - -.. code-block:: python - - from openapi_core.contrib.falcon import FalconOpenAPIRequest - - openapi_request = FalconOpenAPIRequest(falcon_request) - result = openapi.unmarshal_request(openapi_request) - -You can use ``FalconOpenAPIResponse`` as a Falcon response factory: - -.. code-block:: python - - from openapi_core.contrib.falcon import FalconOpenAPIResponse - - openapi_response = FalconOpenAPIResponse(falcon_response) - result = openapi.unmarshal_response(openapi_request, openapi_response) diff --git a/docs/integrations/fastapi.md b/docs/integrations/fastapi.md new file mode 100644 index 00000000..cef85ec9 --- /dev/null +++ b/docs/integrations/fastapi.md @@ -0,0 +1,56 @@ +# FastAPI + +This section describes integration with [FastAPI](https://fastapi.tiangolo.com) ASGI framework. + +!!! note + + FastAPI also provides OpenAPI support. The main difference is that, unlike FastAPI's code-first approach, OpenAPI-core allows you to 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](https://www.postman.com/api-first/). + +## Middleware + +FastAPI can be integrated by [middleware](https://fastapi.tiangolo.com/tutorial/middleware/) to apply OpenAPI validation to your entire application. + +Add `FastAPIOpenAPIMiddleware` with OpenAPI object to your `middleware` list. + +``` python hl_lines="2 5" +from fastapi import FastAPI +from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware + +app = FastAPI() +app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi) +``` + +After that all your requests and responses will be validated. + +Also you have access to unmarshal result object with all unmarshalled request data through `openapi` scope of request object. + +``` python +async def homepage(request): + # get parameters object with path, query, cookies and headers parameters + unmarshalled_params = request.scope["openapi"].parameters + # or specific location parameters + unmarshalled_path_params = request.scope["openapi"].parameters.path + + # get body + unmarshalled_body = request.scope["openapi"].body + + # get security data + unmarshalled_security = request.scope["openapi"].security +``` + +### Response validation + +You can skip response validation process: by setting `response_cls` to `None` + +``` python hl_lines="5" +app = FastAPI() +app.add_middleware( + FastAPIOpenAPIMiddleware, + openapi=openapi, + response_cls=None, +) +``` + +## Low level + +For low level integration see [Starlette](starlette.md) integration. diff --git a/docs/integrations/fastapi.rst b/docs/integrations/fastapi.rst deleted file mode 100644 index 830ce643..00000000 --- a/docs/integrations/fastapi.rst +++ /dev/null @@ -1,62 +0,0 @@ -FastAPI -======= - -This section describes integration with `FastAPI `__ ASGI framework. - -.. note:: - - FastAPI also provides OpenAPI support. The main difference is that, unlike FastAPI's code-first approach, OpenAPI-core allows you to laverage your existing specification that alligns with API-First approach. You can read more about API-first vs. code-first in the `Guide to API-first `__. - -Middleware ----------- - -FastAPI can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``FastAPIOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. - -.. code-block:: python - :emphasize-lines: 2,5 - - from fastapi import FastAPI - from openapi_core.contrib.fastapi.middlewares import FastAPIOpenAPIMiddleware - - app = FastAPI() - app.add_middleware(FastAPIOpenAPIMiddleware, openapi=openapi) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. - -.. code-block:: python - - async def homepage(request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.scope["openapi"].parameters - # or specific location parameters - unmarshalled_path_params = request.scope["openapi"].parameters.path - - # get body - unmarshalled_body = request.scope["openapi"].body - - # get security data - unmarshalled_security = request.scope["openapi"].security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - app = FastAPI() - app.add_middleware( - FastAPIOpenAPIMiddleware, - openapi=openapi, - response_cls=None, - ) - -Low level ---------- - -For low level integration see `Starlette `_ integration. diff --git a/docs/integrations/flask.md b/docs/integrations/flask.md new file mode 100644 index 00000000..8aea5c76 --- /dev/null +++ b/docs/integrations/flask.md @@ -0,0 +1,107 @@ +# Flask + +This section describes integration with [Flask](https://flask.palletsprojects.com) web framework. + +## View decorator + +Flask can be integrated by [view decorator](https://flask.palletsprojects.com/en/latest/patterns/viewdecorators/) to apply OpenAPI validation to your application's specific views. + +Use `FlaskOpenAPIViewDecorator` with OpenAPI object to create the decorator. + +``` python hl_lines="1 3 6" +from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator + +openapi_validated = FlaskOpenAPIViewDecorator(openapi) + +@app.route('/home') +@openapi_validated +def home(): + return "Welcome home" +``` + +You can skip response validation process: by setting `response_cls` to `None` + +``` python hl_lines="5" +from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator + +openapi_validated = FlaskOpenAPIViewDecorator( + openapi, + response_cls=None, +) +``` + +If you want to decorate class based view you can use the decorators attribute: + +``` python hl_lines="2" +class MyView(View): + decorators = [openapi_validated] + + def dispatch_request(self): + return "Welcome home" + +app.add_url_rule('/home', view_func=MyView.as_view('home')) +``` + +## View + +As an alternative to the decorator-based integration, a Flask method based views can be integrated by inheritance from `FlaskOpenAPIView` class. + +``` python hl_lines="1 3 8" +from openapi_core.contrib.flask.views import FlaskOpenAPIView + +class MyView(FlaskOpenAPIView): + def get(self): + return "Welcome home" + +app.add_url_rule( + '/home', + view_func=MyView.as_view('home', spec), +) +``` + +Additional customization parameters can be passed to the view. + +``` python hl_lines="10" +from openapi_core.contrib.flask.views import FlaskOpenAPIView + +class MyView(FlaskOpenAPIView): + def get(self): + return "Welcome home" + +app.add_url_rule( + '/home', + view_func=MyView.as_view( + 'home', spec, + extra_format_validators=extra_format_validators, + ), +) +``` + +## Request parameters + +In Flask, all unmarshalled request data are provided as Flask request object's `openapi.parameters` attribute + +``` python hl_lines="6 7" +from flask.globals import request + +@app.route('/browse//') +@openapi +def browse(id): + browse_id = request.openapi.parameters.path['id'] + page = request.openapi.parameters.query.get('page', 1) + + return f"Browse {browse_id}, page {page}" +``` + +## Low level + +You can use `FlaskOpenAPIRequest` as a Flask request factory: + +```python +from openapi_core.contrib.flask import FlaskOpenAPIRequest + +openapi_request = FlaskOpenAPIRequest(flask_request) +result = openapi.unmarshal_request(openapi_request) +``` + +For response factory see [Werkzeug](werkzeug.md) integration. diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst deleted file mode 100644 index 91e5c6d7..00000000 --- a/docs/integrations/flask.rst +++ /dev/null @@ -1,118 +0,0 @@ -Flask -====== - -This section describes integration with `Flask `__ web framework. - -View decorator --------------- - -Flask can be integrated by `view decorator `__ to apply OpenAPI validation to your application's specific views. - -Use ``FlaskOpenAPIViewDecorator`` with OpenAPI object to create the decorator. - -.. code-block:: python - :emphasize-lines: 1,3,6 - - from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator - - openapi_validated = FlaskOpenAPIViewDecorator(openapi) - - @app.route('/home') - @openapi_validated - def home(): - return "Welcome home" - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 5 - - from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator - - openapi_validated = FlaskOpenAPIViewDecorator( - openapi, - response_cls=None, - ) - -If you want to decorate class based view you can use the decorators attribute: - -.. code-block:: python - :emphasize-lines: 2 - - class MyView(View): - decorators = [openapi_validated] - - def dispatch_request(self): - return "Welcome home" - - app.add_url_rule('/home', view_func=MyView.as_view('home')) - -View ----- - -As an alternative to the decorator-based integration, a Flask method based views can be integrated by inheritance from ``FlaskOpenAPIView`` class. - -.. code-block:: python - :emphasize-lines: 1,3,8 - - from openapi_core.contrib.flask.views import FlaskOpenAPIView - - class MyView(FlaskOpenAPIView): - def get(self): - return "Welcome home" - - app.add_url_rule( - '/home', - view_func=MyView.as_view('home', spec), - ) - -Additional customization parameters can be passed to the view. - -.. code-block:: python - :emphasize-lines: 10 - - from openapi_core.contrib.flask.views import FlaskOpenAPIView - - class MyView(FlaskOpenAPIView): - def get(self): - return "Welcome home" - - app.add_url_rule( - '/home', - view_func=MyView.as_view( - 'home', spec, - extra_format_validators=extra_format_validators, - ), - ) - -Request parameters ------------------- - -In Flask, all unmarshalled request data are provided as Flask request object's ``openapi.parameters`` attribute - -.. code-block:: python - :emphasize-lines: 6,7 - - from flask.globals import request - - @app.route('/browse//') - @openapi - def browse(id): - browse_id = request.openapi.parameters.path['id'] - page = request.openapi.parameters.query.get('page', 1) - - return f"Browse {browse_id}, page {page}" - -Low level ---------- - -You can use ``FlaskOpenAPIRequest`` as a Flask request factory: - -.. code-block:: python - - from openapi_core.contrib.flask import FlaskOpenAPIRequest - - openapi_request = FlaskOpenAPIRequest(flask_request) - result = openapi.unmarshal_request(openapi_request) - -For response factory see `Werkzeug `_ integration. diff --git a/docs/integrations/index.rst b/docs/integrations/index.md similarity index 50% rename from docs/integrations/index.rst rename to docs/integrations/index.md index f48c8cc9..4e3a86c2 100644 --- a/docs/integrations/index.rst +++ b/docs/integrations/index.md @@ -1,19 +1,3 @@ -Integrations -============ +# Integrations Openapi-core integrates with your popular libraries and frameworks. Each integration offers different levels of integration that help validate and unmarshal your request and response data. - -.. toctree:: - :maxdepth: 1 - - aiohttp - bottle - django - falcon - fastapi - flask - pyramid - requests - starlette - tornado - werkzeug diff --git a/docs/integrations/pyramid.md b/docs/integrations/pyramid.md new file mode 100644 index 00000000..7a83632f --- /dev/null +++ b/docs/integrations/pyramid.md @@ -0,0 +1,3 @@ +# Pyramid + +See [pyramid_openapi3](https://github.com/niteoweb/pyramid_openapi3) project. diff --git a/docs/integrations/pyramid.rst b/docs/integrations/pyramid.rst deleted file mode 100644 index 6989c5ce..00000000 --- a/docs/integrations/pyramid.rst +++ /dev/null @@ -1,4 +0,0 @@ -Pyramid -======= - -See `pyramid_openapi3 `_ project. diff --git a/docs/integrations/requests.md b/docs/integrations/requests.md new file mode 100644 index 00000000..5e306f9a --- /dev/null +++ b/docs/integrations/requests.md @@ -0,0 +1,50 @@ +# Requests + +This section describes integration with [Requests](https://requests.readthedocs.io) library. + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `RequestsOpenAPIRequest` to create OpenAPI request from Requests request: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIRequest + +request = Request('POST', url, data=data, headers=headers) +openapi_request = RequestsOpenAPIRequest(request) +openapi.validate_request(openapi_request) +``` + +### Webhook request + +Use `RequestsOpenAPIWebhookRequest` to create OpenAPI webhook request from Requests request: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest + +request = Request('POST', url, data=data, headers=headers) +openapi_webhook_request = RequestsOpenAPIWebhookRequest(request, "my_webhook") +openapi.validate_request(openapi_webhook_request) +``` + +### Response + +Use `RequestsOpenAPIResponse` to create OpenAPI response from Requests response: + +``` python +from requests import Request, Session +from openapi_core.contrib.requests import RequestsOpenAPIResponse + +session = Session() +request = Request('POST', url, data=data, headers=headers) +prepped = session.prepare_request(req) +response = session.send(prepped) +openapi_request = RequestsOpenAPIRequest(request) +openapi_response = RequestsOpenAPIResponse(response) +openapi.validate_response(openapi_request, openapi_response) +``` diff --git a/docs/integrations/requests.rst b/docs/integrations/requests.rst deleted file mode 100644 index de8164b6..00000000 --- a/docs/integrations/requests.rst +++ /dev/null @@ -1,55 +0,0 @@ -Requests -======== - -This section describes integration with `Requests `__ library. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``RequestsOpenAPIRequest`` to create OpenAPI request from Requests request: - -.. code-block:: python - - from requests import Request, Session - from openapi_core.contrib.requests import RequestsOpenAPIRequest - - request = Request('POST', url, data=data, headers=headers) - openapi_request = RequestsOpenAPIRequest(request) - openapi.validate_request(openapi_request) - -Webhook request -^^^^^^^^^^^^^^^ - -Use ``RequestsOpenAPIWebhookRequest`` to create OpenAPI webhook request from Requests request: - -.. code-block:: python - - from requests import Request, Session - from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest - - request = Request('POST', url, data=data, headers=headers) - openapi_webhook_request = RequestsOpenAPIWebhookRequest(request, "my_webhook") - openapi.validate_request(openapi_webhook_request) - -Response -^^^^^^^^ - -Use ``RequestsOpenAPIResponse`` to create OpenAPI response from Requests response: - -.. code-block:: python - - from requests import Request, Session - from openapi_core.contrib.requests import RequestsOpenAPIResponse - - session = Session() - request = Request('POST', url, data=data, headers=headers) - prepped = session.prepare_request(req) - response = session.send(prepped) - openapi_request = RequestsOpenAPIRequest(request) - openapi_response = RequestsOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) diff --git a/docs/integrations/starlette.md b/docs/integrations/starlette.md new file mode 100644 index 00000000..8e73b672 --- /dev/null +++ b/docs/integrations/starlette.md @@ -0,0 +1,89 @@ +# Starlette + +This section describes integration with [Starlette](https://www.starlette.io) ASGI framework. + +## Middleware + +Starlette can be integrated by [middleware](https://www.starlette.io/middleware/) to apply OpenAPI validation to your entire application. + +Add `StarletteOpenAPIMiddleware` with OpenAPI object to your `middleware` list. + +``` python hl_lines="1 6" +from openapi_core.contrib.starlette.middlewares import StarletteOpenAPIMiddleware +from starlette.applications import Starlette +from starlette.middleware import Middleware + +middleware = [ + Middleware(StarletteOpenAPIMiddleware, openapi=openapi), +] + +app = Starlette( + # ... + middleware=middleware, +) +``` + +After that all your requests and responses will be validated. + +Also you have access to unmarshal result object with all unmarshalled request data through `openapi` scope of request object. + +``` python +async def homepage(request): + # get parameters object with path, query, cookies and headers parameters + unmarshalled_params = request.scope["openapi"].parameters + # or specific location parameters + unmarshalled_path_params = request.scope["openapi"].parameters.path + + # get body + unmarshalled_body = request.scope["openapi"].body + + # get security data + unmarshalled_security = request.scope["openapi"].security +``` + +### Response validation + +You can skip response validation process: by setting `response_cls` to `None` + +``` python hl_lines="2" +middleware = [ + Middleware(StarletteOpenAPIMiddleware, openapi=openapi, response_cls=None), +] + +app = Starlette( + # ... + middleware=middleware, +) +``` + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `StarletteOpenAPIRequest` to create OpenAPI request from Starlette request: + +``` python +from openapi_core.contrib.starlette import StarletteOpenAPIRequest + +async def homepage(request): + openapi_request = StarletteOpenAPIRequest(request) + result = openapi.unmarshal_request(openapi_request) + return JSONResponse({'hello': 'world'}) +``` + +### Response + +Use `StarletteOpenAPIResponse` to create OpenAPI response from Starlette response: + +``` python +from openapi_core.contrib.starlette import StarletteOpenAPIResponse + +async def homepage(request): + response = JSONResponse({'hello': 'world'}) + openapi_request = StarletteOpenAPIRequest(request) + openapi_response = StarletteOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response +``` diff --git a/docs/integrations/starlette.rst b/docs/integrations/starlette.rst deleted file mode 100644 index 42911879..00000000 --- a/docs/integrations/starlette.rst +++ /dev/null @@ -1,97 +0,0 @@ -Starlette -========= - -This section describes integration with `Starlette `__ ASGI framework. - -Middleware ----------- - -Starlette can be integrated by `middleware `__ to apply OpenAPI validation to your entire application. - -Add ``StarletteOpenAPIMiddleware`` with OpenAPI object to your ``middleware`` list. - -.. code-block:: python - :emphasize-lines: 1,6 - - from openapi_core.contrib.starlette.middlewares import StarletteOpenAPIMiddleware - from starlette.applications import Starlette - from starlette.middleware import Middleware - - middleware = [ - Middleware(StarletteOpenAPIMiddleware, openapi=openapi), - ] - - app = Starlette( - # ... - middleware=middleware, - ) - -After that all your requests and responses will be validated. - -Also you have access to unmarshal result object with all unmarshalled request data through ``openapi`` scope of request object. - -.. code-block:: python - - async def homepage(request): - # get parameters object with path, query, cookies and headers parameters - unmarshalled_params = request.scope["openapi"].parameters - # or specific location parameters - unmarshalled_path_params = request.scope["openapi"].parameters.path - - # get body - unmarshalled_body = request.scope["openapi"].body - - # get security data - unmarshalled_security = request.scope["openapi"].security - -Response validation -^^^^^^^^^^^^^^^^^^^ - -You can skip response validation process: by setting ``response_cls`` to ``None`` - -.. code-block:: python - :emphasize-lines: 2 - - middleware = [ - Middleware(StarletteOpenAPIMiddleware, openapi=openapi, response_cls=None), - ] - - app = Starlette( - # ... - middleware=middleware, - ) - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``StarletteOpenAPIRequest`` to create OpenAPI request from Starlette request: - -.. code-block:: python - - from openapi_core.contrib.starlette import StarletteOpenAPIRequest - - async def homepage(request): - openapi_request = StarletteOpenAPIRequest(request) - result = openapi.unmarshal_request(openapi_request) - return JSONResponse({'hello': 'world'}) - -Response -^^^^^^^^ - -Use ``StarletteOpenAPIResponse`` to create OpenAPI response from Starlette response: - -.. code-block:: python - - from openapi_core.contrib.starlette import StarletteOpenAPIResponse - - async def homepage(request): - response = JSONResponse({'hello': 'world'}) - openapi_request = StarletteOpenAPIRequest(request) - openapi_response = StarletteOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response diff --git a/docs/integrations/tornado.md b/docs/integrations/tornado.md new file mode 100644 index 00000000..0a8c7198 --- /dev/null +++ b/docs/integrations/tornado.md @@ -0,0 +1,3 @@ +# Tornado + +See [tornado-openapi3](https://github.com/correl/tornado-openapi3) project. diff --git a/docs/integrations/tornado.rst b/docs/integrations/tornado.rst deleted file mode 100644 index 59ace988..00000000 --- a/docs/integrations/tornado.rst +++ /dev/null @@ -1,4 +0,0 @@ -Tornado -======= - -See `tornado-openapi3 `_ project. diff --git a/docs/integrations/werkzeug.md b/docs/integrations/werkzeug.md new file mode 100644 index 00000000..0ca451a5 --- /dev/null +++ b/docs/integrations/werkzeug.md @@ -0,0 +1,38 @@ +# Werkzeug + +This section describes integration with [Werkzeug](https://werkzeug.palletsprojects.com) a WSGI web application library. + +## Low level + +The integration defines classes useful for low level integration. + +### Request + +Use `WerkzeugOpenAPIRequest` to create OpenAPI request from Werkzeug request: + +``` python +from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest + +def application(environ, start_response): + request = Request(environ) + openapi_request = WerkzeugOpenAPIRequest(request) + openapi.validate_request(openapi_request) + response = Response("Hello world", mimetype='text/plain') + return response(environ, start_response) +``` + +### Response + +Use `WerkzeugOpenAPIResponse` to create OpenAPI response from Werkzeug response: + +``` python +from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse + +def application(environ, start_response): + request = Request(environ) + response = Response("Hello world", mimetype='text/plain') + openapi_request = WerkzeugOpenAPIRequest(request) + openapi_response = WerkzeugOpenAPIResponse(response) + openapi.validate_response(openapi_request, openapi_response) + return response(environ, start_response) +``` diff --git a/docs/integrations/werkzeug.rst b/docs/integrations/werkzeug.rst deleted file mode 100644 index 5061d9a6..00000000 --- a/docs/integrations/werkzeug.rst +++ /dev/null @@ -1,42 +0,0 @@ -Werkzeug -======== - -This section describes integration with `Werkzeug `__ a WSGI web application library. - -Low level ---------- - -The integration defines classes useful for low level integration. - -Request -^^^^^^^ - -Use ``WerkzeugOpenAPIRequest`` to create OpenAPI request from Werkzeug request: - -.. code-block:: python - - from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest - - def application(environ, start_response): - request = Request(environ) - openapi_request = WerkzeugOpenAPIRequest(request) - openapi.validate_request(openapi_request) - response = Response("Hello world", mimetype='text/plain') - return response(environ, start_response) - -Response -^^^^^^^^ - -Use ``WerkzeugOpenAPIResponse`` to create OpenAPI response from Werkzeug response: - -.. code-block:: python - - from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse - - def application(environ, start_response): - request = Request(environ) - response = Response("Hello world", mimetype='text/plain') - openapi_request = WerkzeugOpenAPIRequest(request) - openapi_response = WerkzeugOpenAPIResponse(response) - openapi.validate_response(openapi_request, openapi_response) - return response(environ, start_response) diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 00000000..bf6df2c6 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,40 @@ +--- +hide: + - navigation +--- + +# Security + +Openapi-core provides you easy access to security data for authentication and authorization process. + +Supported security schemas: + +- http – for Basic and Bearer HTTP authentications schemes +- apiKey – for API keys and cookie authentication + +Here's an example with scheme `BasicAuth` and `ApiKeyAuth` security schemes: + +```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. + +```python +# get basic auth decoded credentials +result.security['BasicAuth'] + +# get api key +result.security['ApiKeyAuth'] +``` diff --git a/docs/security.rst b/docs/security.rst deleted file mode 100644 index fc0e9a90..00000000 --- a/docs/security.rst +++ /dev/null @@ -1,36 +0,0 @@ -Security -======== - -Openapi-core provides you easy access to security data for authentication and authorization process. - -Supported security schemas: - -* http – for Basic and Bearer HTTP authentications schemes -* apiKey – for API keys and cookie authentication - -Here's an example with scheme ``BasicAuth`` and ``ApiKeyAuth`` security schemes: - -.. code-block:: yaml - - security: - - BasicAuth: [] - - ApiKeyAuth: [] - components: - securitySchemes: - BasicAuth: - type: http - scheme: basic - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - -Security schemes data are accessible from `security` attribute of `RequestUnmarshalResult` object. - -.. code-block:: python - - # get basic auth decoded credentials - result.security['BasicAuth'] - - # get api key - result.security['ApiKeyAuth'] diff --git a/docs/unmarshalling.md b/docs/unmarshalling.md new file mode 100644 index 00000000..1133ae3d --- /dev/null +++ b/docs/unmarshalling.md @@ -0,0 +1,94 @@ +--- +hide: + - navigation +--- + +# Unmarshalling + +Unmarshalling is the process of converting a primitive schema type of value into a higher-level object based on a `format` keyword. All request/response data, that can be described by a schema in OpenAPI specification, can be unmarshalled. + +Unmarshallers firstly validate data against the provided schema (See [Validation](validation.md)). + +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 [Format unmarshallers](customizations/extra_format_unmarshallers.md)). + +## Request unmarshalling + +Use `unmarshal_request` method to validate and unmarshal request data against a given spec. By default, OpenAPI spec version is detected: + +```python +# raises error if request is invalid +result = openapi.unmarshal_request(request) +``` + +Request object should implement OpenAPI Request protocol (See [Integrations](integrations/index.md)). + +!!! 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. + +```python +# raises error if request is invalid +result = openapi.unmarshal_request(webhook_request) +``` + +Webhook request object should implement OpenAPI WebhookRequest protocol (See [Integrations](integrations/index.md)). + +Retrieve validated and unmarshalled request data + +```python +# get parameters +path_params = result.parameters.path +query_params = result.parameters.query +cookies_params = result.parameters.cookies +headers_params = result.parameters.headers +# get body +body = result.body +# get security data +security = result.security +``` + +You can also define your own request unmarshaller (See [Request unmarshaller](customizations/request_unmarshaller_cls.md)). + +## Response unmarshalling + +Use `unmarshal_response` method to validate and unmarshal response data against a given spec. By default, OpenAPI spec version is detected: + +```python +# raises error if response is invalid +result = openapi.unmarshal_response(request, response) +``` + +Response object should implement OpenAPI Response protocol (See [Integrations](integrations/index.md)). + +!!! 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. + +```python +# raises error if request is invalid +result = openapi.unmarshal_response(webhook_request, response) +``` + +Retrieve validated and unmarshalled response data + +```python +# get headers +headers = result.headers +# get data +data = result.data +``` + +You can also define your own response unmarshaller (See [Response unmarshaller](customizations/response_unmarshaller_cls.md)). diff --git a/docs/unmarshalling.rst b/docs/unmarshalling.rst deleted file mode 100644 index 82c1302b..00000000 --- a/docs/unmarshalling.rst +++ /dev/null @@ -1,91 +0,0 @@ -Unmarshalling -============= - -Unmarshalling is the process of converting a primitive schema type of value into a higher-level object based on a ``format`` keyword. All request/response data, that can be described by a schema in OpenAPI specification, can be unmarshalled. - -Unmarshallers firstly validate data against the provided schema (See :doc:`validation`). - -Openapi-core comes with a set of built-in format unmarshallers: - -* ``date`` - converts string into a date object, -* ``date-time`` - converts string into a datetime object, -* ``binary`` - converts string into a byte object, -* ``uuid`` - converts string into an UUID object, -* ``byte`` - decodes Base64-encoded string. - -You can also define your own format unmarshallers (See :doc:`customizations/extra_format_unmarshallers`). - -Request unmarshalling ---------------------- - -Use ``unmarshal_request`` method to validate and unmarshal request data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(request) - -Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate and unmarshal webhook request data against a given spec. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_request(webhook_request) - -Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). - -Retrieve validated and unmarshalled request data - -.. code-block:: python - - # get parameters - path_params = result.parameters.path - query_params = result.parameters.query - cookies_params = result.parameters.cookies - headers_params = result.parameters.headers - # get body - body = result.body - # get security data - security = result.security - -You can also define your own request unmarshaller (See :doc:`customizations/request_unmarshaller_cls`). - -Response unmarshalling ----------------------- - -Use ``unmarshal_response`` method to validate and unmarshal response data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if response is invalid - result = openapi.unmarshal_response(request, response) - -Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate and unmarshal response data from webhook request against a given spec. - -.. code-block:: python - - # raises error if request is invalid - result = openapi.unmarshal_response(webhook_request, response) - -Retrieve validated and unmarshalled response data - -.. code-block:: python - - # get headers - headers = result.headers - # get data - data = result.data - -You can also define your own response unmarshaller (See :doc:`customizations/response_unmarshaller_cls`). diff --git a/docs/validation.md b/docs/validation.md new file mode 100644 index 00000000..376e0301 --- /dev/null +++ b/docs/validation.md @@ -0,0 +1,69 @@ +--- +hide: + - navigation +--- + +# Validation + +Validation is a process to validate request/response data under a given schema defined in OpenAPI specification. + +Additionally, openapi-core uses the `format` keyword to check if primitive types conform to defined formats. + +Such valid formats can be forther unmarshalled (See [Unmarshalling](unmarshalling.md)). + +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 [Format validators](customizations/extra_format_validators.md)). + +## Request validation + +Use `validate_request` method to validate request data against a given spec. By default, OpenAPI spec version is detected: + +```python +# raises error if request is invalid +openapi.validate_request(request) +``` + +Request object should implement OpenAPI Request protocol (See [Integrations](integrations/index.md)). + +!!! note + + Webhooks feature is part of OpenAPI v3.1 only + + +Use the same method to validate webhook request data against a given spec. + +```python +# raises error if request is invalid +openapi.validate_request(webhook_request) +``` + +Webhook request object should implement OpenAPI WebhookRequest protocol (See [Integrations](integrations/index.md)). + +You can also define your own request validator (See [Request validator](customizations/request_validator_cls.md)). + +## Response validation + +Use `validate_response` function to validate response data against a given spec. By default, OpenAPI spec version is detected: + +```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 [Integrations](integrations/index.md)). + +!!! 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. + +```python +# raises error if request is invalid +openapi.validate_response(webhook_request, response) +``` + +You can also define your own response validator (See [Response validator](customizations/response_validator_cls.md)). diff --git a/docs/validation.rst b/docs/validation.rst deleted file mode 100644 index 0cd9ac22..00000000 --- a/docs/validation.rst +++ /dev/null @@ -1,66 +0,0 @@ -Validation -========== - -Validation is a process to validate request/response data under a given schema defined in OpenAPI specification. - -Additionally, openapi-core uses the ``format`` keyword to check if primitive types conform to defined formats. - -Such valid formats can be forther unmarshalled (See :doc:`unmarshalling`). - -Depends on the OpenAPI version, openapi-core comes with a set of built-in format validators such as: ``date``, ``date-time``, ``binary``, ``uuid`` or ``byte``. - -You can also define your own format validators (See :doc:`customizations/extra_format_validators`). - -Request validation ------------------- - -Use ``validate_request`` method to validate request data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_request(request) - -Request object should implement OpenAPI Request protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same method to validate webhook request data against a given spec. - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_request(webhook_request) - -Webhook request object should implement OpenAPI WebhookRequest protocol (See :doc:`integrations/index`). - -You can also define your own request validator (See :doc:`customizations/request_validator_cls`). - -Response validation -------------------- - -Use ``validate_response`` function to validate response data against a given spec. By default, OpenAPI spec version is detected: - -.. code-block:: python - - from openapi_core import validate_response - - # raises error if response is invalid - openapi.validate_response(request, response) - -Response object should implement OpenAPI Response protocol (See :doc:`integrations/index`). - -.. note:: - - Webhooks feature is part of OpenAPI v3.1 only - -Use the same function to validate response data from webhook request against a given spec. - -.. code-block:: python - - # raises error if request is invalid - openapi.validate_response(webhook_request, response) - -You can also define your own response validator (See :doc:`customizations/response_validator_cls`). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..d168d8ed --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,84 @@ +site_name: OpenAPI-core +site_description: OpenAPI for Python +site_url: https://openapi-core.readthedocs.io/ +theme: + name: material + icon: + repo: fontawesome/brands/github-alt + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/toggle-switch + name: Switch to light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: lime + accent: amber + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: lime + accent: amber + toggle: + icon: material/toggle-switch-off + name: Switch to system preference + features: + - content.code.annotate + - content.code.copy + - content.footnote.tooltips + - content.tabs.link + - content.tooltips + - navigation.footer + - navigation.indexes + - navigation.instant + - navigation.instant.prefetch + - navigation.instant.progress + - navigation.path + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow +repo_name: python-openapi/openapi-core +repo_url: https://github.com/python-openapi/openapi-core +nav: + - OpenAPI-core: index.md + - unmarshalling.md + - validation.md + - Integrations: + - integrations/index.md + - integrations/aiohttp.md + - integrations/bottle.md + - integrations/django.md + - integrations/falcon.md + - integrations/fastapi.md + - integrations/flask.md + - integrations/pyramid.md + - integrations/requests.md + - integrations/starlette.md + - integrations/tornado.md + - integrations/werkzeug.md + - Customizations: + - customizations/index.md + - customizations/spec_validator_cls.md + - customizations/request_validator_cls.md + - customizations/response_validator_cls.md + - customizations/request_unmarshaller_cls.md + - customizations/response_unmarshaller_cls.md + - customizations/extra_media_type_deserializers.md + - customizations/extra_format_validators.md + - customizations/extra_format_unmarshallers.md + - security.md + - extensions.md + - contributing.md +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true diff --git a/poetry.lock b/poetry.lock index c1a7b11b..4774593d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -155,17 +155,6 @@ files = [ [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" @@ -201,17 +190,6 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- 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" @@ -229,6 +207,21 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "astunparse" +version = "1.6.3" +description = "An AST unparser for Python" +optional = false +python-versions = "*" +files = [ + {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] + +[package.dependencies] +six = ">=1.6.1,<2.0" +wheel = ">=0.23.0,<1.0" + [[package]] name = "async-timeout" version = "4.0.3" @@ -658,17 +651,6 @@ files = [ "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} django = ">=4.2" -[[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" @@ -884,6 +866,38 @@ files = [ {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "1.3.0" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "griffe-1.3.0-py3-none-any.whl", hash = "sha256:3c85b5704136379bed767ef9c1d7776cac50886e341b61b71c6983dfe04d7cb2"}, + {file = "griffe-1.3.0.tar.gz", hash = "sha256:878cd99709b833fab7c41a6545188bcdbc1fcb3b441374449d34b69cb864de69"}, +] + +[package.dependencies] +astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""} +colorama = ">=0.4" + [[package]] name = "h11" version = "0.14.0" @@ -966,17 +980,6 @@ files = [ {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" @@ -1181,6 +1184,24 @@ files = [ {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] +[[package]] +name = "markdown" +version = "3.7" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -1261,6 +1282,166 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "1.2.0" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, + {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, +] + +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.5.34" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"}, + {file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.26.1" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf"}, + {file = "mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33"}, +] + +[package.dependencies] +click = ">=7.0" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.11.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.4" +mkdocs-autorefs = ">=1.2" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +platformdirs = ">=2.2" +pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.11.1" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af"}, + {file = "mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322"}, +] + +[package.dependencies] +griffe = ">=0.49" +mkdocs-autorefs = ">=1.2" +mkdocstrings = ">=0.26" + [[package]] name = "more-itertools" version = "10.4.0" @@ -1488,6 +1669,21 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "parse" version = "1.20.2" @@ -1728,23 +1924,6 @@ files = [ [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" @@ -1770,6 +1949,24 @@ files = [ [package.extras] plugins = ["importlib-metadata"] +[[package]] +name = "pymdown-extensions" +version = "10.9" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"}, + {file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.12)"] + [[package]] name = "pytest" version = "8.3.2" @@ -1877,6 +2074,20 @@ flake8 = ">=2.3" pytest = ">=2.4.2" pytest-cache = "*" +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-multipart" version = "0.0.9" @@ -1962,6 +2173,20 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + [[package]] name = "referencing" version = "0.30.2" @@ -1977,6 +2202,94 @@ files = [ attrs = ">=22.2.0" rpds-py = ">=0.7.0" +[[package]] +name = "regex" +version = "2024.7.24" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, + {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, + {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, + {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, + {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, + {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, + {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, + {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, + {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, + {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, + {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, + {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -2176,168 +2489,6 @@ files = [ {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.14" -description = "Adaptation of mkdocs-material theme for the Sphinx documentation system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinx_immaterial-0.11.14-py3-none-any.whl", hash = "sha256:dd1a30614c8ecaa931155189e7d54f211232e31cf3e5c6d28ba9f04a4817f0a3"}, - {file = "sphinx_immaterial-0.11.14.tar.gz", hash = "sha256:e1e8ba93c78a3e007743fede01a3be43f5ae97c5cc19b8e2a4d2aa058abead61"}, -] - -[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" @@ -2451,6 +2602,53 @@ platformdirs = ">=3.9.1,<4" 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 = "watchdog" +version = "4.0.2" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, + {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, + {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, + {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, + {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + [[package]] name = "webob" version = "1.8.8" @@ -2483,6 +2681,20 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "wheel" +version = "0.44.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"}, + {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [[package]] name = "yarl" version = "1.9.2" @@ -2597,4 +2809,4 @@ starlette = ["aioitertools", "starlette"] [metadata] lock-version = "2.0" python-versions = "^3.8.0" -content-hash = "3678bfd659a8819b5b792e20944d99a4f28febaf8f8950b628463ce73acb89dd" +content-hash = "cb117447f6a1382c9e5630a66deb66694fa09170a41444d03df75d77b8465718" diff --git a/pyproject.toml b/pyproject.toml index 980d962a..2db15a4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,8 +114,9 @@ pyflakes = "^3.1.0" fastapi = ">=0.111,<0.113" [tool.poetry.group.docs.dependencies] -sphinx = ">=5.3,<8.0" -sphinx-immaterial = "^0.11.0" +mkdocs = "^1.6.1" +mkdocstrings = {extras = ["python"], version = "^0.26.1"} +mkdocs-material = "^9.5.34" [tool.pytest.ini_options] addopts = """