Skip to content

Commit

Permalink
Merge pull request #508 from p1c2u/refactor/unmarshallers-format-vali…
Browse files Browse the repository at this point in the history
…dators-refactor-2

Unmarshallers and validators refactor
  • Loading branch information
p1c2u authored Feb 18, 2023
2 parents d22aaf9 + 9a57fd6 commit 25bc5f0
Show file tree
Hide file tree
Showing 93 changed files with 5,271 additions and 3,215 deletions.
55 changes: 33 additions & 22 deletions docs/customizations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,43 +42,54 @@ Pass custom defined media type deserializers dictionary with supported mimetypes
media_type_deserializers_factory=media_type_deserializers_factory,
)
Formats
-------
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-core comes with a set of built-in formatters, but it's also possible to add custom formatters in `SchemaUnmarshallersFactory` and pass it to `RequestValidator` or `ResponseValidator`.
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
from openapi_core.unmarshalling.schemas.factories import SchemaUnmarshallersFactory
from openapi_schema_validator import OAS30Validator
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 format(self, value):
return datetime.strptime(value, "%m/%d/%y").date
def validate_usdate(value):
return bool(re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", value))
custom_formatters = {
'usdate': USDateFormatter(),
extra_format_validators = {
'usdate': validate_usdate,
}
schema_unmarshallers_factory = SchemaUnmarshallersFactory(
OAS30Validator,
custom_formatters=custom_formatters,
context=ValidationContext.RESPONSE,
)
result = validate_response(
request, response,
spec=spec,
cls=ResponseValidator,
schema_unmarshallers_factory=schema_unmarshallers_factory,
extra_format_validators=extra_format_validators,
)
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
from datetime import datetime
def unmarshal_usdate(value):
return datetime.strptime(value, "%m/%d/%y").date
extra_format_unmarshallers = {
'usdate': unmarshal_usdate,
}
result = unmarshal_response(
request, response,
spec=spec,
extra_format_unmarshallers=extra_format_unmarshallers,
)
66 changes: 43 additions & 23 deletions openapi_core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
"""OpenAPI core module"""
from openapi_core.shortcuts import unmarshal_request
from openapi_core.shortcuts import unmarshal_response
from openapi_core.shortcuts import unmarshal_webhook_request
from openapi_core.shortcuts import unmarshal_webhook_response
from openapi_core.shortcuts import validate_request
from openapi_core.shortcuts import validate_response
from openapi_core.spec import Spec
from openapi_core.unmarshalling.request import RequestValidator
from openapi_core.unmarshalling.request import V3RequestUnmarshaller
from openapi_core.unmarshalling.request import V3WebhookRequestUnmarshaller
from openapi_core.unmarshalling.request import V30RequestUnmarshaller
from openapi_core.unmarshalling.request import V31RequestUnmarshaller
from openapi_core.unmarshalling.request import V31WebhookRequestUnmarshaller
from openapi_core.unmarshalling.request import openapi_request_validator
from openapi_core.unmarshalling.request import openapi_v3_request_validator
from openapi_core.unmarshalling.request import openapi_v30_request_validator
from openapi_core.unmarshalling.request import openapi_v31_request_validator
from openapi_core.unmarshalling.response import ResponseValidator
from openapi_core.unmarshalling.response import V3ResponseUnmarshaller
from openapi_core.unmarshalling.response import V3WebhookResponseUnmarshaller
from openapi_core.unmarshalling.response import V30ResponseUnmarshaller
from openapi_core.unmarshalling.response import V31ResponseUnmarshaller
from openapi_core.unmarshalling.response import V31WebhookResponseUnmarshaller
from openapi_core.unmarshalling.response import openapi_response_validator
from openapi_core.unmarshalling.response import openapi_v3_response_validator
from openapi_core.unmarshalling.response import openapi_v30_response_validator
from openapi_core.unmarshalling.response import openapi_v31_response_validator
from openapi_core.validation.request import V3RequestValidator
from openapi_core.validation.request import V3WebhookRequestValidator
from openapi_core.validation.request import V30RequestValidator
from openapi_core.validation.request import V31RequestValidator
from openapi_core.validation.request import V31WebhookRequestValidator
from openapi_core.validation.request import openapi_request_body_validator
from openapi_core.validation.request import (
openapi_request_parameters_validator,
)
from openapi_core.validation.request import openapi_request_security_validator
from openapi_core.validation.request import openapi_request_validator
from openapi_core.validation.request import openapi_v3_request_validator
from openapi_core.validation.request import openapi_v30_request_validator
from openapi_core.validation.request import openapi_v31_request_validator
from openapi_core.validation.response import V3ResponseValidator
from openapi_core.validation.response import V3WebhookResponseValidator
from openapi_core.validation.response import V30ResponseValidator
from openapi_core.validation.response import V31ResponseValidator
from openapi_core.validation.response import V31WebhookResponseValidator
from openapi_core.validation.response import openapi_response_data_validator
from openapi_core.validation.response import openapi_response_headers_validator
from openapi_core.validation.response import openapi_response_validator
from openapi_core.validation.response import openapi_v3_response_validator
from openapi_core.validation.response import openapi_v30_response_validator
from openapi_core.validation.response import openapi_v31_response_validator
from openapi_core.validation.shortcuts import validate_request
from openapi_core.validation.shortcuts import validate_response

__author__ = "Artur Maciag"
__email__ = "maciag.artur@gmail.com"
Expand All @@ -36,29 +45,40 @@

__all__ = [
"Spec",
"unmarshal_request",
"unmarshal_response",
"unmarshal_webhook_request",
"unmarshal_webhook_response",
"validate_request",
"validate_response",
"V30RequestUnmarshaller",
"V30ResponseUnmarshaller",
"V31RequestUnmarshaller",
"V31ResponseUnmarshaller",
"V31WebhookRequestUnmarshaller",
"V31WebhookResponseUnmarshaller",
"V3RequestUnmarshaller",
"V3ResponseUnmarshaller",
"V3WebhookRequestUnmarshaller",
"V3WebhookResponseUnmarshaller",
"V30RequestValidator",
"V31RequestValidator",
"V30ResponseValidator",
"V31RequestValidator",
"V31ResponseValidator",
"V31WebhookRequestValidator",
"V31WebhookResponseValidator",
"V3RequestValidator",
"V3ResponseValidator",
"V3WebhookRequestValidator",
"V3WebhookResponseValidator",
"RequestValidator",
"ResponseValidator",
"openapi_v3_request_validator",
"openapi_v30_request_validator",
"openapi_v31_request_validator",
"openapi_request_body_validator",
"openapi_request_parameters_validator",
"openapi_request_security_validator",
"openapi_request_validator",
"openapi_v3_response_validator",
"openapi_v30_response_validator",
"openapi_v31_response_validator",
"openapi_response_data_validator",
"openapi_response_headers_validator",
"openapi_response_validator",
]
18 changes: 10 additions & 8 deletions openapi_core/contrib/django/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler
from openapi_core.contrib.django.requests import DjangoOpenAPIRequest
from openapi_core.contrib.django.responses import DjangoOpenAPIResponse
from openapi_core.validation.processors import OpenAPIProcessor
from openapi_core.validation.request.datatypes import RequestValidationResult
from openapi_core.validation.response.datatypes import ResponseValidationResult
from openapi_core.unmarshalling.processors import UnmarshallingProcessor
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)


class DjangoOpenAPIMiddleware:
Expand All @@ -26,19 +28,19 @@ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
if not hasattr(settings, "OPENAPI_SPEC"):
raise ImproperlyConfigured("OPENAPI_SPEC not defined in settings")

self.validation_processor = OpenAPIProcessor(settings.OPENAPI_SPEC)
self.processor = UnmarshallingProcessor(settings.OPENAPI_SPEC)

def __call__(self, request: HttpRequest) -> HttpResponse:
openapi_request = self._get_openapi_request(request)
req_result = self.validation_processor.process_request(openapi_request)
req_result = self.processor.process_request(openapi_request)
if req_result.errors:
response = self._handle_request_errors(req_result, request)
else:
request.openapi = req_result
response = self.get_response(request)

openapi_response = self._get_openapi_response(response)
resp_result = self.validation_processor.process_response(
resp_result = self.processor.process_response(
openapi_request, openapi_response
)
if resp_result.errors:
Expand All @@ -47,13 +49,13 @@ def __call__(self, request: HttpRequest) -> HttpResponse:
return response

def _handle_request_errors(
self, request_result: RequestValidationResult, req: HttpRequest
self, request_result: RequestUnmarshalResult, req: HttpRequest
) -> JsonResponse:
return self.errors_handler.handle(request_result.errors, req, None)

def _handle_response_errors(
self,
response_result: ResponseValidationResult,
response_result: ResponseUnmarshalResult,
req: HttpRequest,
resp: HttpResponse,
) -> JsonResponse:
Expand Down
2 changes: 1 addition & 1 deletion openapi_core/contrib/django/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict

from openapi_core.validation.request.datatypes import RequestParameters
from openapi_core.datatypes import RequestParameters

# https://docs.djangoproject.com/en/stable/topics/http/urls/
#
Expand Down
34 changes: 18 additions & 16 deletions openapi_core/contrib/falcon/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,33 @@
from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest
from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse
from openapi_core.spec import Spec
from openapi_core.validation.processors import OpenAPIProcessor
from openapi_core.validation.request.datatypes import RequestValidationResult
from openapi_core.validation.request.protocols import RequestValidator
from openapi_core.validation.response.datatypes import ResponseValidationResult
from openapi_core.validation.response.protocols import ResponseValidator
from openapi_core.unmarshalling.processors import UnmarshallingProcessor
from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult
from openapi_core.unmarshalling.request.types import RequestUnmarshallerType
from openapi_core.unmarshalling.response.datatypes import (
ResponseUnmarshalResult,
)
from openapi_core.unmarshalling.response.types import ResponseUnmarshallerType


class FalconOpenAPIMiddleware(OpenAPIProcessor):
class FalconOpenAPIMiddleware(UnmarshallingProcessor):
request_class = FalconOpenAPIRequest
response_class = FalconOpenAPIResponse
errors_handler = FalconOpenAPIErrorsHandler()

def __init__(
self,
spec: Spec,
request_validator_cls: Optional[Type[RequestValidator]] = None,
response_validator_cls: Optional[Type[ResponseValidator]] = None,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
):
super().__init__(
spec,
request_validator_cls=request_validator_cls,
response_validator_cls=response_validator_cls,
request_unmarshaller_cls=request_unmarshaller_cls,
response_unmarshaller_cls=response_unmarshaller_cls,
)
self.request_class = request_class or self.request_class
self.response_class = response_class or self.response_class
Expand All @@ -44,16 +46,16 @@ def __init__(
def from_spec(
cls,
spec: Spec,
request_validator_cls: Optional[Type[RequestValidator]] = None,
response_validator_cls: Optional[Type[ResponseValidator]] = None,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
) -> "FalconOpenAPIMiddleware":
return cls(
spec,
request_validator_cls=request_validator_cls,
response_validator_cls=response_validator_cls,
request_unmarshaller_cls=request_unmarshaller_cls,
response_unmarshaller_cls=response_unmarshaller_cls,
request_class=request_class,
response_class=response_class,
errors_handler=errors_handler,
Expand Down Expand Up @@ -82,15 +84,15 @@ def _handle_request_errors(
self,
req: Request,
resp: Response,
request_result: RequestValidationResult,
request_result: RequestUnmarshalResult,
) -> None:
return self.errors_handler.handle(req, resp, request_result.errors)

def _handle_response_errors(
self,
req: Request,
resp: Response,
response_result: ResponseValidationResult,
response_result: ResponseUnmarshalResult,
) -> None:
return self.errors_handler.handle(req, resp, response_result.errors)

Expand Down
2 changes: 1 addition & 1 deletion openapi_core/contrib/falcon/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableMultiDict

from openapi_core.validation.request.datatypes import RequestParameters
from openapi_core.datatypes import RequestParameters


class FalconOpenAPIRequest:
Expand Down
Loading

0 comments on commit 25bc5f0

Please sign in to comment.