diff --git a/.gitignore b/.gitignore index b776e1999c2..cc01240a405 100644 --- a/.gitignore +++ b/.gitignore @@ -305,5 +305,8 @@ site/ !404.html !docs/overrides/*.html +# CDK +.cdk + !.github/workflows/lib examples/**/sam/.aws-sam diff --git a/aws_lambda_powertools/__init__.py b/aws_lambda_powertools/__init__.py index 65b5eb86730..750ae92c4d1 100644 --- a/aws_lambda_powertools/__init__.py +++ b/aws_lambda_powertools/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -"""Top-level package for Lambda Python Powertools.""" - +from pathlib import Path +"""Top-level package for Lambda Python Powertools.""" from .logging import Logger # noqa: F401 from .metrics import Metrics, single_metric # noqa: F401 from .package_logger import set_package_logger_handler @@ -10,4 +10,6 @@ __author__ = """Amazon Web Services""" +PACKAGE_PATH = Path(__file__).parent + set_package_logger_handler() diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 903fc7e828f..11adcfc2ed6 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -32,7 +32,7 @@ _SAFE_URI = "-._~()'!*:@,;" # https://www.ietf.org/rfc/rfc3986.txt # API GW/ALB decode non-safe URI chars; we must support them too _UNSAFE_URI = "%<> \[\]{}|^" # noqa: W605 -_NAMED_GROUP_BOUNDARY_PATTERN = fr"(?P\1[{_SAFE_URI}{_UNSAFE_URI}\\w]+)" +_NAMED_GROUP_BOUNDARY_PATTERN = rf"(?P\1[{_SAFE_URI}{_UNSAFE_URI}\\w]+)" class ProxyEventType(Enum): @@ -124,10 +124,11 @@ def __init__( def to_dict(self) -> Dict[str, str]: """Builds the configured Access-Control http headers""" - headers = { + headers: Dict[str, str] = { "Access-Control-Allow-Origin": self.allow_origin, "Access-Control-Allow-Headers": ",".join(sorted(self.allow_headers)), } + if self.expose_headers: headers["Access-Control-Expose-Headers"] = ",".join(self.expose_headers) if self.max_age is not None: @@ -145,7 +146,8 @@ def __init__( status_code: int, content_type: Optional[str], body: Union[str, bytes, None], - headers: Optional[Dict] = None, + headers: Optional[Dict[str, Union[str, List[str]]]] = None, + cookies: Optional[List[str]] = None, ): """ @@ -158,13 +160,16 @@ def __init__( provided http headers body: Union[str, bytes, None] Optionally set the response body. Note: bytes body will be automatically base64 encoded - headers: dict - Optionally set specific http headers. Setting "Content-Type" hear would override the `content_type` value. + headers: dict[str, Union[str, List[str]]] + Optionally set specific http headers. Setting "Content-Type" here would override the `content_type` value. + cookies: list[str] + Optionally set cookies. """ self.status_code = status_code self.body = body self.base64_encoded = False - self.headers: Dict = headers or {} + self.headers: Dict[str, Union[str, List[str]]] = headers if headers else {} + self.cookies = cookies or [] if content_type: self.headers.setdefault("Content-Type", content_type) @@ -196,11 +201,12 @@ def _add_cors(self, cors: CORSConfig): def _add_cache_control(self, cache_control: str): """Set the specified cache control headers for 200 http responses. For non-200 `no-cache` is used.""" - self.response.headers["Cache-Control"] = cache_control if self.response.status_code == 200 else "no-cache" + cache_control = cache_control if self.response.status_code == 200 else "no-cache" + self.response.headers["Cache-Control"] = cache_control def _compress(self): """Compress the response body, but only if `Accept-Encoding` headers includes gzip.""" - self.response.headers["Content-Encoding"] = "gzip" + self.response.headers["Content-Encoding"].append("gzip") if isinstance(self.response.body, str): logger.debug("Converting string response to bytes before compressing it") self.response.body = bytes(self.response.body, "utf-8") @@ -226,11 +232,12 @@ def build(self, event: BaseProxyEvent, cors: Optional[CORSConfig] = None) -> Dic logger.debug("Encoding bytes response with base64") self.response.base64_encoded = True self.response.body = base64.b64encode(self.response.body).decode() + return { "statusCode": self.response.status_code, - "headers": self.response.headers, "body": self.response.body, "isBase64Encoded": self.response.base64_encoded, + **event.header_serializer().serialize(headers=self.response.headers, cookies=self.response.cookies), } @@ -596,7 +603,7 @@ def _path_starts_with(path: str, prefix: str): def _not_found(self, method: str) -> ResponseBuilder: """Called when no matching route was found and includes support for the cors preflight response""" - headers = {} + headers: Dict[str, Union[str, List[str]]] = {} if self._cors: logger.debug("CORS is enabled, updating headers.") headers.update(self._cors.to_dict()) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 896b303cd08..4ddc51cd102 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,5 +1,4 @@ import logging -from abc import ABC from typing import Any, Callable, Optional, Type, TypeVar from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent @@ -10,7 +9,7 @@ AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent) -class BaseRouter(ABC): +class BaseRouter: current_event: AppSyncResolverEventT # type: ignore[valid-type] lambda_context: LambdaContext diff --git a/aws_lambda_powertools/shared/headers_serializer.py b/aws_lambda_powertools/shared/headers_serializer.py new file mode 100644 index 00000000000..4db7effe81b --- /dev/null +++ b/aws_lambda_powertools/shared/headers_serializer.py @@ -0,0 +1,111 @@ +import warnings +from collections import defaultdict +from typing import Any, Dict, List, Union + + +class BaseHeadersSerializer: + """ + Helper class to correctly serialize headers and cookies for Amazon API Gateway, + ALB and Lambda Function URL response payload. + """ + + def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]: + """ + Serializes headers and cookies according to the request type. + Returns a dict that can be merged with the response payload. + + Parameters + ---------- + headers: Dict[str, List[str]] + A dictionary of headers to set in the response + cookies: List[str] + A list of cookies to set in the response + """ + raise NotImplementedError() + + +class HttpApiHeadersSerializer(BaseHeadersSerializer): + def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]: + """ + When using HTTP APIs or LambdaFunctionURLs, everything is taken care automatically for us. + We can directly assign a list of cookies and a dict of headers to the response payload, and the + runtime will automatically serialize them correctly on the output. + + https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format + https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response + """ + + # Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. + # Duplicate headers are combined with commas and included in the headers field. + combined_headers: Dict[str, str] = {} + for key, values in headers.items(): + if isinstance(values, str): + combined_headers[key] = values + else: + combined_headers[key] = ", ".join(values) + + return {"headers": combined_headers, "cookies": cookies} + + +class MultiValueHeadersSerializer(BaseHeadersSerializer): + def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]: + """ + When using REST APIs, headers can be encoded using the `multiValueHeaders` key on the response. + This is also the case when using an ALB integration with the `multiValueHeaders` option enabled. + The solution covers headers with just one key or multiple keys. + + https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format + https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers-response + """ + payload: Dict[str, List[str]] = defaultdict(list) + + for key, values in headers.items(): + if isinstance(values, str): + payload[key].append(values) + else: + for value in values: + payload[key].append(value) + + if cookies: + payload.setdefault("Set-Cookie", []) + for cookie in cookies: + payload["Set-Cookie"].append(cookie) + + return {"multiValueHeaders": payload} + + +class SingleValueHeadersSerializer(BaseHeadersSerializer): + def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]: + """ + The ALB integration has `multiValueHeaders` disabled by default. + If we try to set multiple headers with the same key, or more than one cookie, print a warning. + + https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#respond-to-load-balancer + """ + payload: Dict[str, Dict[str, str]] = {} + payload.setdefault("headers", {}) + + if cookies: + if len(cookies) > 1: + warnings.warn( + "Can't encode more than one cookie in the response. Sending the last cookie only. " + "Did you enable multiValueHeaders on the ALB Target Group?" + ) + + # We can only send one cookie, send the last one + payload["headers"]["Set-Cookie"] = cookies[-1] + + for key, values in headers.items(): + if isinstance(values, str): + payload["headers"][key] = values + else: + if len(values) > 1: + warnings.warn( + f"Can't encode more than one header value for the same key ('{key}') in the response. " + "Did you enable multiValueHeaders on the ALB Target Group?" + ) + + # We can only set one header per key, send the last one + payload["headers"][key] = values[-1] + + return payload diff --git a/aws_lambda_powertools/utilities/data_classes/alb_event.py b/aws_lambda_powertools/utilities/data_classes/alb_event.py index 159779c86a7..1bd49fd05b6 100644 --- a/aws_lambda_powertools/utilities/data_classes/alb_event.py +++ b/aws_lambda_powertools/utilities/data_classes/alb_event.py @@ -1,5 +1,10 @@ from typing import Dict, List, Optional +from aws_lambda_powertools.shared.headers_serializer import ( + BaseHeadersSerializer, + MultiValueHeadersSerializer, + SingleValueHeadersSerializer, +) from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent, DictWrapper @@ -30,3 +35,11 @@ def multi_value_query_string_parameters(self) -> Optional[Dict[str, List[str]]]: @property def multi_value_headers(self) -> Optional[Dict[str, List[str]]]: return self.get("multiValueHeaders") + + def header_serializer(self) -> BaseHeadersSerializer: + # When using the ALB integration, the `multiValueHeaders` feature can be disabled (default) or enabled. + # We can determine if the feature is enabled by looking if the event has a `multiValueHeaders` key. + if self.multi_value_headers: + return MultiValueHeadersSerializer() + + return SingleValueHeadersSerializer() diff --git a/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py b/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py index be374aba398..030d9739fa4 100644 --- a/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py +++ b/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py @@ -1,5 +1,10 @@ from typing import Any, Dict, List, Optional +from aws_lambda_powertools.shared.headers_serializer import ( + BaseHeadersSerializer, + HttpApiHeadersSerializer, + MultiValueHeadersSerializer, +) from aws_lambda_powertools.utilities.data_classes.common import ( BaseProxyEvent, BaseRequestContext, @@ -106,6 +111,9 @@ def path_parameters(self) -> Optional[Dict[str, str]]: def stage_variables(self) -> Optional[Dict[str, str]]: return self.get("stageVariables") + def header_serializer(self) -> BaseHeadersSerializer: + return MultiValueHeadersSerializer() + class RequestContextV2AuthorizerIam(DictWrapper): @property @@ -250,3 +258,6 @@ def path(self) -> str: def http_method(self) -> str: """The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT.""" return self.request_context.http.method + + def header_serializer(self): + return HttpApiHeadersSerializer() diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py index 2109ee3dd3e..ffd608f3015 100644 --- a/aws_lambda_powertools/utilities/data_classes/common.py +++ b/aws_lambda_powertools/utilities/data_classes/common.py @@ -2,6 +2,8 @@ import json from typing import Any, Dict, Optional +from aws_lambda_powertools.shared.headers_serializer import BaseHeadersSerializer + class DictWrapper: """Provides a single read only access to a wrapper dict""" @@ -127,6 +129,9 @@ def get_header_value( """ return get_header_value(self.headers, name, default_value, case_sensitive) + def header_serializer(self) -> BaseHeadersSerializer: + raise NotImplementedError() + class RequestContextClientCert(DictWrapper): @property diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index f4f45a051f8..c4cae718289 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -312,11 +312,18 @@ For convenience, these are the default values when using `CORSConfig` to enable ### Fine grained responses -You can use the `Response` class to have full control over the response, for example you might want to add additional headers or set a custom Content-type. +You can use the `Response` class to have full control over the response. For example, you might want to add additional headers, cookies, or set a custom Content-type. + +???+ info + Powertools serializes headers and cookies according to the type of input event. + Some event sources require headers and cookies to be encoded as `multiValueHeaders`. + +???+ warning "Using multiple values for HTTP headers in ALB?" + Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers) to serialize response headers correctly. === "fine_grained_responses.py" - ```python hl_lines="7 24-28" + ```python hl_lines="7 24-29" --8<-- "examples/event_handler_rest/src/fine_grained_responses.py" ``` diff --git a/docs/upgrade.md b/docs/upgrade.md new file mode 100644 index 00000000000..91ad54e42d3 --- /dev/null +++ b/docs/upgrade.md @@ -0,0 +1,57 @@ +--- +title: Upgrade guide +description: Guide to update between major Powertools versions +--- + + + +## Migrate to v2 from v1 + +The transition from Powertools for Python v1 to v2 is as painless as possible, as we aimed for minimal breaking changes. +Changes at a glance: + +* The API for **event handler's `Response`** has minor changes to support multi value headers and cookies. + +???+ important + Powertools for Python v2 drops suport for Python 3.6, following the Python 3.6 End-Of-Life (EOL) reached on December 23, 2021. + +### Initial Steps + +Before you start, we suggest making a copy of your current working project or create a new branch with git. + +1. **Upgrade** Python to at least v3.7 + +2. **Ensure** you have the latest `aws-lambda-powertools` + + ```bash + pip install aws-lambda-powertools -U + ``` + +3. **Review** the following sections to confirm whether they affect your code + +## Event Handler Response (headers and cookies) + +The `Response` class of the event handler utility changed slightly: + +1. The `headers` parameter now expects either a value or list of values per header (type `Union[str, Dict[str, List[str]]]`) +2. We introduced a new `cookies` parameter (type `List[str]`) + +???+ note + Code that set headers as `Dict[str, str]` will still work unchanged. + +```python hl_lines="6 12 13" +@app.get("/todos") +def get_todos(): + # Before + return Response( + # ... + headers={"Content-Type": "text/plain"} + ) + + # After + return Response( + # ... + headers={"Content-Type": ["text/plain"]}, + cookies=["CookieName=CookieValue"] + ) +``` diff --git a/examples/event_handler_rest/src/binary_responses_output.json b/examples/event_handler_rest/src/binary_responses_output.json index 0938dee6811..ec59d251732 100644 --- a/examples/event_handler_rest/src/binary_responses_output.json +++ b/examples/event_handler_rest/src/binary_responses_output.json @@ -1,7 +1,7 @@ { "body": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICAgIDx0aXRsZT5BV1MgTGFtYmRhPC90aXRsZT4KICAgIDxkZWZzPgogICAgICAgIDxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSIxMDAlIiB4Mj0iMTAwJSIgeTI9IjAlIiBpZD0ibGluZWFyR3JhZGllbnQtMSI+CiAgICAgICAgICAgIDxzdG9wIHN0b3AtY29sb3I9IiNDODUxMUIiIG9mZnNldD0iMCUiPjwvc3RvcD4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI0ZGOTkwMCIgb2Zmc2V0PSIxMDAlIj48L3N0b3A+CiAgICAgICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDwvZGVmcz4KICAgIDxnPgogICAgICAgIDxyZWN0IGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQtMSkiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2Ij48L3JlY3Q+CiAgICAgICAgPHBhdGggZD0iTTg5LjYyNDExMjYsMjExLjIgTDQ5Ljg5MDMyNzcsMjExLjIgTDkzLjgzNTQ4MzIsMTE5LjM0NzIgTDExMy43NDcyOCwxNjAuMzM5MiBMODkuNjI0MTEyNiwyMTEuMiBaIE05Ni43MDI5MzU3LDExMC41Njk2IEM5Ni4xNjQwODU4LDEwOS40NjU2IDk1LjA0MTQ4MTMsMTA4Ljc2NDggOTMuODE2MjM4NCwxMDguNzY0OCBMOTMuODA2NjE2MywxMDguNzY0OCBDOTIuNTcxNzUxNCwxMDguNzY4IDkxLjQ0OTE0NjYsMTA5LjQ3NTIgOTAuOTE5OTE4NywxMTAuNTg1NiBMNDEuOTEzNDIwOCwyMTMuMDIwOCBDNDEuNDM4NzE5NywyMTQuMDEyOCA0MS41MDYwNzU4LDIxNS4xNzc2IDQyLjA5NjI0NTEsMjE2LjEwODggQzQyLjY3OTk5OTQsMjE3LjAzNjggNDMuNzA2MzgwNSwyMTcuNiA0NC44MDY1MzMxLDIxNy42IEw5MS42NTQ0MjMsMjE3LjYgQzkyLjg5NTcwMjcsMjE3LjYgOTQuMDIxNTE0OSwyMTYuODg2NCA5NC41NTM5NTAxLDIxNS43Njk2IEwxMjAuMjAzODU5LDE2MS42ODk2IEMxMjAuNjE3NjE5LDE2MC44MTI4IDEyMC42MTQ0MTIsMTU5Ljc5ODQgMTIwLjE4NzgyMiwxNTguOTI4IEw5Ni43MDI5MzU3LDExMC41Njk2IFogTTIwNy45ODUxMTcsMjExLjIgTDE2OC41MDc5MjgsMjExLjIgTDEwNS4xNzM3ODksNzguNjI0IEMxMDQuNjQ0NTYxLDc3LjUxMDQgMTAzLjUxNTU0MSw3Ni44IDEwMi4yNzc0NjksNzYuOCBMNzYuNDQ3OTQzLDc2LjggTDc2LjQ3NjgwOTksNDQuOCBMMTI3LjEwMzA2Niw0NC44IEwxOTAuMTQ1MzI4LDE3Ny4zNzI4IEMxOTAuNjc0NTU2LDE3OC40ODY0IDE5MS44MDM1NzUsMTc5LjIgMTkzLjA0MTY0NywxNzkuMiBMMjA3Ljk4NTExNywxNzkuMiBMMjA3Ljk4NTExNywyMTEuMiBaIE0yMTEuMTkyNTU4LDE3Mi44IEwxOTUuMDcxOTU4LDE3Mi44IEwxMzIuMDI5Njk2LDQwLjIyNzIgQzEzMS41MDA0NjgsMzkuMTEzNiAxMzAuMzcxNDQ5LDM4LjQgMTI5LjEzMDE2OSwzOC40IEw3My4yNzI1NzYsMzguNCBDNzEuNTA1Mjc1OCwzOC40IDcwLjA2ODM0MjEsMzkuODMwNCA3MC4wNjUxMzQ0LDQxLjU5NjggTDcwLjAyOTg1MjgsNzkuOTk2OCBDNzAuMDI5ODUyOCw4MC44NDggNzAuMzYzNDI2Niw4MS42NjA4IDcwLjk2OTYzMyw4Mi4yNjI0IEM3MS41Njk0MjQ2LDgyLjg2NCA3Mi4zODQxMTQ2LDgzLjIgNzMuMjM3Mjk0MSw4My4yIEwxMDAuMjUzNTczLDgzLjIgTDE2My41OTA5MiwyMTUuNzc2IEMxNjQuMTIzMzU1LDIxNi44ODk2IDE2NS4yNDU5NiwyMTcuNiAxNjYuNDg0MDMyLDIxNy42IEwyMTEuMTkyNTU4LDIxNy42IEMyMTIuOTY2Mjc0LDIxNy42IDIxNC40LDIxNi4xNjY0IDIxNC40LDIxNC40IEwyMTQuNCwxNzYgQzIxNC40LDE3NC4yMzM2IDIxMi45NjYyNzQsMTcyLjggMjExLjE5MjU1OCwxNzIuOCBMMjExLjE5MjU1OCwxNzIuOCBaIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==", - "headers": { - "Content-Type": "image/svg+xml" + "multiValueHeaders": { + "Content-Type": ["image/svg+xml"] }, "isBase64Encoded": true, "statusCode": 200 diff --git a/examples/event_handler_rest/src/compressing_responses_output.json b/examples/event_handler_rest/src/compressing_responses_output.json index 0836b3aa726..60a63966494 100644 --- a/examples/event_handler_rest/src/compressing_responses_output.json +++ b/examples/event_handler_rest/src/compressing_responses_output.json @@ -1,8 +1,8 @@ { "statusCode": 200, - "headers": { - "Content-Type": "application/json", - "Content-Encoding": "gzip" + "multiValueHeaders": { + "Content-Type": ["application/json"], + "Content-Encoding": ["gzip"] }, "body": "H4sIAAAAAAACE42STU4DMQyFrxJl3QXln96AMyAW7sSDLCVxiJ0Kqerd8TCCUOgii1EmP/783pOPXjmw+N3L0TfB+hz8brvxtC5KGtHvfMCIkzZx0HT5MPmNnziViIr2dIYoeNr8Q1x3xHsjcVadIbkZJoq2RXU8zzQROLseQ9505NzeCNQdMJNBE+UmY4zbzjAJhWtlZ57sB84BWtul+rteH2HPlVgWARwjqXkxpklK5gmEHAQqJBMtFsGVygcKmNVRjG0wxvuzGF2L0dpVUOKMC3bfJNjJgWMrCuZk7cUp02AiD72D6WKHHwUDKbiJs6AZ0VZXKOUx4uNvzdxT+E4mLcMA+6G8nzrLQkaxkNEVrFKW2VGbJCoCY7q2V3+tiv5kGThyxfTecDWbgGz/NfYXhL6ePgF9PnFdPgMAAA==", "isBase64Encoded": true diff --git a/examples/event_handler_rest/src/fine_grained_responses.py b/examples/event_handler_rest/src/fine_grained_responses.py index 3e477160307..4892de9c798 100644 --- a/examples/event_handler_rest/src/fine_grained_responses.py +++ b/examples/event_handler_rest/src/fine_grained_responses.py @@ -19,13 +19,14 @@ def get_todos(): todos: requests.Response = requests.get("https://jsonplaceholder.typicode.com/todos") todos.raise_for_status() - custom_headers = {"X-Transaction-Id": f"{uuid4()}"} + custom_headers = {"X-Transaction-Id": [f"{uuid4()}"]} return Response( status_code=HTTPStatus.OK.value, # 200 content_type=content_types.APPLICATION_JSON, body=todos.json()[:10], headers=custom_headers, + cookies=["=; Secure; Expires="], ) diff --git a/examples/event_handler_rest/src/fine_grained_responses_output.json b/examples/event_handler_rest/src/fine_grained_responses_output.json index c3d58098e80..1ce606839b1 100644 --- a/examples/event_handler_rest/src/fine_grained_responses_output.json +++ b/examples/event_handler_rest/src/fine_grained_responses_output.json @@ -1,8 +1,9 @@ { "statusCode": 200, - "headers": { - "Content-Type": "application/json", - "X-Transaction-Id": "3490eea9-791b-47a0-91a4-326317db61a9" + "multiValueHeaders": { + "Content-Type": ["application/json"], + "X-Transaction-Id": ["3490eea9-791b-47a0-91a4-326317db61a9"], + "Set-Cookie": ["=; Secure; Expires="] }, "body": "{\"todos\":[{\"userId\":1,\"id\":1,\"title\":\"delectus aut autem\",\"completed\":false},{\"userId\":1,\"id\":2,\"title\":\"quis ut nam facilis et officia qui\",\"completed\":false},{\"userId\":1,\"id\":3,\"title\":\"fugiat veniam minus\",\"completed\":false},{\"userId\":1,\"id\":4,\"title\":\"et porro tempora\",\"completed\":true},{\"userId\":1,\"id\":5,\"title\":\"laboriosam mollitia et enim quasi adipisci quia provident illum\",\"completed\":false},{\"userId\":1,\"id\":6,\"title\":\"qui ullam ratione quibusdam voluptatem quia omnis\",\"completed\":false},{\"userId\":1,\"id\":7,\"title\":\"illo expedita consequatur quia in\",\"completed\":false},{\"userId\":1,\"id\":8,\"title\":\"quo adipisci enim quam ut ab\",\"completed\":true},{\"userId\":1,\"id\":9,\"title\":\"molestiae perspiciatis ipsa\",\"completed\":false},{\"userId\":1,\"id\":10,\"title\":\"illo est ratione doloremque quia maiores aut\",\"completed\":true}]}", "isBase64Encoded": false diff --git a/examples/event_handler_rest/src/getting_started_rest_api_resolver_output.json b/examples/event_handler_rest/src/getting_started_rest_api_resolver_output.json index 2ef3714531f..24d2b5c6dbc 100644 --- a/examples/event_handler_rest/src/getting_started_rest_api_resolver_output.json +++ b/examples/event_handler_rest/src/getting_started_rest_api_resolver_output.json @@ -1,7 +1,7 @@ { "statusCode": 200, - "headers": { - "Content-Type": "application/json" + "multiValueHeaders": { + "Content-Type": ["application/json"] }, "body": "{\"todos\":[{\"userId\":1,\"id\":1,\"title\":\"delectus aut autem\",\"completed\":false},{\"userId\":1,\"id\":2,\"title\":\"quis ut nam facilis et officia qui\",\"completed\":false},{\"userId\":1,\"id\":3,\"title\":\"fugiat veniam minus\",\"completed\":false},{\"userId\":1,\"id\":4,\"title\":\"et porro tempora\",\"completed\":true},{\"userId\":1,\"id\":5,\"title\":\"laboriosam mollitia et enim quasi adipisci quia provident illum\",\"completed\":false},{\"userId\":1,\"id\":6,\"title\":\"qui ullam ratione quibusdam voluptatem quia omnis\",\"completed\":false},{\"userId\":1,\"id\":7,\"title\":\"illo expedita consequatur quia in\",\"completed\":false},{\"userId\":1,\"id\":8,\"title\":\"quo adipisci enim quam ut ab\",\"completed\":true},{\"userId\":1,\"id\":9,\"title\":\"molestiae perspiciatis ipsa\",\"completed\":false},{\"userId\":1,\"id\":10,\"title\":\"illo est ratione doloremque quia maiores aut\",\"completed\":true}]}", "isBase64Encoded": false diff --git a/examples/event_handler_rest/src/setting_cors_output.json b/examples/event_handler_rest/src/setting_cors_output.json index ca86e892d38..19660941e91 100644 --- a/examples/event_handler_rest/src/setting_cors_output.json +++ b/examples/event_handler_rest/src/setting_cors_output.json @@ -1,9 +1,9 @@ { "statusCode": 200, - "headers": { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "https://www.example.com", - "Access-Control-Allow-Headers": "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key" + "multiValueHeaders": { + "Content-Type": ["application/json"], + "Access-Control-Allow-Origin": ["https://www.example.com"], + "Access-Control-Allow-Headers": ["Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key"] }, "body": "{\"todos\":[{\"userId\":1,\"id\":1,\"title\":\"delectus aut autem\",\"completed\":false},{\"userId\":1,\"id\":2,\"title\":\"quis ut nam facilis et officia qui\",\"completed\":false},{\"userId\":1,\"id\":3,\"title\":\"fugiat veniam minus\",\"completed\":false},{\"userId\":1,\"id\":4,\"title\":\"et porro tempora\",\"completed\":true},{\"userId\":1,\"id\":5,\"title\":\"laboriosam mollitia et enim quasi adipisci quia provident illum\",\"completed\":false},{\"userId\":1,\"id\":6,\"title\":\"qui ullam ratione quibusdam voluptatem quia omnis\",\"completed\":false},{\"userId\":1,\"id\":7,\"title\":\"illo expedita consequatur quia in\",\"completed\":false},{\"userId\":1,\"id\":8,\"title\":\"quo adipisci enim quam ut ab\",\"completed\":true},{\"userId\":1,\"id\":9,\"title\":\"molestiae perspiciatis ipsa\",\"completed\":false},{\"userId\":1,\"id\":10,\"title\":\"illo est ratione doloremque quia maiores aut\",\"completed\":true}]}", "isBase64Encoded": false diff --git a/mkdocs.yml b/mkdocs.yml index 171cf36eb13..59fcdfa6a08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,7 @@ nav: - Tutorial: tutorial/index.md - Roadmap: roadmap.md - API reference: api/" target="_blank + - Upgrade guide: upgrade.md - Core utilities: - core/tracer.md - core/logger.md diff --git a/poetry.lock b/poetry.lock index 9b94cb96cfe..43c92ec29ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,30 +8,62 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.4.0" +version = "22.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "aws-cdk-lib" -version = "2.23.0" +version = "2.38.1" description = "Version 2 of the AWS Cloud Development Kit library" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = "~=3.7" + +[package.dependencies] +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "aws-cdk.aws-apigatewayv2-alpha" +version = "2.38.1a0" +description = "The CDK Construct Library for AWS::APIGatewayv2" +category = "dev" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +aws-cdk-lib = ">=2.38.1,<3.0.0" +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "aws-cdk.aws-apigatewayv2-integrations-alpha" +version = "2.38.1a0" +description = "Integrations for AWS APIGateway V2" +category = "dev" +optional = false +python-versions = "~=3.7" [package.dependencies] +aws-cdk-lib = ">=2.38.1,<3.0.0" +"aws-cdk.aws-apigatewayv2-alpha" = "2.38.1.a0" constructs = ">=10.0.0,<11.0.0" -jsii = ">=1.57.0,<2.0.0" +jsii = ">=1.63.2,<2.0.0" publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-xray-sdk" @@ -47,11 +79,11 @@ wrapt = "*" [[package]] name = "bandit" -version = "1.7.1" +version = "1.7.4" description = "Security oriented static analyser for python code." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} @@ -59,57 +91,57 @@ GitPython = ">=1.0.1" PyYAML = ">=5.3.1" stevedore = ">=1.20.0" +[package.extras] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml"] +toml = ["toml"] +yaml = ["pyyaml"] + [[package]] name = "black" -version = "21.12b0" +version = "22.6.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -click = ">=7.1.2" -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0,<1" +pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = ">=0.2.6,<2.0.0" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = [ - {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, - {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, -] +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.23.10" +version = "1.24.58" description = "The AWS SDK for Python" category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] -botocore = ">=1.26.10,<1.27.0" +botocore = ">=1.27.58,<1.28.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.5.0,<0.6.0" +s3transfer = ">=0.6.0,<0.7.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.26.10" +version = "1.27.58" description = "Low-level, data-driven core of boto 3." category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] jmespath = ">=0.7.1,<2.0.0" @@ -117,21 +149,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = ">=1.25.4,<1.27" [package.extras] -crt = ["awscrt (==0.13.8)"] - -[[package]] -name = "cattrs" -version = "1.0.0" -description = "Composable complex class support for attrs." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -attrs = ">=17.3" - -[package.extras] -dev = ["bumpversion", "wheel", "watchdog", "flake8", "tox", "coverage", "sphinx", "pytest", "hypothesis", "pendulum"] +crt = ["awscrt (==0.14.0)"] [[package]] name = "cattrs" @@ -156,22 +174,22 @@ python-versions = ">=3.6" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.4" +version = "8.1.3" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -187,38 +205,31 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "constructs" -version = "10.1.1" +version = "10.1.85" description = "A programming model for software-defined state" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = "~=3.7" [package.dependencies] -jsii = ">=1.57.0,<2.0.0" +jsii = ">=1.65.0,<2.0.0" publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" [[package]] name = "coverage" -version = "6.2" +version = "6.4.4" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] -[[package]] -name = "dataclasses" -version = "0.8" -description = "A backport of the dataclasses module for Python 3.6" -category = "main" -optional = false -python-versions = ">=3.6, <3.7" - [[package]] name = "decorator" version = "5.1.1" @@ -236,8 +247,8 @@ optional = true python-versions = ">=3.6,<4.0" [package.extras] -dnssec = ["cryptography (>=2.6,<37.0)"] curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +dnssec = ["cryptography (>=2.6,<37.0)"] doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.20)"] @@ -294,7 +305,7 @@ optional = false python-versions = "*" [package.extras] -devel = ["colorama", "jsonschema", "json-spec", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] [[package]] name = "flake8" @@ -310,9 +321,22 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" +[[package]] +name = "flake8-black" +version = "0.3.3" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +black = ">=22.1.0" +flake8 = ">=3.0.0" +tomli = "*" + [[package]] name = "flake8-bugbear" -version = "22.7.1" +version = "22.8.23" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false @@ -341,28 +365,27 @@ test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] [[package]] name = "flake8-comprehensions" -version = "3.7.0" +version = "3.10.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -flake8 = ">=3.0,<3.2.0 || >3.2.0,<5" +flake8 = ">=3.0,<3.2.0 || >3.2.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "flake8-debugger" -version = "4.0.0" +version = "4.1.2" description = "ipdb/pdb statement checker plugin for flake8" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] flake8 = ">=3.0" pycodestyle = "*" -six = "*" [[package]] name = "flake8-eradicate" @@ -428,7 +451,7 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["twine", "markdown", "flake8", "wheel"] +dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "gitdb" @@ -443,15 +466,15 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.20" -description = "Python Git Library" +version = "3.1.27" +description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} [[package]] name = "idna" @@ -474,24 +497,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] - -[[package]] -name = "importlib-resources" -version = "5.4.0" -description = "Read resources from Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -510,18 +518,18 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jinja2" -version = "3.0.3" +version = "3.1.2" description = "A very fast and expressive template engine." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" @@ -531,44 +539,44 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jmespath" -version = "0.10.0" +version = "1.0.1" description = "JSON Matching Expressions" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.7" [[package]] name = "jsii" -version = "1.57.0" +version = "1.65.0" description = "Python client for jsii runtime" category = "dev" optional = false -python-versions = "~=3.6" +python-versions = "~=3.7" [package.dependencies] -attrs = ">=21.2,<22.0" -cattrs = [ - {version = ">=1.0.0,<1.1.0", markers = "python_version < \"3.7\""}, - {version = ">=1.8,<22.2", markers = "python_version >= \"3.7\""}, -] -importlib-resources = {version = "*", markers = "python_version < \"3.7\""} +attrs = ">=21.2,<23.0" +cattrs = ">=1.8,<22.2" +publication = ">=0.0.3" python-dateutil = "*" +typeguard = ">=2.13.3,<2.14.0" typing-extensions = ">=3.7,<5.0" [[package]] name = "mako" -version = "1.1.6" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +version = "1.2.1" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} MarkupSafe = ">=0.9.2" [package.extras] babel = ["babel"] lingua = ["lingua"] +testing = ["pytest"] [[package]] name = "mando" @@ -600,11 +608,11 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mccabe" @@ -878,14 +886,14 @@ markdown = ">=3.0" [[package]] name = "platformdirs" -version = "2.4.0" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] @@ -944,12 +952,11 @@ optional = true python-versions = ">=3.6.1" [package.dependencies] -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} typing-extensions = ">=3.7.4.3" [package.extras] -email = ["email-validator (>=1.0.3)"] dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] [[package]] name = "pyflakes" @@ -983,22 +990,22 @@ markdown = ">=3.2" [[package]] name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.0.1" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -1058,7 +1065,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-forked" @@ -1074,17 +1081,17 @@ pytest = ">=3.10" [[package]] name = "pytest-mock" -version = "3.6.1" +version = "3.8.2" description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] pytest = ">=5.0" [package.extras] -dev = ["pre-commit", "tox", "pytest-asyncio"] +dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-xdist" @@ -1157,21 +1164,21 @@ mando = ">=0.6,<0.7" [[package]] name = "requests" -version = "2.27.1" +version = "2.28.1" description = "Python HTTP for Humans." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "retry" @@ -1210,11 +1217,11 @@ python-versions = ">=3.5" [[package]] name = "s3transfer" -version = "0.5.2" +version = "0.6.0" description = "An Amazon S3 Transfer Manager" category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" @@ -1252,11 +1259,11 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "tomli" -version = "1.2.3" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "typed-ast" @@ -1266,6 +1273,18 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "typeguard" +version = "2.13.3" +description = "Run-time type checker for Python" +category = "dev" +optional = false +python-versions = ">=3.5.3" + +[package.extras] +doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["mypy", "pytest", "typing-extensions"] + [[package]] name = "types-requests" version = "2.28.9" @@ -1295,15 +1314,15 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.11" +version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1347,205 +1366,193 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" -python-versions = "^3.6.2" -content-hash = "0a9e21c2f15825934ad6c786121da020c4a964c5a0dd138e0e8ae09c0865a055" +python-versions = "^3.7.4" +content-hash = "5f36bb2dc0877d19d3a6405df72819c3678fc97fa5ae3e31f9b125c40f39404f" [metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] +atomicwrites = [] attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] aws-cdk-lib = [ - {file = "aws-cdk-lib-2.23.0.tar.gz", hash = "sha256:3e07d1c6b320795d38567be183e56c2125b4c4492589775257aabec3d3e2a384"}, - {file = "aws_cdk_lib-2.23.0-py3-none-any.whl", hash = "sha256:1ec04a146d3364cd0fc4da08e3f8ca25e28df68abaa90641936db17a415ca4bc"}, + {file = "aws-cdk-lib-2.38.1.tar.gz", hash = "sha256:5d56effc85fc998de5277aa87e89b2177564b43111a7e331bb8b83c0cd8e96ee"}, + {file = "aws_cdk_lib-2.38.1-py3-none-any.whl", hash = "sha256:400062e9f021412c470cbde8125ee4db68a13af1d501a03512ef46c4a026ab2e"}, +] +"aws-cdk.aws-apigatewayv2-alpha" = [ + {file = "aws-cdk.aws-apigatewayv2-alpha-2.38.1a0.tar.gz", hash = "sha256:a51a5cf462043f783b03b4e6a92e9fc280acff77781924d5730d53868502fbe9"}, + {file = "aws_cdk.aws_apigatewayv2_alpha-2.38.1a0-py3-none-any.whl", hash = "sha256:5345959a98b7fe405a9779fc7ea42b3453f0d550ccf257bbc4d9e9d5830c6046"}, ] -aws-xray-sdk = [ - {file = "aws-xray-sdk-2.10.0.tar.gz", hash = "sha256:9b14924fd0628cf92936055864655354003f0b1acc3e1c3ffde6403d0799dd7a"}, - {file = "aws_xray_sdk-2.10.0-py2.py3-none-any.whl", hash = "sha256:7551e81a796e1a5471ebe84844c40e8edf7c218db33506d046fec61f7495eda4"}, +"aws-cdk.aws-apigatewayv2-integrations-alpha" = [ + {file = "aws-cdk.aws-apigatewayv2-integrations-alpha-2.38.1a0.tar.gz", hash = "sha256:4df15306e9d4db41f818bb3b3b67cf956ee36a87d3989b94b0c98ea152dbaa01"}, + {file = "aws_cdk.aws_apigatewayv2_integrations_alpha-2.38.1a0-py3-none-any.whl", hash = "sha256:184fe6ce9718237fe69a4ccda9d7c5d62fd0adf823269d2816b4b9a57978c204"}, ] +aws-xray-sdk = [] bandit = [ - {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, - {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, + {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, + {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, ] black = [ - {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, - {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, + {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, + {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, + {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, + {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, + {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, + {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, + {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, + {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, + {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, + {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, + {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, + {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, + {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, + {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, + {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, + {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, + {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, + {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, + {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, ] boto3 = [ - {file = "boto3-1.23.10-py3-none-any.whl", hash = "sha256:40d08614f17a69075e175c02c5d5aab69a6153fd50e40fa7057b913ac7bf40e7"}, - {file = "boto3-1.23.10.tar.gz", hash = "sha256:2a4395e3241c20eef441d7443a5e6eaa0ee3f7114653fb9d9cef41587526f7bd"}, + {file = "boto3-1.24.58-py3-none-any.whl", hash = "sha256:edd79104b478203459a4e45a8602c83994ccd820c39fd85cc5de9aa0ee8e75d6"}, + {file = "boto3-1.24.58.tar.gz", hash = "sha256:8a888707586de7eb4ea367a4c51354d91efbe93c2e61dc02e5c98f8c8dc69584"}, ] botocore = [ - {file = "botocore-1.26.10-py3-none-any.whl", hash = "sha256:8a4a984bf901ccefe40037da11ba2abd1ddbcb3b490a492b7f218509c99fc12f"}, - {file = "botocore-1.26.10.tar.gz", hash = "sha256:5df2cf7ebe34377470172bd0bbc582cf98c5cbd02da0909a14e9e2885ab3ae9c"}, -] -cattrs = [ - {file = "cattrs-1.0.0-py2.py3-none-any.whl", hash = "sha256:616972ae3dfa6e623a40ad3cb845420e64942989152774ab055e5c2b2f89f997"}, - {file = "cattrs-1.0.0.tar.gz", hash = "sha256:b7ab5cf8ad127c42eefd01410c1c6e28569a45a255ea80ed968511873c433c7a"}, - {file = "cattrs-22.1.0-py3-none-any.whl", hash = "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364"}, - {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, + {file = "botocore-1.27.58-py3-none-any.whl", hash = "sha256:4cc12ab3b6f195292e3e34da6aa356edb8acee51e8405d048b50ccbb64a1b9f3"}, + {file = "botocore-1.27.58.tar.gz", hash = "sha256:d904ee4743bd4b2517634b012c98dcb166f82e9d20e50d8367cd078779181ec2"}, ] +cattrs = [] +certifi = [] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] +colorama = [] constructs = [ - {file = "constructs-10.1.1-py3-none-any.whl", hash = "sha256:c1f3deb196f54e070ded3c92c4339f73ef2b6022d35fb34908c0ebfa7ef8a640"}, - {file = "constructs-10.1.1.tar.gz", hash = "sha256:6ce0dd1352367237b5d7c51a25740482c852735d2a5e067c536acc1657f39ea5"}, + {file = "constructs-10.1.85-py3-none-any.whl", hash = "sha256:dbcdeda26e61df8855af81e915a338345ca2d33f04b2b2861ac3a19fe98c5cfd"}, + {file = "constructs-10.1.85.tar.gz", hash = "sha256:70b2b4d75760524ff903aae0056862cd83ad9b0a5b857d9aef50597c8bbf4cfc"}, ] coverage = [ - {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, - {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, - {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, - {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, - {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, - {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, - {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, - {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, - {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, - {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, - {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, - {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, - {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, - {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, - {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, - {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, - {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, - {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, - {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, - {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, -] -dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] + {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, + {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, + {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, + {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, + {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, + {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, + {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, + {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, + {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, + {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, + {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, + {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, + {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, + {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, + {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, + {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, +] +decorator = [] dnspython = [ {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] -email-validator = [ - {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, - {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, -] +email-validator = [] eradicate = [ {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, ] -exceptiongroup = [ - {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, - {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"}, -] -execnet = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, -] -fastjsonschema = [ - {file = "fastjsonschema-2.16.1-py3-none-any.whl", hash = "sha256:2f7158c4de792555753d6c2277d6a2af2d406dfd97aeca21d17173561ede4fe6"}, - {file = "fastjsonschema-2.16.1.tar.gz", hash = "sha256:d6fa3ffbe719768d70e298b9fb847484e2bdfdb7241ed052b8d57a9294a8c334"}, -] +exceptiongroup = [] +execnet = [] +fastjsonschema = [] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -flake8-bugbear = [ - {file = "flake8-bugbear-22.7.1.tar.gz", hash = "sha256:e450976a07e4f9d6c043d4f72b17ec1baf717fe37f7997009c8ae58064f88305"}, - {file = "flake8_bugbear-22.7.1-py3-none-any.whl", hash = "sha256:db5d7a831ef4412a224b26c708967ff816818cabae415e76b8c58df156c4b8e5"}, +flake8-black = [ + {file = "flake8-black-0.3.3.tar.gz", hash = "sha256:8211f5e20e954cb57c709acccf2f3281ce27016d4c4b989c3e51f878bb7ce12a"}, + {file = "flake8_black-0.3.3-py3-none-any.whl", hash = "sha256:7d667d0059fd1aa468de1669d77cc934b7f1feeac258d57bdae69a8e73c4cd90"}, ] -flake8-builtins = [ - {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, - {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, +flake8-bugbear = [ + {file = "flake8-bugbear-22.8.23.tar.gz", hash = "sha256:de0717d11124a082118dd08387b34fd86b2721642ec2d8e92be66cfa5ea7c445"}, + {file = "flake8_bugbear-22.8.23-py3-none-any.whl", hash = "sha256:1b0ebe0873d1cd55bf9f1588bfcb930db339018ef44a3981a26532daa9fd14a8"}, ] +flake8-builtins = [] flake8-comprehensions = [ - {file = "flake8-comprehensions-3.7.0.tar.gz", hash = "sha256:6b3218b2dde8ac5959c6476cde8f41a79e823c22feb656be2710cd2a3232cef9"}, - {file = "flake8_comprehensions-3.7.0-py3-none-any.whl", hash = "sha256:a5d7aea6315bbbd6fbcb2b4e80bff6a54d1600155e26236e555d0c6fe1d62522"}, + {file = "flake8-comprehensions-3.10.0.tar.gz", hash = "sha256:181158f7e7aa26a63a0a38e6017cef28c6adee71278ce56ce11f6ec9c4905058"}, + {file = "flake8_comprehensions-3.10.0-py3-none-any.whl", hash = "sha256:dad454fd3d525039121e98fa1dd90c46bc138708196a4ebbc949ad3c859adedb"}, ] flake8-debugger = [ - {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"}, - {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, + {file = "flake8-debugger-4.1.2.tar.gz", hash = "sha256:52b002560941e36d9bf806fca2523dc7fb8560a295d5f1a6e15ac2ded7a73840"}, + {file = "flake8_debugger-4.1.2-py3-none-any.whl", hash = "sha256:0a5e55aeddcc81da631ad9c8c366e7318998f83ff00985a49e6b3ecf61e571bf"}, ] flake8-eradicate = [ {file = "flake8-eradicate-1.3.0.tar.gz", hash = "sha256:e4c98f00d17dc8653e3388cac2624cd81e9735de2fd4a8dcf99029633ebd7a63"}, {file = "flake8_eradicate-1.3.0-py3-none-any.whl", hash = "sha256:85a71e0c5f4e07f7c6c5fec520483561fd6bd295417d622855bdeade99242e3d"}, ] -flake8-fixme = [ - {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"}, - {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, -] +flake8-fixme = [] flake8-isort = [ {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, ] -flake8-variables-names = [ - {file = "flake8_variables_names-0.0.4.tar.gz", hash = "sha256:d6fa0571a807c72940b5773827c5760421ea6f8206595ff0a8ecfa01e42bf2cf"}, -] -future = [ - {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, -] +flake8-variables-names = [] +future = [] ghp-import = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] -gitdb = [ - {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, - {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, -] +gitdb = [] gitpython = [ - {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, - {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1555,130 +1562,85 @@ importlib-metadata = [ {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] -importlib-resources = [ - {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, - {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] +iniconfig = [] +isort = [] jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] jmespath = [ - {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, - {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] jsii = [ - {file = "jsii-1.57.0-py3-none-any.whl", hash = "sha256:4888091986a9ed8d50b042cc9c35a9564dd54c19e78adb890bf06d9ffac1b325"}, - {file = "jsii-1.57.0.tar.gz", hash = "sha256:ff7a3c51c1a653dd8a4342043b5f8e40b928bc617e3141e0d5d66175d22a754b"}, + {file = "jsii-1.65.0-py3-none-any.whl", hash = "sha256:47b939259ec465bc044aed6d3288b9b27a8edef2af21f1a8448e297b8429f154"}, + {file = "jsii-1.65.0.tar.gz", hash = "sha256:a09ef1f9864fe27457ba4233b3a0744a76d2562077d5b8b3a7f2242edc4a8293"}, ] mako = [ - {file = "Mako-1.1.6-py2.py3-none-any.whl", hash = "sha256:afaf8e515d075b22fad7d7b8b30e4a1c90624ff2f3733a06ec125f5a5f043a57"}, - {file = "Mako-1.1.6.tar.gz", hash = "sha256:4e9e345a41924a954251b95b4b28e14a301145b544901332e658907a7464b6b2"}, -] -mando = [ - {file = "mando-0.6.4-py2.py3-none-any.whl", hash = "sha256:4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c"}, - {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"}, + {file = "Mako-1.2.1-py3-none-any.whl", hash = "sha256:df3921c3081b013c8a2d5ff03c18375651684921ae83fd12e64800b7da923257"}, + {file = "Mako-1.2.1.tar.gz", hash = "sha256:f054a5ff4743492f1aa9ecc47172cb33b42b9d993cffcc146c9de17e717b0307"}, ] +mando = [] markdown = [ {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mergedeep = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] -mike = [ - {file = "mike-0.6.0-py3-none-any.whl", hash = "sha256:cef9b9c803ff5c3fbb410f51f5ceb00902a9fe16d9fabd93b69c65cf481ab5a1"}, - {file = "mike-0.6.0.tar.gz", hash = "sha256:6d6239de2a60d733da2f34617e9b9a14c4b5437423b47e524f14dc96d6ce5f2f"}, -] + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +mccabe = [] +mergedeep = [] +mike = [] mkdocs = [ {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, ] -mkdocs-git-revision-date-plugin = [ - {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, -] +mkdocs-git-revision-date-plugin = [] mkdocs-material = [ {file = "mkdocs-material-8.4.1.tar.gz", hash = "sha256:92c70f94b2e1f8a05d9e05eec1c7af9dffc516802d69222329db89503c97b4f3"}, {file = "mkdocs_material-8.4.1-py2.py3-none-any.whl", hash = "sha256:319a6254819ce9d864ff79de48c43842fccfdebb43e4e6820eef75216f8cfb0a"}, ] -mkdocs-material-extensions = [ - {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, - {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, -] -mypy = [ - {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, - {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, - {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, - {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, - {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, - {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, - {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, - {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, - {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, - {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, - {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, - {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, - {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, - {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, - {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, - {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, - {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, - {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, - {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, -] +mkdocs-material-extensions = [] +mypy = [] mypy-boto3-appconfig = [ {file = "mypy-boto3-appconfig-1.24.36.post1.tar.gz", hash = "sha256:e1916b3754915cb411ef977083500e1f30f81f7b3aea6ff5eed1cec91944dea6"}, {file = "mypy_boto3_appconfig-1.24.36.post1-py3-none-any.whl", hash = "sha256:a5dbe549dbebf4bc7a6cfcbfa9dff89ceb4983c042b785763ee656504bdb49f6"}, @@ -1719,44 +1681,22 @@ mypy-boto3-xray = [ {file = "mypy-boto3-xray-1.24.36.post1.tar.gz", hash = "sha256:104f1ecf7f1f6278c582201e71a7ab64843d3a3fdc8f23295cf68788cc77e9bb"}, {file = "mypy_boto3_xray-1.24.36.post1-py3-none-any.whl", hash = "sha256:97b9f0686c717c8be99ac06cb52febaf71712b4e4cd0b61ed2eb5ed012a9b5fd"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] +mypy-extensions = [] +packaging = [] +pathspec = [] pbr = [ {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"}, {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"}, ] -pdoc3 = [ - {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, -] +pdoc3 = [] platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -publication = [ - {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, - {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -py-cpuinfo = [ - {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] +pluggy = [] +publication = [] +py = [] +py-cpuinfo = [] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, @@ -1806,46 +1746,22 @@ pygments = [ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, ] -pymdown-extensions = [ - {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, - {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, -] -pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, -] +pymdown-extensions = [] +pyparsing = [] pytest = [ - {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, - {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"}, - {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"}, -] -pytest-benchmark = [ - {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"}, - {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"}, -] -pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] -pytest-forked = [ - {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, - {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] +pytest-asyncio = [] +pytest-benchmark = [] +pytest-cov = [] +pytest-forked = [] pytest-mock = [ - {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, - {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, -] -pytest-xdist = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, -] -python-dateutil = [ - {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"}, + {file = "pytest-mock-3.8.2.tar.gz", hash = "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2"}, + {file = "pytest_mock-3.8.2-py3-none-any.whl", hash = "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"}, ] +pytest-xdist = [] +python-dateutil = [] python-snappy = [ {file = "python-snappy-0.6.1.tar.gz", hash = "sha256:b6a107ab06206acc5359d4c5632bd9b22d448702a79b3169b0c62e0fb808bb2a"}, {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b7f920eaf46ebf41bd26f9df51c160d40f9e00b7b48471c3438cb8d027f7fb9b"}, @@ -1931,72 +1847,28 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -pyyaml-env-tag = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] -radon = [ - {file = "radon-5.1.0-py2.py3-none-any.whl", hash = "sha256:fa74e018197f1fcb54578af0f675d8b8e2342bd8e0b72bef8197bc4c9e645f36"}, - {file = "radon-5.1.0.tar.gz", hash = "sha256:cb1d8752e5f862fb9e20d82b5f758cbc4fb1237c92c9a66450ea0ea7bf29aeee"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -retry = [ - {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, - {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, -] +pyyaml-env-tag = [] +radon = [] +requests = [] +retry = [] "ruamel.yaml" = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, ] -"ruamel.yaml.clib" = [ - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, - {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, -] +"ruamel.yaml.clib" = [] s3transfer = [ - {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, - {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, + {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, + {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -smmap = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] -stevedore = [ - {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, - {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, -] +smmap = [] +stevedore = [] tomli = [ - {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, - {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, @@ -2024,6 +1896,10 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] +typeguard = [ + {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"}, + {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"}, +] types-requests = [ {file = "types-requests-2.28.9.tar.gz", hash = "sha256:feaf581bd580497a47fe845d506fa3b91b484cf706ff27774e87659837de9962"}, {file = "types_requests-2.28.9-py3-none-any.whl", hash = "sha256:86cb66d3de2f53eac5c09adc42cf6547eefbd0c7e1210beca1ee751c35d96083"}, @@ -2036,7 +1912,10 @@ typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] -urllib3 = [] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] watchdog = [ {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, @@ -2130,10 +2009,7 @@ wrapt = [ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] -xenon = [ - {file = "xenon-0.9.0-py2.py3-none-any.whl", hash = "sha256:994c80c7f1c6d40596b600b93734d85a5739208f31895ef99f1e4d362caf9e35"}, - {file = "xenon-0.9.0.tar.gz", hash = "sha256:d2b9cb6c6260f771a432c1e588e51fddb17858f88f73ef641e7532f7a5f58fb8"}, -] +xenon = [] zipp = [ {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, diff --git a/pyproject.toml b/pyproject.toml index b12fdcc092a..4b75a9822ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ classifiers=[ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -20,7 +19,7 @@ keywords = ["aws_lambda_powertools", "aws", "tracing", "logging", "lambda", "pow license = "MIT-0" [tool.poetry.dependencies] -python = "^3.6.2" +python = "^3.7.4" aws-xray-sdk = "^2.8.0" fastjsonschema = "^2.14.5" boto3 = "^1.18" @@ -32,13 +31,14 @@ email-validator = {version = "*", optional = true } # issue #1148 coverage = {extras = ["toml"], version = "^6.2"} pytest = "^7.0.1" -black = "^21.12b0" +black = "^22.6.0" flake8-builtins = "^1.5.3" flake8-comprehensions = "^3.7.0" flake8-debugger = "^4.0.0" flake8-fixme = "^1.1.1" flake8-isort = "^4.1.2" flake8-variables-names = "^0.0.4" +flake8-black = "^0.3.3" isort = "^5.10.1" pytest-cov = "^3.0.0" pytest-mock = "^3.5.1" @@ -54,22 +54,24 @@ mike = "^0.6.0" mypy = "^0.971" retry = "^0.9.2" pytest-xdist = "^2.5.0" -aws-cdk-lib = "^2.23.0" +aws-cdk-lib = "^2.38.1" +"aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" +"aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" pytest-benchmark = "^3.4.1" -mypy-boto3-appconfig = { version = "^1.24.29", python = ">=3.7" } -mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" } -mypy-boto3-cloudwatch = { version = "^1.24.35", python = ">=3.7" } -mypy-boto3-dynamodb = { version = "^1.24.27", python = ">=3.7" } -mypy-boto3-lambda = { version = "^1.24.0", python = ">=3.7" } -mypy-boto3-logs = { version = "^1.24.0", python = ">=3.7" } -mypy-boto3-secretsmanager = { version = "^1.24.11", python = ">=3.7" } -mypy-boto3-ssm = { version = "^1.24.0", python = ">=3.7" } -mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" } -mypy-boto3-xray = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-appconfig = "^1.24.29" +mypy-boto3-cloudformation = "^1.24.0" +mypy-boto3-cloudwatch = "^1.24.35" +mypy-boto3-dynamodb = "^1.24.27" +mypy-boto3-lambda = "^1.24.0" +mypy-boto3-logs = "^1.24.0" +mypy-boto3-secretsmanager = "^1.24.11" +mypy-boto3-ssm = "^1.24.0" +mypy-boto3-s3 = "^1.24.0" +mypy-boto3-xray = "^1.24.0" types-requests = "^2.28.8" -typing-extensions = { version = "^4.3.0", python = ">=3.7" } +typing-extensions = "^4.3.0" python-snappy = "^0.6.1" -mkdocs-material = { version = "^8.3.9", python = ">=3.7" } +mkdocs-material = "^8.3.9" [tool.poetry.extras] pydantic = ["pydantic", "email-validator"] diff --git a/tests/e2e/event_handler/__init__.py b/tests/e2e/event_handler/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/e2e/event_handler/conftest.py b/tests/e2e/event_handler/conftest.py new file mode 100644 index 00000000000..207ec443456 --- /dev/null +++ b/tests/e2e/event_handler/conftest.py @@ -0,0 +1,28 @@ +from pathlib import Path + +import pytest + +from tests.e2e.event_handler.infrastructure import EventHandlerStack + + +@pytest.fixture(autouse=True, scope="module") +def infrastructure(request: pytest.FixtureRequest, lambda_layer_arn: str): + """Setup and teardown logic for E2E test infrastructure + + Parameters + ---------- + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + lambda_layer_arn : str + Lambda Layer ARN + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + stack = EventHandlerStack(handlers_dir=Path(f"{request.path.parent}/handlers"), layer_arn=lambda_layer_arn) + try: + yield stack.deploy() + finally: + stack.delete() diff --git a/tests/e2e/event_handler/handlers/alb_handler.py b/tests/e2e/event_handler/handlers/alb_handler.py new file mode 100644 index 00000000000..4c3f4f9dac3 --- /dev/null +++ b/tests/e2e/event_handler/handlers/alb_handler.py @@ -0,0 +1,18 @@ +from aws_lambda_powertools.event_handler import ALBResolver, Response, content_types + +app = ALBResolver() + + +@app.get("/todos") +def hello(): + return Response( + status_code=200, + content_type=content_types.TEXT_PLAIN, + body="Hello world", + cookies=["CookieMonster", "MonsterCookie"], + headers={"Foo": ["bar", "zbr"]}, + ) + + +def lambda_handler(event, context): + return app.resolve(event, context) diff --git a/tests/e2e/event_handler/handlers/api_gateway_http_handler.py b/tests/e2e/event_handler/handlers/api_gateway_http_handler.py new file mode 100644 index 00000000000..1a20b730285 --- /dev/null +++ b/tests/e2e/event_handler/handlers/api_gateway_http_handler.py @@ -0,0 +1,18 @@ +from aws_lambda_powertools.event_handler import APIGatewayHttpResolver, Response, content_types + +app = APIGatewayHttpResolver() + + +@app.get("/todos") +def hello(): + return Response( + status_code=200, + content_type=content_types.TEXT_PLAIN, + body="Hello world", + cookies=["CookieMonster", "MonsterCookie"], + headers={"Foo": ["bar", "zbr"]}, + ) + + +def lambda_handler(event, context): + return app.resolve(event, context) diff --git a/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py b/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py new file mode 100644 index 00000000000..2f5ad0b94fa --- /dev/null +++ b/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py @@ -0,0 +1,18 @@ +from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types + +app = APIGatewayRestResolver() + + +@app.get("/todos") +def hello(): + return Response( + status_code=200, + content_type=content_types.TEXT_PLAIN, + body="Hello world", + cookies=["CookieMonster", "MonsterCookie"], + headers={"Foo": ["bar", "zbr"]}, + ) + + +def lambda_handler(event, context): + return app.resolve(event, context) diff --git a/tests/e2e/event_handler/handlers/lambda_function_url_handler.py b/tests/e2e/event_handler/handlers/lambda_function_url_handler.py new file mode 100644 index 00000000000..3fd4b46ea28 --- /dev/null +++ b/tests/e2e/event_handler/handlers/lambda_function_url_handler.py @@ -0,0 +1,18 @@ +from aws_lambda_powertools.event_handler import LambdaFunctionUrlResolver, Response, content_types + +app = LambdaFunctionUrlResolver() + + +@app.get("/todos") +def hello(): + return Response( + status_code=200, + content_type=content_types.TEXT_PLAIN, + body="Hello world", + cookies=["CookieMonster", "MonsterCookie"], + headers={"Foo": ["bar", "zbr"]}, + ) + + +def lambda_handler(event, context): + return app.resolve(event, context) diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py new file mode 100644 index 00000000000..62421b8aac9 --- /dev/null +++ b/tests/e2e/event_handler/infrastructure.py @@ -0,0 +1,81 @@ +from pathlib import Path +from typing import Dict, Optional + +from aws_cdk import CfnOutput +from aws_cdk import aws_apigateway as apigwv1 +from aws_cdk import aws_apigatewayv2_alpha as apigwv2 +from aws_cdk import aws_apigatewayv2_integrations_alpha as apigwv2integrations +from aws_cdk import aws_ec2 as ec2 +from aws_cdk import aws_elasticloadbalancingv2 as elbv2 +from aws_cdk import aws_elasticloadbalancingv2_targets as targets +from aws_cdk.aws_lambda import Function, FunctionUrlAuthType + +from tests.e2e.utils.infrastructure import BaseInfrastructure + + +class EventHandlerStack(BaseInfrastructure): + FEATURE_NAME = "event-handlers" + + def __init__(self, handlers_dir: Path, feature_name: str = FEATURE_NAME, layer_arn: str = "") -> None: + super().__init__(feature_name, handlers_dir, layer_arn) + + def create_resources(self): + functions = self.create_lambda_functions() + + self._create_alb(function=functions["AlbHandler"]) + self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) + self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) + self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) + + def _create_alb(self, function: Function): + vpc = ec2.Vpc(self.stack, "EventHandlerVPC", max_azs=2) + + alb = elbv2.ApplicationLoadBalancer(self.stack, "ALB", vpc=vpc, internet_facing=True) + CfnOutput(self.stack, "ALBDnsName", value=alb.load_balancer_dns_name) + + self._create_alb_listener(alb=alb, name="Basic", port=80, function=function) + self._create_alb_listener( + alb=alb, + name="MultiValueHeader", + port=8080, + function=function, + attributes={"lambda.multi_value_headers.enabled": "true"}, + ) + + def _create_alb_listener( + self, + alb: elbv2.ApplicationLoadBalancer, + name: str, + port: int, + function: Function, + attributes: Optional[Dict[str, str]] = None, + ): + listener = alb.add_listener(name, port=port, protocol=elbv2.ApplicationProtocol.HTTP) + target = listener.add_targets(f"ALB{name}Target", targets=[targets.LambdaTarget(function)]) + if attributes is not None: + for key, value in attributes.items(): + target.set_attribute(key, value) + CfnOutput(self.stack, f"ALB{name}ListenerPort", value=str(port)) + + def _create_api_gateway_http(self, function: Function): + apigw = apigwv2.HttpApi(self.stack, "APIGatewayHTTP", create_default_stage=True) + apigw.add_routes( + path="/todos", + methods=[apigwv2.HttpMethod.GET], + integration=apigwv2integrations.HttpLambdaIntegration("TodosIntegration", function), + ) + + CfnOutput(self.stack, "APIGatewayHTTPUrl", value=(apigw.url or "")) + + def _create_api_gateway_rest(self, function: Function): + apigw = apigwv1.RestApi(self.stack, "APIGatewayRest", deploy_options=apigwv1.StageOptions(stage_name="dev")) + + todos = apigw.root.add_resource("todos") + todos.add_method("GET", apigwv1.LambdaIntegration(function, proxy=True)) + + CfnOutput(self.stack, "APIGatewayRestUrl", value=apigw.url) + + def _create_lambda_function_url(self, function: Function): + # Maintenance: move auth to IAM when we create sigv4 builders + function_url = function.add_function_url(auth_type=FunctionUrlAuthType.NONE) + CfnOutput(self.stack, "LambdaFunctionUrl", value=function_url.url) diff --git a/tests/e2e/event_handler/test_header_serializer.py b/tests/e2e/event_handler/test_header_serializer.py new file mode 100644 index 00000000000..2b1d51bfb3d --- /dev/null +++ b/tests/e2e/event_handler/test_header_serializer.py @@ -0,0 +1,141 @@ +import pytest +from requests import Request + +from tests.e2e.utils import data_fetcher + + +@pytest.fixture +def alb_basic_listener_endpoint(infrastructure: dict) -> str: + dns_name = infrastructure.get("ALBDnsName") + port = infrastructure.get("ALBBasicListenerPort", "") + return f"http://{dns_name}:{port}" + + +@pytest.fixture +def alb_multi_value_header_listener_endpoint(infrastructure: dict) -> str: + dns_name = infrastructure.get("ALBDnsName") + port = infrastructure.get("ALBMultiValueHeaderListenerPort", "") + return f"http://{dns_name}:{port}" + + +@pytest.fixture +def apigw_rest_endpoint(infrastructure: dict) -> str: + return infrastructure.get("APIGatewayRestUrl", "") + + +@pytest.fixture +def apigw_http_endpoint(infrastructure: dict) -> str: + return infrastructure.get("APIGatewayHTTPUrl", "") + + +@pytest.fixture +def lambda_function_url_endpoint(infrastructure: dict) -> str: + return infrastructure.get("LambdaFunctionUrl", "") + + +def test_alb_headers_serializer(alb_basic_listener_endpoint): + # GIVEN + url = f"{alb_basic_listener_endpoint}/todos" + + # WHEN + response = data_fetcher.get_http_response(Request(method="GET", url=url)) + + # THEN + assert response.status_code == 200 + assert response.content == b"Hello world" + assert response.headers["content-type"] == "text/plain" + + # Only the last header for key "Foo" should be set + assert response.headers["Foo"] == "zbr" + + # Only the last cookie should be set + assert "MonsterCookie" in response.cookies.keys() + assert "CookieMonster" not in response.cookies.keys() + + +def test_alb_multi_value_headers_serializer(alb_multi_value_header_listener_endpoint): + # GIVEN + url = f"{alb_multi_value_header_listener_endpoint}/todos" + + # WHEN + response = data_fetcher.get_http_response(Request(method="GET", url=url)) + + # THEN + assert response.status_code == 200 + assert response.content == b"Hello world" + assert response.headers["content-type"] == "text/plain" + + # Only the last header for key "Foo" should be set + assert "Foo" in response.headers + foo_headers = [x.strip() for x in response.headers["Foo"].split(",")] + assert sorted(foo_headers) == ["bar", "zbr"] + + # Only the last cookie should be set + assert "MonsterCookie" in response.cookies.keys() + assert "CookieMonster" in response.cookies.keys() + + +def test_api_gateway_rest_headers_serializer(apigw_rest_endpoint): + # GIVEN + url = f"{apigw_rest_endpoint}/todos" + + # WHEN + response = data_fetcher.get_http_response(Request(method="GET", url=url)) + + # THEN + assert response.status_code == 200 + assert response.content == b"Hello world" + assert response.headers["content-type"] == "text/plain" + + # Only the last header for key "Foo" should be set + assert "Foo" in response.headers + foo_headers = [x.strip() for x in response.headers["Foo"].split(",")] + assert sorted(foo_headers) == ["bar", "zbr"] + + # Only the last cookie should be set + assert "MonsterCookie" in response.cookies.keys() + assert "CookieMonster" in response.cookies.keys() + + +def test_api_gateway_http_headers_serializer(apigw_http_endpoint): + # GIVEN + url = f"{apigw_http_endpoint}/todos" + + # WHEN + response = data_fetcher.get_http_response(Request(method="GET", url=url)) + + # THEN + assert response.status_code == 200 + assert response.content == b"Hello world" + assert response.headers["content-type"] == "text/plain" + + # Only the last header for key "Foo" should be set + assert "Foo" in response.headers + foo_headers = [x.strip() for x in response.headers["Foo"].split(",")] + assert sorted(foo_headers) == ["bar", "zbr"] + + # Only the last cookie should be set + assert "MonsterCookie" in response.cookies.keys() + assert "CookieMonster" in response.cookies.keys() + + +def test_lambda_function_url_headers_serializer(lambda_function_url_endpoint): + # GIVEN + url = f"{lambda_function_url_endpoint}todos" # the function url endpoint already has the trailing / + + # WHEN + response = data_fetcher.get_http_response(Request(method="GET", url=url)) + + # THEN + assert response.status_code == 200 + assert response.content == b"Hello world" + assert response.headers["content-type"] == "text/plain" + + # Only the last header for key "Foo" should be set + assert "Foo" in response.headers + foo_headers = [x.strip() for x in response.headers["Foo"].split(",")] + assert sorted(foo_headers) == ["bar", "zbr"] + + # Only the last cookie should be set + assert "MonsterCookie" in response.cookies.keys() + assert "CookieMonster" in response.cookies.keys() diff --git a/tests/e2e/utils/data_fetcher/__init__.py b/tests/e2e/utils/data_fetcher/__init__.py index 43024f9946f..be6909537e5 100644 --- a/tests/e2e/utils/data_fetcher/__init__.py +++ b/tests/e2e/utils/data_fetcher/__init__.py @@ -1,4 +1,4 @@ -from tests.e2e.utils.data_fetcher.common import get_lambda_response +from tests.e2e.utils.data_fetcher.common import get_http_response, get_lambda_response from tests.e2e.utils.data_fetcher.logs import get_logs from tests.e2e.utils.data_fetcher.metrics import get_metrics from tests.e2e.utils.data_fetcher.traces import get_traces diff --git a/tests/e2e/utils/data_fetcher/common.py b/tests/e2e/utils/data_fetcher/common.py index 2de8838dc74..29f97eab2de 100644 --- a/tests/e2e/utils/data_fetcher/common.py +++ b/tests/e2e/utils/data_fetcher/common.py @@ -2,8 +2,12 @@ from typing import Optional, Tuple import boto3 +import requests as requests from mypy_boto3_lambda import LambdaClient from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef +from requests import Request, Response +from requests.exceptions import RequestException +from retry import retry def get_lambda_response( @@ -13,3 +17,11 @@ def get_lambda_response( payload = payload or "" execution_time = datetime.utcnow() return client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse", Payload=payload), execution_time + + +@retry(RequestException, delay=2, jitter=1.5, tries=5) +def get_http_response(request: Request) -> Response: + session = requests.Session() + result = session.send(request.prepare()) + result.raise_for_status() + return result diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py index 7f232bb063f..47f2d65b7eb 100644 --- a/tests/e2e/utils/infrastructure.py +++ b/tests/e2e/utils/infrastructure.py @@ -15,9 +15,11 @@ from filelock import FileLock from mypy_boto3_cloudformation import CloudFormationClient +from aws_lambda_powertools import PACKAGE_PATH from tests.e2e.utils.asset import Assets PYTHON_RUNTIME_VERSION = f"V{''.join(map(str, sys.version_info[:2]))}" +SOURCE_CODE_ROOT_PATH = PACKAGE_PATH.parent logger = logging.getLogger(__name__) @@ -48,7 +50,7 @@ def __init__(self, feature_name: str, handlers_dir: Path, layer_arn: str = "") - # NOTE: Investigate why cdk.Environment in Stack # changes synthesized asset (no object_key in asset manifest) - self.app = App() + self.app = App(outdir=str(SOURCE_CODE_ROOT_PATH / ".cdk")) self.stack = Stack(self.app, self.stack_name) self.session = boto3.Session() self.cfn: CloudFormationClient = self.session.client("cloudformation") @@ -57,7 +59,7 @@ def __init__(self, feature_name: str, handlers_dir: Path, layer_arn: str = "") - self.account_id = self.session.client("sts").get_caller_identity()["Account"] self.region = self.session.region_name - def create_lambda_functions(self, function_props: Optional[Dict] = None): + def create_lambda_functions(self, function_props: Optional[Dict] = None) -> Dict[str, Function]: """Create Lambda functions available under handlers_dir It creates CloudFormation Outputs for every function found in PascalCase. For example, @@ -69,6 +71,11 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None): function_props: Optional[Dict] Dictionary representing CDK Lambda FunctionProps to override defaults + Returns + ------- + output: Dict[str, Function] + A dict with PascalCased function names and the corresponding CDK Function object + Examples -------- @@ -97,6 +104,8 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None): layer = LayerVersion.from_layer_version_arn(self.stack, "layer-arn", layer_version_arn=self.layer_arn) function_settings_override = function_props or {} + output: Dict[str, Function] = {} + for fn in handlers: fn_name = fn.stem fn_name_pascal_case = fn_name.title().replace("_", "") # basic_handler -> BasicHandler @@ -124,6 +133,10 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None): # CFN Outputs only support hyphen hence pascal case self.add_cfn_output(name=fn_name_pascal_case, value=function.function_name, arn=function.function_arn) + output[fn_name_pascal_case] = function + + return output + def deploy(self) -> Dict[str, str]: """Creates CloudFormation Stack and return stack outputs as dict @@ -287,7 +300,7 @@ def _create_layer(self) -> str: layer_version_name="aws-lambda-powertools-e2e-test", compatible_runtimes=[PythonVersion[PYTHON_RUNTIME_VERSION].value["runtime"]], code=Code.from_asset( - path=".", + path=str(SOURCE_CODE_ROOT_PATH), bundling=BundlingOptions( image=DockerImage.from_build( str(Path(__file__).parent), diff --git a/tests/events/albMultiValueHeadersEvent.json b/tests/events/albMultiValueHeadersEvent.json new file mode 100644 index 00000000000..6b34709605c --- /dev/null +++ b/tests/events/albMultiValueHeadersEvent.json @@ -0,0 +1,35 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:eu-central-1:1234567890:targetgroup/alb-c-Targe-11GDXTPQ7663S/804a67588bfdc10f" + } + }, + "httpMethod": "GET", + "path": "/todos", + "multiValueQueryStringParameters": {}, + "multiValueHeaders": { + "accept": [ + "*/*" + ], + "host": [ + "alb-c-LoadB-14POFKYCLBNSF-1815800096.eu-central-1.elb.amazonaws.com" + ], + "user-agent": [ + "curl/7.79.1" + ], + "x-amzn-trace-id": [ + "Root=1-62fa9327-21cdd4da4c6db451490a5fb7" + ], + "x-forwarded-for": [ + "123.123.123.123" + ], + "x-forwarded-port": [ + "80" + ], + "x-forwarded-proto": [ + "http" + ] + }, + "body": "", + "isBase64Encoded": false +} diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 4b1d7c1ee32..125a0f8c147 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -92,7 +92,26 @@ def get_lambda() -> Response: # THEN process event correctly # AND set the current_event type as APIGatewayProxyEvent assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] + + +def test_api_gateway_v1_cookies(): + # GIVEN a Http API V1 proxy type event + app = APIGatewayRestResolver() + cookie = "CookieMonster" + + @app.get("/my/path") + def get_lambda() -> Response: + assert isinstance(app.current_event, APIGatewayProxyEvent) + return Response(200, content_types.TEXT_PLAIN, "Hello world", cookies=[cookie]) + + # WHEN calling the event handler + result = app(LOAD_GW_EVENT, {}) + + # THEN process event correctly + # AND set the current_event type as APIGatewayProxyEvent + assert result["statusCode"] == 200 + assert result["multiValueHeaders"]["Set-Cookie"] == [cookie] def test_api_gateway(): @@ -110,7 +129,7 @@ def get_lambda() -> Response: # THEN process event correctly # AND set the current_event type as APIGatewayProxyEvent assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.TEXT_HTML + assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_HTML] assert result["body"] == "foo" @@ -132,9 +151,30 @@ def my_path() -> Response: # AND set the current_event type as APIGatewayProxyEventV2 assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.TEXT_PLAIN + assert "Cookies" not in result["headers"] assert result["body"] == "tom" +def test_api_gateway_v2_cookies(): + # GIVEN a Http API V2 proxy type event + app = APIGatewayHttpResolver() + cookie = "CookieMonster" + + @app.post("/my/path") + def my_path() -> Response: + assert isinstance(app.current_event, APIGatewayProxyEventV2) + return Response(200, content_types.TEXT_PLAIN, "Hello world", cookies=[cookie]) + + # WHEN calling the event handler + result = app(load_event("apiGatewayProxyV2Event.json"), {}) + + # THEN process event correctly + # AND set the current_event type as APIGatewayProxyEventV2 + assert result["statusCode"] == 200 + assert result["headers"]["Content-Type"] == content_types.TEXT_PLAIN + assert result["cookies"] == [cookie] + + def test_include_rule_matching(): # GIVEN app = ApiGatewayResolver() @@ -149,7 +189,7 @@ def get_lambda(my_id: str, name: str) -> Response: # THEN assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.TEXT_HTML + assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_HTML] assert result["body"] == "path" @@ -200,7 +240,7 @@ def handler(event, context): result = handler(LOAD_GW_EVENT, None) assert result["statusCode"] == 404 # AND cors headers are not returned - assert "Access-Control-Allow-Origin" not in result["headers"] + assert "Access-Control-Allow-Origin" not in result["multiValueHeaders"] def test_cors(): @@ -223,17 +263,17 @@ def handler(event, context): result = handler(LOAD_GW_EVENT, None) # THEN the headers should include cors headers - assert "headers" in result - headers = result["headers"] - assert headers["Content-Type"] == content_types.TEXT_HTML - assert headers["Access-Control-Allow-Origin"] == "*" + assert "multiValueHeaders" in result + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.TEXT_HTML] + assert headers["Access-Control-Allow-Origin"] == ["*"] assert "Access-Control-Allow-Credentials" not in headers - assert headers["Access-Control-Allow-Headers"] == ",".join(sorted(CORSConfig._REQUIRED_HEADERS)) + assert headers["Access-Control-Allow-Headers"] == [",".join(sorted(CORSConfig._REQUIRED_HEADERS))] # THEN for routes without cors flag return no cors headers mock_event = {"path": "/my/request", "httpMethod": "GET"} result = handler(mock_event, None) - assert "Access-Control-Allow-Origin" not in result["headers"] + assert "Access-Control-Allow-Origin" not in result["multiValueHeaders"] def test_cors_preflight_body_is_empty_not_null(): @@ -272,8 +312,8 @@ def handler(event, context): assert isinstance(body, str) decompress = zlib.decompress(base64.b64decode(body), wbits=zlib.MAX_WBITS | 16).decode("UTF-8") assert decompress == expected_value - headers = result["headers"] - assert headers["Content-Encoding"] == "gzip" + headers = result["multiValueHeaders"] + assert headers["Content-Encoding"] == ["gzip"] def test_base64_encode(): @@ -292,8 +332,8 @@ def read_image() -> Response: assert result["isBase64Encoded"] is True body = result["body"] assert isinstance(body, str) - headers = result["headers"] - assert headers["Content-Encoding"] == "gzip" + headers = result["multiValueHeaders"] + assert headers["Content-Encoding"] == ["gzip"] def test_compress_no_accept_encoding(): @@ -348,9 +388,9 @@ def handler(event, context): result = handler({"path": "/success", "httpMethod": "GET"}, None) # THEN return the set Cache-Control - headers = result["headers"] - assert headers["Content-Type"] == content_types.TEXT_HTML - assert headers["Cache-Control"] == "max-age=600" + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.TEXT_HTML] + assert headers["Cache-Control"] == ["max-age=600"] def test_cache_control_non_200(): @@ -369,9 +409,9 @@ def handler(event, context): result = handler({"path": "/fails", "httpMethod": "DELETE"}, None) # THEN return a Cache-Control of "no-cache" - headers = result["headers"] - assert headers["Content-Type"] == content_types.TEXT_HTML - assert headers["Cache-Control"] == "no-cache" + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.TEXT_HTML] + assert headers["Cache-Control"] == ["no-cache"] def test_rest_api(): @@ -388,7 +428,7 @@ def rest_func() -> Dict: # THEN automatically process this as a json rest api response assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected_str = json.dumps(expected_dict, separators=(",", ":"), indent=None, cls=Encoder) assert result["body"] == expected_str @@ -403,7 +443,7 @@ def rest_func() -> Response: status_code=404, content_type="used-if-not-set-in-header", body="Not found", - headers={"Content-Type": "header-content-type-wins", "custom": "value"}, + headers={"Content-Type": ["header-content-type-wins"], "custom": ["value"]}, ) # WHEN calling the event handler @@ -411,8 +451,8 @@ def rest_func() -> Response: # THEN the result can include some additional field control like overriding http headers assert result["statusCode"] == 404 - assert result["headers"]["Content-Type"] == "header-content-type-wins" - assert result["headers"]["custom"] == "value" + assert result["multiValueHeaders"]["Content-Type"] == ["header-content-type-wins"] + assert result["multiValueHeaders"]["custom"] == ["value"] assert result["body"] == "Not found" @@ -441,16 +481,16 @@ def another_one(): result = app(event, None) # THEN routes by default return the custom cors headers - assert "headers" in result - headers = result["headers"] - assert headers["Content-Type"] == content_types.APPLICATION_JSON - assert headers["Access-Control-Allow-Origin"] == cors_config.allow_origin - expected_allows_headers = ",".join(sorted(set(allow_header + cors_config._REQUIRED_HEADERS))) + assert "multiValueHeaders" in result + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.APPLICATION_JSON] + assert headers["Access-Control-Allow-Origin"] == [cors_config.allow_origin] + expected_allows_headers = [",".join(sorted(set(allow_header + cors_config._REQUIRED_HEADERS)))] assert headers["Access-Control-Allow-Headers"] == expected_allows_headers - assert headers["Access-Control-Expose-Headers"] == ",".join(cors_config.expose_headers) - assert headers["Access-Control-Max-Age"] == str(cors_config.max_age) + assert headers["Access-Control-Expose-Headers"] == [",".join(cors_config.expose_headers)] + assert headers["Access-Control-Max-Age"] == [str(cors_config.max_age)] assert "Access-Control-Allow-Credentials" in headers - assert headers["Access-Control-Allow-Credentials"] == "true" + assert headers["Access-Control-Allow-Credentials"] == ["true"] # AND custom cors was set on the app assert isinstance(app._cors, CORSConfig) @@ -459,7 +499,7 @@ def another_one(): # AND routes without cors don't include "Access-Control" headers event = {"path": "/another-one", "httpMethod": "GET"} result = app(event, None) - headers = result["headers"] + headers = result["multiValueHeaders"] assert "Access-Control-Allow-Origin" not in headers @@ -474,7 +514,7 @@ def test_no_content_response(): # THEN return an None body and no Content-Type header assert result["statusCode"] == response.status_code assert result["body"] is None - headers = result["headers"] + headers = result["multiValueHeaders"] assert "Content-Type" not in headers @@ -489,7 +529,7 @@ def test_no_matches_with_cors(): # THEN return a 404 # AND cors headers are returned assert result["statusCode"] == 404 - assert "Access-Control-Allow-Origin" in result["headers"] + assert "Access-Control-Allow-Origin" in result["multiValueHeaders"] assert "Not found" in result["body"] @@ -517,10 +557,10 @@ def post_no_cors(): # AND include Access-Control-Allow-Methods of the cors methods used assert result["statusCode"] == 204 assert result["body"] == "" - headers = result["headers"] + headers = result["multiValueHeaders"] assert "Content-Type" not in headers - assert "Access-Control-Allow-Origin" in result["headers"] - assert headers["Access-Control-Allow-Methods"] == "DELETE,GET,OPTIONS" + assert "Access-Control-Allow-Origin" in result["multiValueHeaders"] + assert headers["Access-Control-Allow-Methods"] == [",".join(sorted(["DELETE", "GET", "OPTIONS"]))] def test_custom_preflight_response(): @@ -535,7 +575,7 @@ def custom_preflight(): status_code=200, content_type=content_types.TEXT_HTML, body="Foo", - headers={"Access-Control-Allow-Methods": "CUSTOM"}, + headers={"Access-Control-Allow-Methods": ["CUSTOM"]}, ) @app.route(method="CUSTOM", rule="/some-call", cors=True) @@ -548,10 +588,10 @@ def custom_method(): # THEN return the custom preflight response assert result["statusCode"] == 200 assert result["body"] == "Foo" - headers = result["headers"] - assert headers["Content-Type"] == content_types.TEXT_HTML - assert "Access-Control-Allow-Origin" in result["headers"] - assert headers["Access-Control-Allow-Methods"] == "CUSTOM" + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.TEXT_HTML] + assert "Access-Control-Allow-Origin" in result["multiValueHeaders"] + assert headers["Access-Control-Allow-Methods"] == ["CUSTOM"] def test_service_error_responses(json_dump): @@ -569,7 +609,7 @@ def bad_request_error(): # THEN return the bad request error response # AND status code equals 400 assert result["statusCode"] == 400 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected = {"statusCode": 400, "message": "Missing required parameter"} assert result["body"] == json_dump(expected) @@ -584,7 +624,7 @@ def unauthorized_error(): # THEN return the unauthorized error response # AND status code equals 401 assert result["statusCode"] == 401 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected = {"statusCode": 401, "message": "Unauthorized"} assert result["body"] == json_dump(expected) @@ -599,7 +639,7 @@ def not_found_error(): # THEN return the not found error response # AND status code equals 404 assert result["statusCode"] == 404 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected = {"statusCode": 404, "message": "Not found"} assert result["body"] == json_dump(expected) @@ -614,7 +654,7 @@ def internal_server_error(): # THEN return the internal server error response # AND status code equals 500 assert result["statusCode"] == 500 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected = {"statusCode": 500, "message": "Internal server error"} assert result["body"] == json_dump(expected) @@ -629,8 +669,8 @@ def service_error(): # THEN return the service error response # AND status code equals 502 assert result["statusCode"] == 502 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON - assert "Access-Control-Allow-Origin" in result["headers"] + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] + assert "Access-Control-Allow-Origin" in result["multiValueHeaders"] expected = {"statusCode": 502, "message": "Something went wrong!"} assert result["body"] == json_dump(expected) @@ -653,8 +693,8 @@ def raises_error(): # AND include the exception traceback in the response assert result["statusCode"] == 500 assert "Traceback (most recent call last)" in result["body"] - headers = result["headers"] - assert headers["Content-Type"] == content_types.TEXT_PLAIN + headers = result["multiValueHeaders"] + assert headers["Content-Type"] == [content_types.TEXT_PLAIN] def test_debug_unhandled_exceptions_debug_off(): @@ -941,7 +981,7 @@ def base(): # THEN process event correctly assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router(): @@ -959,7 +999,7 @@ def foo(): # THEN process event correctly assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router_with_params(): @@ -985,7 +1025,7 @@ def foo(account_id): # THEN process event correctly assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router_with_prefix(): @@ -1004,7 +1044,7 @@ def foo(): # THEN process event correctly assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router_with_prefix_equals_path(): @@ -1024,7 +1064,7 @@ def foo(): # THEN process event correctly assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router_with_different_methods(): @@ -1074,7 +1114,7 @@ def patch_func(): result = app(LOAD_GW_EVENT, None) assert result["statusCode"] == 404 # AND cors headers are not returned - assert "Access-Control-Allow-Origin" not in result["headers"] + assert "Access-Control-Allow-Origin" not in result["multiValueHeaders"] def test_duplicate_routes(): @@ -1133,11 +1173,11 @@ def foo(account_id): # THEN events are processed correctly assert get_result["statusCode"] == 200 - assert get_result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert get_result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] assert post_result["statusCode"] == 200 - assert post_result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert post_result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] assert put_result["statusCode"] == 404 - assert put_result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert put_result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_api_gateway_app_router_access_to_resolver(): @@ -1156,7 +1196,7 @@ def foo(): result = app(LOAD_GW_EVENT, {}) assert result["statusCode"] == 200 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] def test_exception_handler(): @@ -1182,7 +1222,7 @@ def get_lambda() -> Response: # THEN call the exception_handler assert result["statusCode"] == 418 - assert result["headers"]["Content-Type"] == content_types.TEXT_HTML + assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_HTML] assert result["body"] == "Foo!" @@ -1209,7 +1249,7 @@ def get_lambda() -> Response: # THEN call the exception_handler assert result["statusCode"] == 500 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] assert result["body"] == "CUSTOM ERROR FORMAT" @@ -1228,7 +1268,7 @@ def handle_not_found(exc: NotFoundError) -> Response: # THEN call the exception_handler assert result["statusCode"] == 404 - assert result["headers"]["Content-Type"] == content_types.TEXT_PLAIN + assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_PLAIN] assert result["body"] == "I am a teapot!" @@ -1266,7 +1306,7 @@ def get_lambda() -> Response: # THEN call the exception_handler assert result["statusCode"] == 400 - assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] expected = {"statusCode": 400, "message": "Bad request"} assert result["body"] == json_dump(expected) diff --git a/tests/functional/event_handler/test_lambda_function_url.py b/tests/functional/event_handler/test_lambda_function_url.py index dc00c535580..ae0a231d46b 100644 --- a/tests/functional/event_handler/test_lambda_function_url.py +++ b/tests/functional/event_handler/test_lambda_function_url.py @@ -21,9 +21,30 @@ def foo(): # AND set the current_event type as LambdaFunctionUrlEvent assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.TEXT_HTML + assert "Cookies" not in result["headers"] assert result["body"] == "foo" +def test_lambda_function_url_event_with_cookies(): + # GIVEN a Lambda Function Url type event + app = LambdaFunctionUrlResolver() + cookie = "CookieMonster" + + @app.get("/") + def foo(): + assert isinstance(app.current_event, LambdaFunctionUrlEvent) + assert app.lambda_context == {} + return Response(200, content_types.TEXT_PLAIN, "foo", cookies=[cookie]) + + # WHEN calling the event handler + result = app(load_event("lambdaFunctionUrlEvent.json"), {}) + + # THEN process event correctly + # AND set the current_event type as LambdaFunctionUrlEvent + assert result["statusCode"] == 200 + assert result["cookies"] == [cookie] + + def test_lambda_function_url_no_matches(): # GIVEN a Lambda Function Url type event app = LambdaFunctionUrlResolver() diff --git a/tests/functional/test_headers_serializer.py b/tests/functional/test_headers_serializer.py new file mode 100644 index 00000000000..8a27ce8baa8 --- /dev/null +++ b/tests/functional/test_headers_serializer.py @@ -0,0 +1,147 @@ +from collections import defaultdict + +import pytest + +from aws_lambda_powertools.shared.headers_serializer import ( + HttpApiHeadersSerializer, + MultiValueHeadersSerializer, + SingleValueHeadersSerializer, +) + + +def test_http_api_headers_serializer(): + cookies = ["UUID=12345", "SSID=0xdeadbeef"] + header_values = ["bar", "zbr"] + headers = {"Foo": header_values} + + serializer = HttpApiHeadersSerializer() + payload = serializer.serialize(headers=headers, cookies=cookies) + + assert payload["cookies"] == cookies + assert payload["headers"]["Foo"] == ", ".join(header_values) + + +def test_http_api_headers_serializer_with_empty_values(): + serializer = HttpApiHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=[]) + assert payload == {"headers": {}, "cookies": []} + + +def test_http_api_headers_serializer_with_headers_only(): + content_type = "text/html" + serializer = HttpApiHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": [content_type]}, cookies=[]) + assert payload["headers"]["Content-Type"] == content_type + + +def test_http_api_headers_serializer_with_single_headers_only(): + content_type = "text/html" + serializer = HttpApiHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": content_type}, cookies=[]) + assert payload["headers"]["Content-Type"] == content_type + + +def test_http_api_headers_serializer_with_cookies_only(): + cookies = ["UUID=12345", "SSID=0xdeadbeef"] + serializer = HttpApiHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=cookies) + assert payload["cookies"] == cookies + + +def test_multi_value_headers_serializer(): + cookies = ["UUID=12345", "SSID=0xdeadbeef"] + header_values = ["bar", "zbr"] + headers = {"Foo": header_values} + + serializer = MultiValueHeadersSerializer() + payload = serializer.serialize(headers=headers, cookies=cookies) + + assert payload["multiValueHeaders"]["Set-Cookie"] == cookies + assert payload["multiValueHeaders"]["Foo"] == header_values + + +def test_multi_value_headers_serializer_with_headers_only(): + content_type = "text/html" + serializer = MultiValueHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": [content_type]}, cookies=[]) + assert payload["multiValueHeaders"]["Content-Type"] == [content_type] + + +def test_multi_value_headers_serializer_with_single_headers_only(): + content_type = "text/html" + serializer = MultiValueHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": content_type}, cookies=[]) + assert payload["multiValueHeaders"]["Content-Type"] == [content_type] + + +def test_multi_value_headers_serializer_with_cookies_only(): + cookie = "UUID=12345" + serializer = MultiValueHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=[cookie]) + assert payload["multiValueHeaders"]["Set-Cookie"] == [cookie] + + +def test_multi_value_headers_serializer_with_empty_values(): + serializer = MultiValueHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=[]) + assert payload["multiValueHeaders"] == defaultdict(list) + + +def test_single_value_headers_serializer(): + cookie = "UUID=12345" + content_type = "text/html" + headers = {"Content-Type": [content_type]} + + serializer = SingleValueHeadersSerializer() + payload = serializer.serialize(headers=headers, cookies=[cookie]) + assert payload["headers"]["Content-Type"] == content_type + assert payload["headers"]["Set-Cookie"] == cookie + + +def test_single_value_headers_serializer_with_headers_only(): + content_type = "text/html" + serializer = SingleValueHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": [content_type]}, cookies=[]) + assert payload["headers"]["Content-Type"] == content_type + + +def test_single_value_headers_serializer_with_single_headers_only(): + content_type = "text/html" + serializer = SingleValueHeadersSerializer() + payload = serializer.serialize(headers={"Content-Type": content_type}, cookies=[]) + assert payload["headers"]["Content-Type"] == content_type + + +def test_single_value_headers_serializer_with_cookies_only(): + cookie = "UUID=12345" + serializer = SingleValueHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=[cookie]) + assert payload["headers"] == {"Set-Cookie": cookie} + + +def test_single_value_headers_serializer_with_empty_values(): + serializer = SingleValueHeadersSerializer() + payload = serializer.serialize(headers={}, cookies=[]) + assert payload["headers"] == {} + + +def test_single_value_headers_with_multiple_cookies_warning(): + cookies = ["UUID=12345", "SSID=0xdeadbeef"] + warning_message = "Can't encode more than one cookie in the response. Sending the last cookie only." + serializer = SingleValueHeadersSerializer() + + with pytest.warns(match=warning_message): + payload = serializer.serialize(cookies=cookies, headers={}) + + assert payload["headers"]["Set-Cookie"] == cookies[-1] + + +def test_single_value_headers_with_multiple_header_values_warning(): + headers = {"Foo": ["bar", "zbr"]} + warning_message = "Can't encode more than one header value for the same key." + serializer = SingleValueHeadersSerializer() + + with pytest.warns(match=warning_message): + payload = serializer.serialize(cookies=[], headers=headers) + + assert payload["headers"]["Foo"] == headers["Foo"][-1]