diff --git a/Makefile b/Makefile index 77533f6..05d4fe1 100644 --- a/Makefile +++ b/Makefile @@ -31,4 +31,10 @@ reports-cleanup: test-cleanup: test-cache-cleanup reports-cleanup +docs-html: + sphinx-build -b html docs docs/_build + +docs-cleanup: + @rm -rf docs/_build + cleanup: dist-cleanup test-cleanup diff --git a/README.rst b/README.rst index 815036b..23dc617 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,24 @@ About ##### Openapi-core is a Python library that adds client-side and server-side support -for the `OpenAPI Specification v3.0.0 `__. +for the `OpenAPI Specification v3 `__. + +Key features +************ + +* **Validation** of requests and responses +* Schema **casting** and **unmarshalling** +* Media type and parameters **deserialization** +* **Security** providers (API keys, Cookie, Basic and Bearer HTTP authentications) +* Custom **deserializers** and **formats** +* **Integration** with libraries and frameworks + + +Documentation +############# + +Check documentation to see more details about the features. All documentation is in the "docs" directory and online at `openapi-core.readthedocs.io `__ + Installation ############ @@ -81,7 +98,7 @@ and unmarshal request data from validation result # get security data validated_security = result.security -Request object should be instance of OpenAPIRequest class (See `Integrations`_). +Request object should be instance of OpenAPIRequest class (See `Integrations `__). Response ******** @@ -111,303 +128,7 @@ and unmarshal response data from validation result # get data validated_data = result.data -Response object should be instance of OpenAPIResponse class (See `Integrations`_). - -Security -******** - -openapi-core supports security for authentication and authorization process. Security data for security schemas are accessible from `security` attribute of `RequestValidationResult` object. - -For given security specification: - -.. code-block:: yaml - - security: - - BasicAuth: [] - - ApiKeyAuth: [] - components: - securitySchemes: - BasicAuth: - type: http - scheme: basic - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - -you can access your security data the following: - -.. code-block:: python - - result = validator.validate(request) - - # get basic auth decoded credentials - result.security['BasicAuth'] - - # get api key - result.security['ApiKeyAuth'] - -Supported security types: - -* http – for Basic and Bearer HTTP authentications schemes -* apiKey – for API keys and cookie authentication - - -Customizations -############## - -Spec validation -*************** - -By default, spec dict is validated on spec creation time. Disabling the validation can improve the performance. - -.. code-block:: python - - from openapi_core import create_spec - - spec = create_spec(spec_dict, validate_spec=False) - -Deserializers -************* - -Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `RequestValidator` or `ResponseValidator` constructor: - -.. code-block:: python - - def protobuf_deserializer(message): - feature = route_guide_pb2.Feature() - feature.ParseFromString(message) - return feature - - custom_media_type_deserializers = { - 'application/protobuf': protobuf_deserializer, - } - - validator = ResponseValidator( - spec, custom_media_type_deserializers=custom_media_type_deserializers) - - result = validator.validate(request, response) - -Formats -******* - -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-core comes with a set of built-in formatters, but it's also possible to add support for custom formatters for `RequestValidator` and `ResponseValidator`. - -Here's how you could add support for a ``usdate`` format that handles dates of the form MM/DD/YYYY: - -.. code-block:: python - - from datetime import datetime - import re - - class USDateFormatter: - def validate(self, value) -> bool: - return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) - - def unmarshal(self, value): - return datetime.strptime(value, "%m/%d/%y").date - - - custom_formatters = { - 'usdate': USDateFormatter(), - } - - validator = ResponseValidator(spec, custom_formatters=custom_formatters) - - result = validator.validate(request, response) - -Integrations -############ - -Django -****** - -For Django 2.2 you can use DjangoOpenAPIRequest a Django request factory: - -.. code-block:: python - - from openapi_core.validation.request.validators import RequestValidator - from openapi_core.contrib.django import DjangoOpenAPIRequest - - openapi_request = DjangoOpenAPIRequest(django_request) - validator = RequestValidator(spec) - result = validator.validate(openapi_request) - -You can use DjangoOpenAPIResponse as a Django response factory: - -.. code-block:: python - - from openapi_core.validation.response.validators import ResponseValidator - from openapi_core.contrib.django import DjangoOpenAPIResponse - - openapi_response = DjangoOpenAPIResponse(django_response) - validator = ResponseValidator(spec) - result = validator.validate(openapi_request, openapi_response) - -Falcon -****** - -This section describes integration with `Falcon `__ web framework. - -Middleware -========== - -Falcon API can be integrated by `FalconOpenAPIMiddleware` middleware. - -.. code-block:: python - - from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware - - openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) - api = falcon.API(middleware=[openapi_middleware]) - -Low level -========= - -For Falcon you can use FalconOpenAPIRequest a Falcon request factory: - -.. code-block:: python - - from openapi_core.validation.request.validators import RequestValidator - from openapi_core.contrib.falcon import FalconOpenAPIRequestFactory - - openapi_request = FalconOpenAPIRequestFactory.create(falcon_request) - validator = RequestValidator(spec) - result = validator.validate(openapi_request) - -You can use FalconOpenAPIResponse as a Falcon response factory: - -.. code-block:: python - - from openapi_core.validation.response.validators import ResponseValidator - from openapi_core.contrib.falcon import FalconOpenAPIResponseFactory - - openapi_response = FalconOpenAPIResponseFactory.create(falcon_response) - validator = ResponseValidator(spec) - result = validator.validate(openapi_request, openapi_response) - -Flask -***** - -Decorator -========= - -Flask views can be integrated by `FlaskOpenAPIViewDecorator` decorator. - -.. code-block:: python - - from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator - - openapi = FlaskOpenAPIViewDecorator.from_spec(spec) - - @app.route('/home') - @openapi - def home(): - pass - -If you want to decorate class based view you can use the decorators attribute: - -.. code-block:: python - - class MyView(View): - decorators = [openapi] - -View -==== - -As an alternative to the decorator-based integration, Flask method based views can be integrated by inheritance from `FlaskOpenAPIView` class. - -.. code-block:: python - - from openapi_core.contrib.flask.views import FlaskOpenAPIView - - class MyView(FlaskOpenAPIView): - pass - - app.add_url_rule('/home', view_func=MyView.as_view('home', spec)) - -Request parameters -================== - -In Flask, all unmarshalled request data are provided as Flask request object's openapi.parameters attribute - -.. code-block:: python - - from flask.globals import request - - @app.route('/browse//') - @openapi - def home(): - browse_id = request.openapi.parameters.path['id'] - page = request.openapi.parameters.query.get('page', 1) - -Low level -========= - -You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory: - -.. code-block:: python - - from openapi_core.validation.request.validators import RequestValidator - from openapi_core.contrib.flask import FlaskOpenAPIRequest - - openapi_request = FlaskOpenAPIRequest(flask_request) - validator = RequestValidator(spec) - result = validator.validate(openapi_request) - -You can use FlaskOpenAPIResponse as a Flask/Werkzeug response factory: - -.. code-block:: python - - from openapi_core.validation.response.validators import ResponseValidator - from openapi_core.contrib.flask import FlaskOpenAPIResponse - - openapi_response = FlaskOpenAPIResponse(flask_response) - validator = ResponseValidator(spec) - result = validator.validate(openapi_request, openapi_response) - -Pyramid -******* - -See `pyramid_openapi3 `_ project. - -Bottle -******* - -See `bottle-openapi-3 `_ project. - - -Requests -******** - -This section describes integration with `Requests `__ library. - -Low level -========= - -For Requests you can use RequestsOpenAPIRequest a Requests request factory: - -.. code-block:: python - - from openapi_core.validation.request.validators import RequestValidator - from openapi_core.contrib.requests import RequestsOpenAPIRequest - - openapi_request = RequestsOpenAPIRequest(requests_request) - validator = RequestValidator(spec) - result = validator.validate(openapi_request) - -You can use RequestsOpenAPIResponse as a Requests response factory: - -.. code-block:: python - - from openapi_core.validation.response.validators import ResponseValidator - from openapi_core.contrib.requests import RequestsOpenAPIResponse - - openapi_response = RequestsOpenAPIResponse(requests_response) - validator = ResponseValidator(spec) - result = validator.validate(openapi_request, openapi_response) +Response object should be instance of OpenAPIResponse class (See `Integrations `__). Related projects ################ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..68450c2 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,61 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import openapi_core + +# -- Project information ----------------------------------------------------- + +project = 'openapi-core' +copyright = '2021, Artur Maciag' +author = 'Artur Maciag' + +# The full version, including alpha/beta/rc tags +release = openapi_core.__version__ + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/customizations.rst b/docs/customizations.rst new file mode 100644 index 0000000..dcbf799 --- /dev/null +++ b/docs/customizations.rst @@ -0,0 +1,65 @@ +Customizations +============== + +Spec validation +--------------- + +By default, spec dict is validated on spec creation time. Disabling the validation can improve the performance. + +.. code-block:: python + + from openapi_core import create_spec + + spec = create_spec(spec_dict, validate_spec=False) + +Deserializers +------------- + +Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `RequestValidator` or `ResponseValidator` constructor: + +.. code-block:: python + + def protobuf_deserializer(message): + feature = route_guide_pb2.Feature() + feature.ParseFromString(message) + return feature + + custom_media_type_deserializers = { + 'application/protobuf': protobuf_deserializer, + } + + validator = ResponseValidator( + spec, custom_media_type_deserializers=custom_media_type_deserializers) + + result = validator.validate(request, response) + +Formats +------- + +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-core comes with a set of built-in formatters, but it's also possible to add support for custom formatters for `RequestValidator` and `ResponseValidator`. + +Here's how you could add support for a ``usdate`` format that handles dates of the form MM/DD/YYYY: + +.. code-block:: python + + from datetime import datetime + import re + + class USDateFormatter: + def validate(self, value) -> bool: + return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value)) + + def unmarshal(self, value): + return datetime.strptime(value, "%m/%d/%y").date + + + custom_formatters = { + 'usdate': USDateFormatter(), + } + + validator = ResponseValidator(spec, custom_formatters=custom_formatters) + + result = validator.validate(request, response) + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..33b5f48 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,43 @@ +.. openapi-core documentation master file, created by + sphinx-quickstart on Tue Feb 2 17:41:34 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to openapi-core's documentation! +======================================== + +Openapi-core is a Python library that adds client-side and server-side support +for the `OpenAPI Specification v3 `__. + +Key features +------------ + +* **Validation** of requests and responses +* Schema **casting** and **unmarshalling** +* Media type and parameters **deserialization** +* **Security** providers (API keys, Cookie, Basic and Bearer HTTP authentications) +* Custom **deserializers** and **formats** +* **Integration** with libraries and frameworks + + +Table of contents +----------------- + +.. Navigation/TOC + +.. toctree:: + :maxdepth: 2 + + installation + usage + customizations + integrations + + +Related projects +================ + +* `openapi-spec-validator `__ + Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger) and OpenAPI 3.0.0 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 which is an extended subset of the JSON Schema Specification Wright Draft 00. diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..ef7032f --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,15 @@ +Installation +============ + +Recommended way (via pip): + +.. code-block:: console + + $ pip install openapi-core + +Alternatively you can download the code and install from the repository: + +.. code-block:: console + + $ pip install -e git+https://github.com/p1c2u/openapi-core.git#egg=openapi_core + diff --git a/docs/integrations.rst b/docs/integrations.rst new file mode 100644 index 0000000..c7d3e47 --- /dev/null +++ b/docs/integrations.rst @@ -0,0 +1,198 @@ +Integrations +============ + +Bottle +------ + +See `bottle-openapi-3 `_ project. + + +Django +------ + +This section describes integration with `Django `__ web framework. + +For Django 2.2 you can use DjangoOpenAPIRequest a Django request factory: + +.. code-block:: python + + from openapi_core.validation.request.validators import RequestValidator + from openapi_core.contrib.django import DjangoOpenAPIRequest + + openapi_request = DjangoOpenAPIRequest(django_request) + validator = RequestValidator(spec) + result = validator.validate(openapi_request) + +You can use DjangoOpenAPIResponse as a Django response factory: + +.. code-block:: python + + from openapi_core.validation.response.validators import ResponseValidator + from openapi_core.contrib.django import DjangoOpenAPIResponse + + openapi_response = DjangoOpenAPIResponse(django_response) + validator = ResponseValidator(spec) + result = validator.validate(openapi_request, openapi_response) + + +Falcon +------ + +This section describes integration with `Falcon `__ web framework. + +Middleware +~~~~~~~~~~ + +Falcon API can be integrated by `FalconOpenAPIMiddleware` middleware. + +.. code-block:: python + + from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware + + openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) + api = falcon.API(middleware=[openapi_middleware]) + +Low level +~~~~~~~~~ + +For Falcon you can use FalconOpenAPIRequest a Falcon request factory: + +.. code-block:: python + + from openapi_core.validation.request.validators import RequestValidator + from openapi_core.contrib.falcon import FalconOpenAPIRequestFactory + + openapi_request = FalconOpenAPIRequestFactory.create(falcon_request) + validator = RequestValidator(spec) + result = validator.validate(openapi_request) + +You can use FalconOpenAPIResponse as a Falcon response factory: + +.. code-block:: python + + from openapi_core.validation.response.validators import ResponseValidator + from openapi_core.contrib.falcon import FalconOpenAPIResponseFactory + + openapi_response = FalconOpenAPIResponseFactory.create(falcon_response) + validator = ResponseValidator(spec) + result = validator.validate(openapi_request, openapi_response) + + +Flask +----- + +This section describes integration with `Flask `__ web framework. + +Decorator +~~~~~~~~~ + +Flask views can be integrated by `FlaskOpenAPIViewDecorator` decorator. + +.. code-block:: python + + from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator + + openapi = FlaskOpenAPIViewDecorator.from_spec(spec) + + @app.route('/home') + @openapi + def home(): + pass + +If you want to decorate class based view you can use the decorators attribute: + +.. code-block:: python + + class MyView(View): + decorators = [openapi] + +View +~~~~ + +As an alternative to the decorator-based integration, Flask method based views can be integrated by inheritance from `FlaskOpenAPIView` class. + +.. code-block:: python + + from openapi_core.contrib.flask.views import FlaskOpenAPIView + + class MyView(FlaskOpenAPIView): + pass + + app.add_url_rule('/home', view_func=MyView.as_view('home', spec)) + +Request parameters +~~~~~~~~~~~~~~~~~~ + +In Flask, all unmarshalled request data are provided as Flask request object's openapi.parameters attribute + +.. code-block:: python + + from flask.globals import request + + @app.route('/browse//') + @openapi + def home(): + browse_id = request.openapi.parameters.path['id'] + page = request.openapi.parameters.query.get('page', 1) + +Low level +~~~~~~~~~ + +You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory: + +.. code-block:: python + + from openapi_core.validation.request.validators import RequestValidator + from openapi_core.contrib.flask import FlaskOpenAPIRequest + + openapi_request = FlaskOpenAPIRequest(flask_request) + validator = RequestValidator(spec) + result = validator.validate(openapi_request) + +You can use FlaskOpenAPIResponse as a Flask/Werkzeug response factory: + +.. code-block:: python + + from openapi_core.validation.response.validators import ResponseValidator + from openapi_core.contrib.flask import FlaskOpenAPIResponse + + openapi_response = FlaskOpenAPIResponse(flask_response) + validator = ResponseValidator(spec) + result = validator.validate(openapi_request, openapi_response) + + +Pyramid +------- + +See `pyramid_openapi3 `_ project. + + +Requests +-------- + +This section describes integration with `Requests `__ library. + +Low level +~~~~~~~~~ + +For Requests you can use RequestsOpenAPIRequest a Requests request factory: + +.. code-block:: python + + from openapi_core.validation.request.validators import RequestValidator + from openapi_core.contrib.requests import RequestsOpenAPIRequest + + openapi_request = RequestsOpenAPIRequest(requests_request) + validator = RequestValidator(spec) + result = validator.validate(openapi_request) + +You can use RequestsOpenAPIResponse as a Requests response factory: + +.. code-block:: python + + from openapi_core.validation.response.validators import ResponseValidator + from openapi_core.contrib.requests import RequestsOpenAPIResponse + + openapi_response = RequestsOpenAPIResponse(requests_response) + validator = ResponseValidator(spec) + result = validator.validate(openapi_request, openapi_response) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..8213302 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx +sphinx_rtd_theme diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..b54f85d --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,116 @@ +Usage +===== + +Firstly create your specification: + +.. code-block:: python + + from openapi_core import create_spec + + spec = create_spec(spec_dict) + + +Request +------- + +Now you can use it to validate requests + +.. code-block:: python + + from openapi_core.validation.request.validators import RequestValidator + + validator = RequestValidator(spec) + result = validator.validate(request) + + # raise errors if request invalid + result.raise_for_errors() + + # get list of errors + errors = result.errors + +and unmarshal request data from validation result + +.. code-block:: python + + # get parameters object with path, query, cookies and headers parameters + validated_params = result.parameters + # or specific parameters + validated_path_params = result.parameters.path + + # get body + validated_body = result.body + + # get security data + validated_security = result.security + +Request object should be instance of OpenAPIRequest class (See :doc:`integrations`). + +Response +-------- + +You can also validate responses + +.. code-block:: python + + from openapi_core.validation.response.validators import ResponseValidator + + validator = ResponseValidator(spec) + result = validator.validate(request, response) + + # raise errors if response invalid + result.raise_for_errors() + + # get list of errors + errors = result.errors + +and unmarshal response data from validation result + +.. code-block:: python + + # get headers + validated_headers = result.headers + + # get data + validated_data = result.data + +Response object should be instance of OpenAPIResponse class (See :doc:`integrations`). + +Security +-------- + +openapi-core supports security for authentication and authorization process. Security data for security schemas are accessible from `security` attribute of `RequestValidationResult` object. + +For given security specification: + +.. code-block:: yaml + + security: + - BasicAuth: [] + - ApiKeyAuth: [] + components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + +you can access your security data the following: + +.. code-block:: python + + result = validator.validate(request) + + # get basic auth decoded credentials + result.security['BasicAuth'] + + # get api key + result.security['ApiKeyAuth'] + +Supported security types: + +* http – for Basic and Bearer HTTP authentications schemes +* apiKey – for API keys and cookie authentication +