From dcd14cb586a854f0224c06d671d5b03ea5e490db Mon Sep 17 00:00:00 2001 From: p1c2u Date: Fri, 24 Mar 2023 15:17:51 +0000 Subject: [PATCH] AWS API Gateway with Amazon Lambda integrations support --- docs/integrations.rst | 53 +++ openapi_core/contrib/aws/__init__.py | 22 ++ openapi_core/contrib/aws/datatypes.py | 74 ++++ openapi_core/contrib/aws/finders.py | 18 + openapi_core/contrib/aws/requests.py | 151 ++++++++ openapi_core/contrib/aws/responses.py | 83 ++++ openapi_core/contrib/aws/types.py | 5 + openapi_core/templating/paths/__init__.py | 7 + openapi_core/templating/paths/finders.py | 148 ++------ openapi_core/templating/paths/iterators.py | 188 +++++++++ openapi_core/templating/paths/protocols.py | 47 +++ openapi_core/templating/paths/types.py | 5 + openapi_core/unmarshalling/processors.py | 10 +- .../unmarshalling/request/unmarshallers.py | 4 + openapi_core/unmarshalling/unmarshallers.py | 3 + openapi_core/validation/request/validators.py | 8 +- openapi_core/validation/validators.py | 21 +- poetry.lock | 356 ++++++++++++++++-- pyproject.toml | 6 + 19 files changed, 1042 insertions(+), 167 deletions(-) create mode 100644 openapi_core/contrib/aws/__init__.py create mode 100644 openapi_core/contrib/aws/datatypes.py create mode 100644 openapi_core/contrib/aws/finders.py create mode 100644 openapi_core/contrib/aws/requests.py create mode 100644 openapi_core/contrib/aws/responses.py create mode 100644 openapi_core/contrib/aws/types.py create mode 100644 openapi_core/templating/paths/iterators.py create mode 100644 openapi_core/templating/paths/protocols.py create mode 100644 openapi_core/templating/paths/types.py diff --git a/docs/integrations.rst b/docs/integrations.rst index 5beb7f26..acb8aef8 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -3,6 +3,59 @@ 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. +Amazon API Gateway +------------------ + +This section describes integration with `Amazon API Gateway `__. + +It is useful for: + * `AWS Lambda integrations `__ where Lambda functions handle events from API Gateway (Amazon API Gateway event format version 1.0 and 2.0). + * `AWS Lambda function URLs `__ where Lambda functions handle events from dedicated HTTP(S) endpoint (Amazon API Gateway event format version 2.0). + +Low level +~~~~~~~~~ + +You can use ``APIGatewayEventV2OpenAPIRequest`` as an API Gateway event (format version 2.0) request factory: + +.. code-block:: python + + from openapi_core import unmarshal_request + from openapi_core.contrib.aws import APIGatewayEventV2OpenAPIRequest + + openapi_request = APIGatewayEventV2OpenAPIRequest(event) + result = unmarshal_request(openapi_request, spec=spec) + +If you use format version 1.0, then import and use ``APIGatewayEventOpenAPIRequest`` as an API Gateway event (format version 1.0) request factory. + +You can use ``APIGatewayEventV2ResponseOpenAPIResponse`` as an API Gateway event (format version 2.0) response factory: + +.. code-block:: python + + from openapi_core import unmarshal_response + from openapi_core.contrib.aws import APIGatewayEventV2ResponseOpenAPIResponse + + openapi_response = APIGatewayEventV2ResponseOpenAPIResponse(response) + result = unmarshal_response(openapi_request, openapi_response, spec=spec) + +If you use format version 1.0, then import and use ``APIGatewayEventResponseOpenAPIResponse`` as an API Gateway event (format version 1.0) response factory. + +ANY method +~~~~~~~~~~ + +API Gateway have special ``ANY`` method that catches all HTTP methods. It's specified as `x-amazon-apigateway-any-method `__ OpenAPI extension. If you use the extension, you want to define ``path_finder_cls`` to be ``APIGatewayPathFinder``: + +.. code-block:: python + + from openapi_core.contrib.aws import APIGatewayPathFinder + + result = unmarshal_response( + openapi_request, + openapi_response, + spec=spec, + path_finder_cls=APIGatewayPathFinder, + ) + + Bottle ------ diff --git a/openapi_core/contrib/aws/__init__.py b/openapi_core/contrib/aws/__init__.py new file mode 100644 index 00000000..ed398a23 --- /dev/null +++ b/openapi_core/contrib/aws/__init__.py @@ -0,0 +1,22 @@ +"""OpenAPI core contrib aws module""" +from openapi_core.contrib.aws.decorators import ( + APIGatewayEventV2OpenAPIHandleDecorator, +) +from openapi_core.contrib.aws.finders import APIGatewayPathFinder +from openapi_core.contrib.aws.requests import APIGatewayEventOpenAPIRequest +from openapi_core.contrib.aws.requests import APIGatewayEventV2OpenAPIRequest +from openapi_core.contrib.aws.responses import ( + APIGatewayEventResponseOpenAPIResponse, +) +from openapi_core.contrib.aws.responses import ( + APIGatewayEventV2ResponseOpenAPIResponse, +) + +__all__ = [ + "APIGatewayEventOpenAPIRequest", + "APIGatewayEventResponseOpenAPIResponse", + "APIGatewayEventV2OpenAPIHandleDecorator", + "APIGatewayEventV2OpenAPIRequest", + "APIGatewayEventV2ResponseOpenAPIResponse", + "APIGatewayPathFinder", +] diff --git a/openapi_core/contrib/aws/datatypes.py b/openapi_core/contrib/aws/datatypes.py new file mode 100644 index 00000000..772fbc52 --- /dev/null +++ b/openapi_core/contrib/aws/datatypes.py @@ -0,0 +1,74 @@ +from typing import Dict +from typing import List +from typing import Optional + +from pydantic import Field +from pydantic.dataclasses import dataclass + + +class APIGatewayEventConfig: + extra = "allow" + + +@dataclass(config=APIGatewayEventConfig, frozen=True) +class APIGatewayEvent: + """AWS API Gateway event""" + + headers: Dict[str, str] + + path: str + httpMethod: str + resource: str + + queryStringParameters: Optional[Dict[str, str]] = None + isBase64Encoded: Optional[bool] = None + body: Optional[str] = None + pathParameters: Optional[Dict[str, str]] = None + stageVariables: Optional[Dict[str, str]] = None + + multiValueHeaders: Optional[Dict[str, List[str]]] = None + version: Optional[str] = "1.0" + multiValueQueryStringParameters: Optional[Dict[str, List[str]]] = None + + +@dataclass(config=APIGatewayEventConfig, frozen=True) +class APIGatewayEventV2: + """AWS API Gateway event v2""" + + headers: Dict[str, str] + + version: str + routeKey: str + rawPath: str + rawQueryString: str + + queryStringParameters: Optional[Dict[str, str]] = None + isBase64Encoded: Optional[bool] = None + body: Optional[str] = None + pathParameters: Optional[Dict[str, str]] = None + stageVariables: Optional[Dict[str, str]] = None + + cookies: Optional[List[str]] = None + + +@dataclass(config=APIGatewayEventConfig, frozen=True) +class APIGatewayEventResponse: + """AWS API Gateway event response""" + + body: str + isBase64Encoded: bool + statusCode: int + headers: Dict[str, str] + multiValueHeaders: Dict[str, List[str]] + + +@dataclass(config=APIGatewayEventConfig, frozen=True) +class APIGatewayEventV2Response: + """AWS API Gateway event v2 response""" + + body: str + isBase64Encoded: bool = False + statusCode: int = 200 + headers: Dict[str, str] = Field( + default_factory=lambda: {"content-type": "application/json"} + ) diff --git a/openapi_core/contrib/aws/finders.py b/openapi_core/contrib/aws/finders.py new file mode 100644 index 00000000..15dda3eb --- /dev/null +++ b/openapi_core/contrib/aws/finders.py @@ -0,0 +1,18 @@ +from openapi_core.templating.paths.finders import APICallPathFinder +from openapi_core.templating.paths.iterators import ( + CatchAllMethodOperationsIterator, +) + + +class APIGatewayPathFinder(APICallPathFinder): + operations_iterator = CatchAllMethodOperationsIterator( + "any", + "x-amazon-apigateway-any-method", + ) + + +class APIGatewayIntegrationPathFinder(APICallPathFinder): + operations_iterator = CatchAllMethodOperationsIterator( + "any", + "x-amazon-apigateway-any-method", + ) diff --git a/openapi_core/contrib/aws/requests.py b/openapi_core/contrib/aws/requests.py new file mode 100644 index 00000000..8b7400be --- /dev/null +++ b/openapi_core/contrib/aws/requests.py @@ -0,0 +1,151 @@ +from typing import Dict +from typing import Optional + +from werkzeug.datastructures import Headers +from werkzeug.datastructures import ImmutableMultiDict + +from openapi_core.contrib.aws.datatypes import APIGatewayEvent +from openapi_core.contrib.aws.datatypes import APIGatewayEventV2 +from openapi_core.contrib.aws.types import APIGatewayEventPayload +from openapi_core.datatypes import RequestParameters + + +class APIGatewayEventOpenAPIRequest: + """ + Converts an API Gateway event payload to an OpenAPI request. + + Designed to be used with API Gateway REST API specification exports for + integrations that use event v1 payload. Uses API Gateway event v1 httpMethod + and path data. Requires APIGatewayPathFinder to resolve ANY methods. + """ + + def __init__(self, payload: APIGatewayEventPayload): + self.event = APIGatewayEvent(**payload) + + self.parameters = RequestParameters( + path=self.path_params, + query=ImmutableMultiDict(self.query_params), + header=Headers(self.event.headers), + cookie=ImmutableMultiDict(), + ) + + @property + def path_params(self) -> Dict[str, str]: + params = self.event.pathParameters + if params is None: + return {} + return params + + @property + def query_params(self) -> Dict[str, str]: + params = self.event.queryStringParameters + if params is None: + return {} + return params + + @property + def proto(self) -> str: + return self.event.headers.get("X-Forwarded-Proto", "https") + + @property + def host(self) -> str: + return self.event.headers["Host"] + + @property + def host_url(self) -> str: + return "://".join([self.proto, self.host]) + + @property + def path(self) -> str: + return self.event.path + + @property + def method(self) -> str: + return self.event.httpMethod.lower() + + @property + def body(self) -> Optional[str]: + return self.event.body + + @property + def mimetype(self) -> str: + return self.event.headers.get("Content-Type", "") + + +class APIGatewayEventV2OpenAPIRequest: + """ + Converts an API Gateway event v2 payload to an OpenAPI request. + + Designed to be used with API Gateway HTTP API specification exports for + integrations that use event v2 payload. Uses API Gateway event v2 routeKey + and rawPath data. Requires APIGatewayPathFinder to resolve ANY methods. + + .. note:: + API Gateway HTTP APIs don't support request validation + """ + + def __init__(self, payload: APIGatewayEventPayload): + self.event = APIGatewayEventV2(**payload) + + self.parameters = RequestParameters( + path=self.path_params, + query=ImmutableMultiDict(self.query_params), + header=Headers(self.event.headers), + cookie=ImmutableMultiDict(), + ) + + @property + def path_params(self) -> Dict[str, str]: + if self.event.pathParameters is None: + return {} + return self.event.pathParameters + + @property + def query_params(self) -> Dict[str, str]: + if self.event.queryStringParameters is None: + return {} + return self.event.queryStringParameters + + @property + def proto(self) -> str: + return self.event.headers.get("x-forwarded-proto", "https") + + @property + def host(self) -> str: + return self.event.headers["host"] + + @property + def host_url(self) -> str: + return "://".join([self.proto, self.host]) + + @property + def path(self) -> str: + return self.event.rawPath + + @property + def method(self) -> str: + return self.event.routeKey.split(" ")[0].lower() + + @property + def body(self) -> Optional[str]: + return self.event.body + + @property + def mimetype(self) -> str: + return self.event.headers.get("content-type", "") + + +class APIGatewayEventV2HTTPOpenAPIRequest(APIGatewayEventV2OpenAPIRequest): + """ + Converts an API Gateway event v2 payload to an OpenAPI request. + + Uses http integration path and method data. + """ + + @property + def path(self) -> str: + return self.event.http.path + + @property + def method(self) -> str: + return self.event.http.method.lower() diff --git a/openapi_core/contrib/aws/responses.py b/openapi_core/contrib/aws/responses.py new file mode 100644 index 00000000..0c19510b --- /dev/null +++ b/openapi_core/contrib/aws/responses.py @@ -0,0 +1,83 @@ +from json import dumps +from typing import Union + +from werkzeug.datastructures import Headers + +from openapi_core.contrib.aws.datatypes import APIGatewayEventResponse +from openapi_core.contrib.aws.datatypes import APIGatewayEventV2Response +from openapi_core.contrib.aws.types import APIGatewayEventResponsePayload + +APIGatewayEventV2ResponseType = Union[APIGatewayEventV2Response, dict, str] + + +class APIGatewayEventResponseOpenAPIResponse: + """ + Converts an API Gateway event response payload to an OpenAPI request + """ + + def __init__(self, payload: APIGatewayEventResponsePayload): + self.response = APIGatewayEventResponse(**payload) + + @property + def data(self) -> str: + return self.response.body + + @property + def status_code(self) -> int: + return self.response.statusCode + + @property + def headers(self) -> Headers: + return Headers(self.response.headers) + + @property + def mimetype(self) -> str: + content_type = self.response.headers.get("Content-Type", "") + assert isinstance(content_type, str) + return content_type + + +class APIGatewayEventV2ResponseOpenAPIResponse: + """ + Converts an API Gateway event v2 response payload to an OpenAPI request + """ + + def __init__(self, payload: Union[APIGatewayEventResponsePayload, str]): + if not isinstance(payload, dict): + payload = self._construct_payload(payload) + elif "statusCode" not in payload: + body = dumps(payload) + payload = self._construct_payload(body) + + self.response = APIGatewayEventV2Response(**payload) + + @staticmethod + def _construct_payload(body: str) -> APIGatewayEventResponsePayload: + return { + "isBase64Encoded": False, + "statusCode": 200, + "headers": { + "content-type": "application/json", + }, + "body": body, + } + + @property + def data(self) -> str: + return self.response.body + + @property + def status_code(self) -> int: + return self.response.statusCode + + @property + def headers(self) -> Headers: + return Headers(self.response.headers) + + @property + def mimetype(self) -> str: + content_type = self.response.headers.get( + "content-type", "application/json" + ) + assert isinstance(content_type, str) + return content_type diff --git a/openapi_core/contrib/aws/types.py b/openapi_core/contrib/aws/types.py new file mode 100644 index 00000000..aa5a6b2e --- /dev/null +++ b/openapi_core/contrib/aws/types.py @@ -0,0 +1,5 @@ +from typing import Any +from typing import Dict + +APIGatewayEventPayload = Dict[str, Any] +APIGatewayEventResponsePayload = Dict[str, Any] diff --git a/openapi_core/templating/paths/__init__.py b/openapi_core/templating/paths/__init__.py index e69de29b..93e94f74 100644 --- a/openapi_core/templating/paths/__init__.py +++ b/openapi_core/templating/paths/__init__.py @@ -0,0 +1,7 @@ +from openapi_core.templating.paths.finders import APICallPathFinder +from openapi_core.templating.paths.finders import WebhookPathFinder + +__all__ = [ + "APICallPathFinder", + "WebhookPathFinder", +] diff --git a/openapi_core/templating/paths/finders.py b/openapi_core/templating/paths/finders.py index e6f70841..74c0e5e0 100644 --- a/openapi_core/templating/paths/finders.py +++ b/openapi_core/templating/paths/finders.py @@ -17,32 +17,52 @@ from openapi_core.templating.paths.exceptions import PathNotFound from openapi_core.templating.paths.exceptions import PathsNotFound from openapi_core.templating.paths.exceptions import ServerNotFound +from openapi_core.templating.paths.iterators import SimpleOperationsIterator +from openapi_core.templating.paths.iterators import SimplePathsIterator +from openapi_core.templating.paths.iterators import SimpleServersIterator +from openapi_core.templating.paths.iterators import TemplatePathsIterator +from openapi_core.templating.paths.iterators import TemplateServersIterator +from openapi_core.templating.paths.protocols import OperationsIterator +from openapi_core.templating.paths.protocols import PathsIterator +from openapi_core.templating.paths.protocols import ServersIterator from openapi_core.templating.paths.util import template_path_len from openapi_core.templating.util import parse from openapi_core.templating.util import search -class BasePathFinder: +class PathFinder: + paths_iterator: PathsIterator = NotImplemented + operations_iterator: OperationsIterator = NotImplemented + servers_iterator: ServersIterator = NotImplemented + def __init__(self, spec: Spec, base_url: Optional[str] = None): self.spec = spec self.base_url = base_url def find(self, method: str, name: str) -> PathOperationServer: - paths_iter = self._get_paths_iter(name) + paths_iter = self.paths_iterator( + name, + self.spec, + base_url=self.base_url, + ) paths_iter_peek = peekable(paths_iter) if not paths_iter_peek: raise PathNotFound(name) - operations_iter = self._get_operations_iter(method, paths_iter_peek) + operations_iter = self.operations_iterator( + method, + paths_iter_peek, + self.spec, + base_url=self.base_url, + ) operations_iter_peek = peekable(operations_iter) if not operations_iter_peek: raise OperationNotFound(name, method) - servers_iter = self._get_servers_iter( - name, - operations_iter_peek, + servers_iter = self.servers_iterator( + name, operations_iter_peek, self.spec, base_url=self.base_url ) try: @@ -50,115 +70,13 @@ def find(self, method: str, name: str) -> PathOperationServer: except StopIteration: raise ServerNotFound(name) - def _get_paths_iter(self, name: str) -> Iterator[Path]: - raise NotImplementedError - - def _get_operations_iter( - self, method: str, paths_iter: Iterator[Path] - ) -> Iterator[PathOperation]: - for path, path_result in paths_iter: - if method not in path: - continue - operation = path / method - yield PathOperation(path, operation, path_result) - - def _get_servers_iter( - self, name: str, operations_iter: Iterator[PathOperation] - ) -> Iterator[PathOperationServer]: - raise NotImplementedError - - -class APICallPathFinder(BasePathFinder): - def __init__(self, spec: Spec, base_url: Optional[str] = None): - self.spec = spec - self.base_url = base_url - - def _get_paths_iter(self, name: str) -> Iterator[Path]: - paths = self.spec / "paths" - if not paths.exists(): - raise PathsNotFound(paths.uri()) - template_paths: List[Path] = [] - for path_pattern, path in list(paths.items()): - # simple path. - # Return right away since it is always the most concrete - if name.endswith(path_pattern): - path_result = TemplateResult(path_pattern, {}) - yield Path(path, path_result) - # template path - else: - result = search(path_pattern, name) - if result: - path_result = TemplateResult(path_pattern, result.named) - template_paths.append(Path(path, path_result)) - - # Fewer variables -> more concrete path - yield from sorted(template_paths, key=template_path_len) - - def _get_servers_iter( - self, name: str, operations_iter: Iterator[PathOperation] - ) -> Iterator[PathOperationServer]: - for path, operation, path_result in operations_iter: - servers = ( - path.get("servers", None) - or operation.get("servers", None) - or self.spec.get("servers", [{"url": "/"}]) - ) - for server in servers: - server_url_pattern = name.rsplit(path_result.resolved, 1)[0] - server_url = server["url"] - if not is_absolute(server_url): - # relative to absolute url - if self.base_url is not None: - server_url = urljoin(self.base_url, server["url"]) - # if no base url check only path part - else: - server_url_pattern = urlparse(server_url_pattern).path - if server_url.endswith("/"): - server_url = server_url[:-1] - # simple path - if server_url_pattern == server_url: - server_result = TemplateResult(server["url"], {}) - yield PathOperationServer( - path, - operation, - server, - path_result, - server_result, - ) - # template path - else: - result = parse(server["url"], server_url_pattern) - if result: - server_result = TemplateResult( - server["url"], result.named - ) - yield PathOperationServer( - path, - operation, - server, - path_result, - server_result, - ) +class APICallPathFinder(PathFinder): + paths_iterator: PathsIterator = TemplatePathsIterator("paths") + operations_iterator: OperationsIterator = SimpleOperationsIterator() + servers_iterator: ServersIterator = TemplateServersIterator() -class WebhookPathFinder(BasePathFinder): - def _get_paths_iter(self, name: str) -> Iterator[Path]: - webhooks = self.spec / "webhooks" - if not webhooks.exists(): - raise PathsNotFound(webhooks.uri()) - for webhook_name, path in list(webhooks.items()): - if name == webhook_name: - path_result = TemplateResult(webhook_name, {}) - yield Path(path, path_result) - def _get_servers_iter( - self, name: str, operations_iter: Iterator[PathOperation] - ) -> Iterator[PathOperationServer]: - for path, operation, path_result in operations_iter: - yield PathOperationServer( - path, - operation, - None, - path_result, - {}, - ) +class WebhookPathFinder(APICallPathFinder): + paths_iterator = SimplePathsIterator("webhooks") + servers_iterator = SimpleServersIterator() diff --git a/openapi_core/templating/paths/iterators.py b/openapi_core/templating/paths/iterators.py new file mode 100644 index 00000000..5e22145f --- /dev/null +++ b/openapi_core/templating/paths/iterators.py @@ -0,0 +1,188 @@ +from itertools import tee +from typing import Iterator +from typing import List +from typing import Optional +from urllib.parse import urljoin +from urllib.parse import urlparse + +from more_itertools import peekable + +from openapi_core.schema.servers import is_absolute +from openapi_core.spec import Spec +from openapi_core.templating.datatypes import TemplateResult +from openapi_core.templating.paths.datatypes import Path +from openapi_core.templating.paths.datatypes import PathOperation +from openapi_core.templating.paths.datatypes import PathOperationServer +from openapi_core.templating.paths.exceptions import OperationNotFound +from openapi_core.templating.paths.exceptions import PathNotFound +from openapi_core.templating.paths.exceptions import PathsNotFound +from openapi_core.templating.paths.exceptions import ServerNotFound +from openapi_core.templating.paths.util import template_path_len +from openapi_core.templating.util import parse +from openapi_core.templating.util import search + + +class SimplePathsIterator: + def __init__(self, paths_part: str): + self.paths_part = paths_part + + def __call__( + self, name: str, spec: Spec, base_url: Optional[str] = None + ) -> Iterator[Path]: + paths = spec / self.paths_part + if not paths.exists(): + raise PathsNotFound(paths.uri()) + for path_name, path in list(paths.items()): + if name == path_name: + path_result = TemplateResult(path_name, {}) + yield Path(path, path_result) + + +class TemplatePathsIterator: + def __init__(self, paths_part: str): + self.paths_part = paths_part + + def __call__( + self, name: str, spec: Spec, base_url: Optional[str] = None + ) -> Iterator[Path]: + paths = spec / self.paths_part + if not paths.exists(): + raise PathsNotFound(paths.uri()) + template_paths: List[Path] = [] + for path_pattern, path in list(paths.items()): + # simple path. + # Return right away since it is always the most concrete + if name.endswith(path_pattern): + path_result = TemplateResult(path_pattern, {}) + yield Path(path, path_result) + # template path + else: + result = search(path_pattern, name) + if result: + path_result = TemplateResult(path_pattern, result.named) + template_paths.append(Path(path, path_result)) + + # Fewer variables -> more concrete path + yield from sorted(template_paths, key=template_path_len) + + +class SimpleOperationsIterator: + def __call__( + self, + method: str, + paths_iter: Iterator[Path], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperation]: + for path, path_result in paths_iter: + if method not in path: + continue + operation = path / method + yield PathOperation(path, operation, path_result) + + +class CatchAllMethodOperationsIterator(SimpleOperationsIterator): + def __init__(self, ca_method_name: str, ca_operation_name: str): + self.ca_method_name = ca_method_name + self.ca_operation_name = ca_operation_name + + def __call__( + self, + method: str, + paths_iter: Iterator[Path], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperation]: + if method == self.ca_method_name: + yield from super().__call__( + self.ca_operation_name, paths_iter, spec, base_url=base_url + ) + else: + yield from super().__call__( + method, paths_iter, spec, base_url=base_url + ) + + +class SimpleServersIterator: + def __call__( + self, + name: str, + operations_iter: Iterator[PathOperation], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperationServer]: + for path, operation, path_result in operations_iter: + yield PathOperationServer( + path, + operation, + None, + path_result, + {}, + ) + + +class TemplateServersIterator: + def __call__( + self, + name: str, + operations_iter: Iterator[PathOperation], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperationServer]: + for path, operation, path_result in operations_iter: + servers = ( + path.get("servers", None) + or operation.get("servers", None) + or spec.get("servers", [{"url": "/"}]) + ) + for server in servers: + server_url_pattern = name.rsplit(path_result.resolved, 1)[0] + server_url = server["url"] + if not is_absolute(server_url): + # relative to absolute url + if base_url is not None: + server_url = urljoin(base_url, server["url"]) + # if no base url check only path part + else: + server_url_pattern = urlparse(server_url_pattern).path + if server_url.endswith("/"): + server_url = server_url[:-1] + # simple path + if server_url_pattern == server_url: + server_result = TemplateResult(server["url"], {}) + yield PathOperationServer( + path, + operation, + server, + path_result, + server_result, + ) + # template path + else: + result = parse(server["url"], server_url_pattern) + if result: + server_result = TemplateResult( + server["url"], result.named + ) + yield PathOperationServer( + path, + operation, + server, + path_result, + server_result, + ) + # servers should'n end with tailing slash + # but let's search for this too + server_url_pattern += "/" + result = parse(server["url"], server_url_pattern) + if result: + server_result = TemplateResult( + server["url"], result.named + ) + yield PathOperationServer( + path, + operation, + server, + path_result, + server_result, + ) diff --git a/openapi_core/templating/paths/protocols.py b/openapi_core/templating/paths/protocols.py new file mode 100644 index 00000000..bfc72b06 --- /dev/null +++ b/openapi_core/templating/paths/protocols.py @@ -0,0 +1,47 @@ +import sys +from typing import Iterator +from typing import Optional + +if sys.version_info >= (3, 8): + from typing import Protocol + from typing import runtime_checkable +else: + from typing_extensions import Protocol + from typing_extensions import runtime_checkable + +from openapi_core.spec import Spec +from openapi_core.templating.paths.datatypes import Path +from openapi_core.templating.paths.datatypes import PathOperation +from openapi_core.templating.paths.datatypes import PathOperationServer + + +@runtime_checkable +class PathsIterator(Protocol): + def __call__( + self, name: str, spec: Spec, base_url: Optional[str] = None + ) -> Iterator[Path]: + ... + + +@runtime_checkable +class OperationsIterator(Protocol): + def __call__( + self, + method: str, + paths_iter: Iterator[Path], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperation]: + ... + + +@runtime_checkable +class ServersIterator(Protocol): + def __call__( + self, + name: str, + operations_iter: Iterator[PathOperation], + spec: Spec, + base_url: Optional[str] = None, + ) -> Iterator[PathOperationServer]: + ... diff --git a/openapi_core/templating/paths/types.py b/openapi_core/templating/paths/types.py new file mode 100644 index 00000000..201fc40f --- /dev/null +++ b/openapi_core/templating/paths/types.py @@ -0,0 +1,5 @@ +from typing import Type + +from openapi_core.templating.paths.finders import PathFinder + +PathFinderType = Type[PathFinder] diff --git a/openapi_core/unmarshalling/processors.py b/openapi_core/unmarshalling/processors.py index b2200a90..167b62bb 100644 --- a/openapi_core/unmarshalling/processors.py +++ b/openapi_core/unmarshalling/processors.py @@ -1,4 +1,5 @@ """OpenAPI core unmarshalling processors module""" +from typing import Any from typing import Optional from typing import Type @@ -20,6 +21,7 @@ def __init__( spec: Spec, request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None, response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None, + **unmarshallers_kwargs: Any, ): self.spec = spec if ( @@ -31,8 +33,12 @@ def __init__( request_unmarshaller_cls = classes.request_unmarshaller_cls if response_unmarshaller_cls is None: response_unmarshaller_cls = classes.response_unmarshaller_cls - self.request_unmarshaller = request_unmarshaller_cls(self.spec) - self.response_unmarshaller = response_unmarshaller_cls(self.spec) + self.request_unmarshaller = request_unmarshaller_cls( + self.spec, **unmarshallers_kwargs + ) + self.response_unmarshaller = response_unmarshaller_cls( + self.spec, **unmarshallers_kwargs + ) def process_request(self, request: Request) -> RequestUnmarshalResult: return self.request_unmarshaller.unmarshal(request) diff --git a/openapi_core/unmarshalling/request/unmarshallers.py b/openapi_core/unmarshalling/request/unmarshallers.py index 96b0b76e..7bd87a25 100644 --- a/openapi_core/unmarshalling/request/unmarshallers.py +++ b/openapi_core/unmarshalling/request/unmarshallers.py @@ -25,6 +25,7 @@ from openapi_core.security.factories import SecurityProviderFactory from openapi_core.spec import Spec from openapi_core.templating.paths.exceptions import PathError +from openapi_core.templating.paths.types import PathFinderType from openapi_core.unmarshalling.request.datatypes import RequestUnmarshalResult from openapi_core.unmarshalling.request.proxies import ( SpecRequestValidatorProxy, @@ -92,6 +93,7 @@ def __init__( schema_casters_factory: SchemaCastersFactory = schema_casters_factory, parameter_deserializers_factory: ParameterDeserializersFactory = parameter_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + path_finder_cls: Optional[PathFinderType] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, @@ -112,6 +114,7 @@ def __init__( schema_casters_factory=schema_casters_factory, parameter_deserializers_factory=parameter_deserializers_factory, media_type_deserializers_factory=media_type_deserializers_factory, + path_finder_cls=path_finder_cls, schema_validators_factory=schema_validators_factory, format_validators=format_validators, extra_format_validators=extra_format_validators, @@ -127,6 +130,7 @@ def __init__( schema_casters_factory=schema_casters_factory, parameter_deserializers_factory=parameter_deserializers_factory, media_type_deserializers_factory=media_type_deserializers_factory, + path_finder_cls=path_finder_cls, schema_validators_factory=schema_validators_factory, format_validators=format_validators, extra_format_validators=extra_format_validators, diff --git a/openapi_core/unmarshalling/unmarshallers.py b/openapi_core/unmarshalling/unmarshallers.py index af857906..cbcaebca 100644 --- a/openapi_core/unmarshalling/unmarshallers.py +++ b/openapi_core/unmarshalling/unmarshallers.py @@ -21,6 +21,7 @@ ParameterDeserializersFactory, ) from openapi_core.spec import Spec +from openapi_core.templating.paths.types import PathFinderType from openapi_core.unmarshalling.schemas.datatypes import ( FormatUnmarshallersDict, ) @@ -42,6 +43,7 @@ def __init__( schema_casters_factory: SchemaCastersFactory = schema_casters_factory, parameter_deserializers_factory: ParameterDeserializersFactory = parameter_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + path_finder_cls: Optional[PathFinderType] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, @@ -64,6 +66,7 @@ def __init__( schema_casters_factory=schema_casters_factory, parameter_deserializers_factory=parameter_deserializers_factory, media_type_deserializers_factory=media_type_deserializers_factory, + path_finder_cls=path_finder_cls, schema_validators_factory=schema_validators_factory, format_validators=format_validators, extra_format_validators=extra_format_validators, diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index d0bf3609..994f7cdc 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -33,6 +33,7 @@ from openapi_core.spec.paths import Spec from openapi_core.templating.paths.exceptions import PathError from openapi_core.templating.paths.finders import WebhookPathFinder +from openapi_core.templating.paths.types import PathFinderType from openapi_core.templating.security.exceptions import SecurityNotFound from openapi_core.util import chainiters from openapi_core.validation.decorators import ValidationErrorWrapper @@ -70,6 +71,7 @@ def __init__( schema_casters_factory: SchemaCastersFactory = schema_casters_factory, parameter_deserializers_factory: ParameterDeserializersFactory = parameter_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + path_finder_cls: Optional[PathFinderType] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, @@ -84,6 +86,7 @@ def __init__( schema_casters_factory=schema_casters_factory, parameter_deserializers_factory=parameter_deserializers_factory, media_type_deserializers_factory=media_type_deserializers_factory, + path_finder_cls=path_finder_cls, schema_validators_factory=schema_validators_factory, format_validators=format_validators, extra_format_validators=extra_format_validators, @@ -414,24 +417,19 @@ class V31RequestSecurityValidator(APICallRequestSecurityValidator): class V31RequestValidator(APICallRequestValidator): schema_validators_factory = oas31_schema_validators_factory - path_finder_cls = WebhookPathFinder class V31WebhookRequestBodyValidator(WebhookRequestBodyValidator): schema_validators_factory = oas31_schema_validators_factory - path_finder_cls = WebhookPathFinder class V31WebhookRequestParametersValidator(WebhookRequestParametersValidator): schema_validators_factory = oas31_schema_validators_factory - path_finder_cls = WebhookPathFinder class V31WebhookRequestSecurityValidator(WebhookRequestSecurityValidator): schema_validators_factory = oas31_schema_validators_factory - path_finder_cls = WebhookPathFinder class V31WebhookRequestValidator(WebhookRequestValidator): schema_validators_factory = oas31_schema_validators_factory - path_finder_cls = WebhookPathFinder diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index b307d97c..f4a4569b 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -4,6 +4,7 @@ from typing import Mapping from typing import Optional from typing import Tuple +from typing import Type from urllib.parse import urljoin if sys.version_info >= (3, 8): @@ -34,13 +35,15 @@ from openapi_core.templating.media_types.datatypes import MediaType from openapi_core.templating.paths.datatypes import PathOperationServer from openapi_core.templating.paths.finders import APICallPathFinder -from openapi_core.templating.paths.finders import BasePathFinder +from openapi_core.templating.paths.finders import PathFinder from openapi_core.templating.paths.finders import WebhookPathFinder +from openapi_core.templating.paths.types import PathFinderType from openapi_core.validation.schemas.datatypes import FormatValidatorsDict from openapi_core.validation.schemas.factories import SchemaValidatorsFactory class BaseValidator: + path_finder_cls: PathFinderType = NotImplemented schema_validators_factory: SchemaValidatorsFactory = NotImplemented def __init__( @@ -50,6 +53,7 @@ def __init__( schema_casters_factory: SchemaCastersFactory = schema_casters_factory, parameter_deserializers_factory: ParameterDeserializersFactory = parameter_deserializers_factory, media_type_deserializers_factory: MediaTypeDeserializersFactory = media_type_deserializers_factory, + path_finder_cls: Optional[PathFinderType] = None, schema_validators_factory: Optional[SchemaValidatorsFactory] = None, format_validators: Optional[FormatValidatorsDict] = None, extra_format_validators: Optional[FormatValidatorsDict] = None, @@ -65,6 +69,9 @@ def __init__( self.media_type_deserializers_factory = ( media_type_deserializers_factory ) + self.path_finder_cls = path_finder_cls or self.path_finder_cls + if self.path_finder_cls is NotImplemented: # type: ignore[comparison-overlap] + raise NotImplementedError("path_finder_cls is not assigned") self.schema_validators_factory = ( schema_validators_factory or self.schema_validators_factory ) @@ -76,6 +83,10 @@ def __init__( self.extra_format_validators = extra_format_validators self.extra_media_type_deserializers = extra_media_type_deserializers + @cached_property + def path_finder(self) -> PathFinder: + return self.path_finder_cls(self.spec, base_url=self.base_url) + def _get_media_type(self, content: Spec, mimetype: str) -> MediaType: from openapi_core.templating.media_types.finders import MediaTypeFinder @@ -176,9 +187,7 @@ def _get_content_value_and_schema( class BaseAPICallValidator(BaseValidator): - @cached_property - def path_finder(self) -> BasePathFinder: - return APICallPathFinder(self.spec, base_url=self.base_url) + path_finder_cls = APICallPathFinder def _find_path(self, request: Request) -> PathOperationServer: path_pattern = getattr(request, "path_pattern", None) or request.path @@ -187,9 +196,7 @@ def _find_path(self, request: Request) -> PathOperationServer: class BaseWebhookValidator(BaseValidator): - @cached_property - def path_finder(self) -> BasePathFinder: - return WebhookPathFinder(self.spec, base_url=self.base_url) + path_finder_cls = WebhookPathFinder def _find_path(self, request: WebhookRequest) -> PathOperationServer: return self.path_finder.find(request.method, request.name) diff --git a/poetry.lock b/poetry.lock index 2a0edf65..fc7e7beb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,6 +161,46 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "boto3" +version = "1.26.96" +description = "The AWS SDK for Python" +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.26.96-py3-none-any.whl", hash = "sha256:f961aa704bd7aeefc186ede52cabc3ef4c336979bb4098d3aad7ca922d55fc27"}, + {file = "boto3-1.26.96.tar.gz", hash = "sha256:7017102c58b9984749bef3b9f476940593c311504354b9ee9dd7bb0b4657a77d"}, +] + +[package.dependencies] +botocore = ">=1.29.96,<1.30.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.29.96" +description = "Low-level, data-driven core of boto 3." +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.29.96-py3-none-any.whl", hash = "sha256:c449d7050e9bc4a8b8a62ae492cbdc931b786bf5752b792867f1276967fadaed"}, + {file = "botocore-1.29.96.tar.gz", hash = "sha256:b9781108810e33f8406942c3e3aab748650c59d5cddb7c9d323f4e2682e7b0b6"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.16.9)"] + [[package]] name = "certifi" version = "2022.12.7" @@ -173,6 +213,83 @@ files = [ {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "cfgv" version = "3.3.1" @@ -365,6 +482,52 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "39.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cryptography-39.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06"}, + {file = "cryptography-39.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011"}, + {file = "cryptography-39.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536"}, + {file = "cryptography-39.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5"}, + {file = "cryptography-39.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0"}, + {file = "cryptography-39.0.2-cp36-abi3-win32.whl", hash = "sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480"}, + {file = "cryptography-39.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9"}, + {file = "cryptography-39.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac"}, + {file = "cryptography-39.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074"}, + {file = "cryptography-39.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1"}, + {file = "cryptography-39.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3"}, + {file = "cryptography-39.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3"}, + {file = "cryptography-39.0.2.tar.gz", hash = "sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] +test-randomorder = ["pytest-randomly"] +tox = ["tox"] + [[package]] name = "distlib" version = "0.3.6" @@ -755,6 +918,18 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "jsonschema" version = "4.17.3" @@ -927,6 +1102,54 @@ files = [ {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, ] +[[package]] +name = "moto" +version = "4.1.5" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "moto-4.1.5-py2.py3-none-any.whl", hash = "sha256:363577f7a0cdf639852420f6ba5caa9aa3c90a688feae6315f8ee4bf324b8c27"}, + {file = "moto-4.1.5.tar.gz", hash = "sha256:63542b7b9f307b00fae460b42d15cf9346de3ad3b1287fba38fc68f3c05e4da4"}, +] + +[package.dependencies] +boto3 = ">=1.9.201" +botocore = ">=1.12.201" +cryptography = ">=3.3.1" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +Jinja2 = ">=2.10.1" +python-dateutil = ">=2.1,<3.0.0" +requests = ">=2.5" +responses = ">=0.13.0" +werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" +xmltodict = "*" + +[package.extras] +all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.2.8)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] +apigatewayv2 = ["PyYAML (>=5.1)"] +appsync = ["graphql-core"] +awslambda = ["docker (>=3.0.0)"] +batch = ["docker (>=3.0.0)"] +cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] +ds = ["sshpubkeys (>=3.1.0)"] +dynamodb = ["docker (>=3.0.0)"] +dynamodbstreams = ["docker (>=3.0.0)"] +ebs = ["sshpubkeys (>=3.1.0)"] +ec2 = ["sshpubkeys (>=3.1.0)"] +efs = ["sshpubkeys (>=3.1.0)"] +eks = ["sshpubkeys (>=3.1.0)"] +glue = ["pyparsing (>=3.0.7)"] +iotdata = ["jsondiff (>=1.1.2)"] +route53resolver = ["sshpubkeys (>=3.1.0)"] +s3 = ["PyYAML (>=5.1)"] +server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +ssm = ["PyYAML (>=5.1)"] +xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] + [[package]] name = "mypy" version = "1.1.1" @@ -1169,50 +1392,62 @@ files = [ {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + [[package]] name = "pydantic" -version = "1.10.5" +version = "1.10.7" description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb"}, - {file = "pydantic-1.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3bb99cf9655b377db1a9e47fa4479e3330ea96f4123c6c8200e482704bf1eda2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2185a3b3d98ab4506a3f6707569802d2d92c3a7ba3a9a35683a7709ea6c2aaa2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f582cac9d11c227c652d3ce8ee223d94eb06f4228b52a8adaafa9fa62e73d5c9"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c9e5b778b6842f135902e2d82624008c6a79710207e28e86966cd136c621bfee"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72ef3783be8cbdef6bca034606a5de3862be6b72415dc5cb1fb8ddbac110049a"}, - {file = "pydantic-1.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:45edea10b75d3da43cfda12f3792833a3fa70b6eee4db1ed6aed528cef17c74e"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63200cd8af1af2c07964546b7bc8f217e8bda9d0a2ef0ee0c797b36353914984"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:305d0376c516b0dfa1dbefeae8c21042b57b496892d721905a6ec6b79494a66d"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd326aff5d6c36f05735c7c9b3d5b0e933b4ca52ad0b6e4b38038d82703d35b"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bb0452d7b8516178c969d305d9630a3c9b8cf16fcf4713261c9ebd465af0d73"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9a9d9155e2a9f38b2eb9374c88f02fd4d6851ae17b65ee786a87d032f87008f8"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f836444b4c5ece128b23ec36a446c9ab7f9b0f7981d0d27e13a7c366ee163f8a"}, - {file = "pydantic-1.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:8481dca324e1c7b715ce091a698b181054d22072e848b6fc7895cd86f79b4449"}, - {file = "pydantic-1.10.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87f831e81ea0589cd18257f84386bf30154c5f4bed373b7b75e5cb0b5d53ea87"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ce1612e98c6326f10888df951a26ec1a577d8df49ddcaea87773bfbe23ba5cc"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58e41dd1e977531ac6073b11baac8c013f3cd8706a01d3dc74e86955be8b2c0c"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6a4b0aab29061262065bbdede617ef99cc5914d1bf0ddc8bcd8e3d7928d85bd6"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36e44a4de37b8aecffa81c081dbfe42c4d2bf9f6dff34d03dce157ec65eb0f15"}, - {file = "pydantic-1.10.5-cp37-cp37m-win_amd64.whl", hash = "sha256:261f357f0aecda005934e413dfd7aa4077004a174dafe414a8325e6098a8e419"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b429f7c457aebb7fbe7cd69c418d1cd7c6fdc4d3c8697f45af78b8d5a7955760"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:663d2dd78596c5fa3eb996bc3f34b8c2a592648ad10008f98d1348be7ae212fb"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51782fd81f09edcf265823c3bf43ff36d00db246eca39ee765ef58dc8421a642"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c428c0f64a86661fb4873495c4fac430ec7a7cef2b8c1c28f3d1a7277f9ea5ab"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:76c930ad0746c70f0368c4596020b736ab65b473c1f9b3872310a835d852eb19"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3257bd714de9db2102b742570a56bf7978e90441193acac109b1f500290f5718"}, - {file = "pydantic-1.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:f5bee6c523d13944a1fdc6f0525bc86dbbd94372f17b83fa6331aabacc8fd08e"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:532e97c35719f137ee5405bd3eeddc5c06eb91a032bc755a44e34a712420daf3"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca9075ab3de9e48b75fa8ccb897c34ccc1519177ad8841d99f7fd74cf43be5bf"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd46a0e6296346c477e59a954da57beaf9c538da37b9df482e50f836e4a7d4bb"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3353072625ea2a9a6c81ad01b91e5c07fa70deb06368c71307529abf70d23325"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3f9d9b2be177c3cb6027cd67fbf323586417868c06c3c85d0d101703136e6b31"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b473d00ccd5c2061fd896ac127b7755baad233f8d996ea288af14ae09f8e0d1e"}, - {file = "pydantic-1.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:5f3bc8f103b56a8c88021d481410874b1f13edf6e838da607dcb57ecff9b4594"}, - {file = "pydantic-1.10.5-py3-none-any.whl", hash = "sha256:7c5b94d598c90f2f46b3a983ffb46ab806a67099d118ae0da7ef21a2a4033b28"}, - {file = "pydantic-1.10.5.tar.gz", hash = "sha256:9e337ac83686645a46db0e825acceea8e02fca4062483f40e9ae178e8bd1103a"}, + {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, + {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, + {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, + {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, + {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, + {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, + {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, + {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, + {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, + {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, ] [package.dependencies] @@ -1346,6 +1581,21 @@ files = [ flake8 = ">=3.5" pytest = ">=3.5" +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pytz" version = "2022.7.1" @@ -1485,6 +1735,24 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} [package.extras] idna2008 = ["idna"] +[[package]] +name = "s3transfer" +version = "0.6.0" +description = "An Amazon S3 Transfer Manager" +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, + {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + [[package]] name = "setuptools" version = "67.5.0" @@ -1881,6 +2149,18 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +category = "dev" +optional = false +python-versions = ">=3.4" +files = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] + [[package]] name = "zipp" version = "3.15.0" @@ -1908,4 +2188,4 @@ starlette = [] [metadata] lock-version = "2.0" python-versions = "^3.7.0" -content-hash = "93e3ce63cf5a2e72870e4da2b22904dc0c80679522371c346bdf87f8e5e05a85" +content-hash = "4b1ab95b5de3e714dc00c53801626d696eeaecb734cf672289d226e41c3fe271" diff --git a/pyproject.toml b/pyproject.toml index 964b6589..1b2e2118 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,9 @@ output = "reports/coverage.xml" [tool.mypy] files = "openapi_core" +plugins = [ + "pydantic.mypy" +] strict = true [[tool.mypy.overrides]] @@ -72,6 +75,7 @@ jsonschema-spec = "^0.1.1" backports-cached-property = {version = "^1.0.2", python = "<3.8" } sphinx = {version = "^5.3.0", optional = true} sphinx-immaterial = {version = "^0.11.0", optional = true} +pydantic = "^1.10.7" [tool.poetry.extras] docs = ["sphinx", "sphinx-immaterial"] @@ -83,11 +87,13 @@ starlette = ["starlette", "httpx"] [tool.poetry.dev-dependencies] black = "^23.1.0" +boto3 = "^1.26.96" django = ">=3.0" djangorestframework = "^3.11.2" falcon = ">=3.0" flask = "*" isort = "^5.11.5" +moto = "^4.1.5" pre-commit = "*" pytest = "^7" pytest-flake8 = "*"