From 462a0be4c835364e470f930323d0c4dcaf33c68b Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Sun, 18 Apr 2021 14:59:33 +0300 Subject: [PATCH 1/6] feat (parser): Feature request: Add parser API GW v1 proxy schema & envelope #402 --- .../utilities/parser/envelopes/__init__.py | 2 + .../utilities/parser/envelopes/apigw.py | 32 ++++++ .../utilities/parser/models/__init__.py | 10 ++ .../utilities/parser/models/apigw.py | 107 ++++++++++++++++++ docs/utilities/parser.md | 21 ++-- tests/events/apiGatewayProxyEvent.json | 16 ++- tests/functional/parser/schemas.py | 5 + tests/functional/parser/test_apigw.py | 102 +++++++++++++++++ 8 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 aws_lambda_powertools/utilities/parser/envelopes/apigw.py create mode 100644 aws_lambda_powertools/utilities/parser/models/apigw.py create mode 100644 tests/functional/parser/test_apigw.py diff --git a/aws_lambda_powertools/utilities/parser/envelopes/__init__.py b/aws_lambda_powertools/utilities/parser/envelopes/__init__.py index 10c70272c7d..e6f63c4792d 100644 --- a/aws_lambda_powertools/utilities/parser/envelopes/__init__.py +++ b/aws_lambda_powertools/utilities/parser/envelopes/__init__.py @@ -1,3 +1,4 @@ +from .apigw import ApiGatewayEnvelope from .base import BaseEnvelope from .cloudwatch import CloudWatchLogsEnvelope from .dynamodb import DynamoDBStreamEnvelope @@ -7,6 +8,7 @@ from .sqs import SqsEnvelope __all__ = [ + "ApiGatewayEnvelope", "CloudWatchLogsEnvelope", "DynamoDBStreamEnvelope", "EventBridgeEnvelope", diff --git a/aws_lambda_powertools/utilities/parser/envelopes/apigw.py b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py new file mode 100644 index 00000000000..205ba9af3a2 --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py @@ -0,0 +1,32 @@ +import logging +from typing import Any, Dict, Optional, Type, Union + +from ..models import APIGatewayProxyEvent +from ..types import Model +from .base import BaseEnvelope + +logger = logging.getLogger(__name__) + + +class ApiGatewayEnvelope(BaseEnvelope): + """Api Gateway envelope to extract data within detail key""" + + def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) -> Optional[Model]: + """Parses data found with model provided + + Parameters + ---------- + data : Dict + Lambda event to be parsed + model : Type[Model] + Data model provided to parse after extracting data using envelope + + Returns + ------- + Any + Parsed detail payload with model provided + """ + logger.debug(f"Parsing incoming data with Api Gateway model {APIGatewayProxyEvent}") + parsed_envelope = APIGatewayProxyEvent.parse_obj(data) + logger.debug(f"Parsing event payload in `detail` with {model}") + return self._parse(data=parsed_envelope.body, model=model) diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 923d5d057c3..0e59b2197a8 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -1,4 +1,10 @@ from .alb import AlbModel, AlbRequestContext, AlbRequestContextData +from .apigw import ( + APIGatewayEventAuthorizer, + APIGatewayEventIdentity, + APIGatewayEventRequestContext, + APIGatewayProxyEventModel, +) from .cloudwatch import CloudWatchLogsData, CloudWatchLogsDecode, CloudWatchLogsLogEvent, CloudWatchLogsModel from .dynamodb import DynamoDBStreamChangedRecordModel, DynamoDBStreamModel, DynamoDBStreamRecordModel from .event_bridge import EventBridgeModel @@ -70,4 +76,8 @@ "SqsRecordModel", "SqsMsgAttributeModel", "SqsAttributesModel", + "APIGatewayProxyEventModel", + "APIGatewayEventRequestContext", + "APIGatewayEventAuthorizer", + "APIGatewayEventIdentity", ] diff --git a/aws_lambda_powertools/utilities/parser/models/apigw.py b/aws_lambda_powertools/utilities/parser/models/apigw.py new file mode 100644 index 00000000000..d5c2f01e346 --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/models/apigw.py @@ -0,0 +1,107 @@ +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional + +from pydantic import BaseModel, root_validator +from pydantic.networks import IPvAnyNetwork + + +class ApiGatewayUserCertValidity(BaseModel): + notBefore: str + notAfter: str + + +class ApiGatewayUserCert(BaseModel): + clientCertPem: str + subjectDN: str + issuerDN: str + serialNumber: str + validity: ApiGatewayUserCertValidity + + +class APIGatewayEventIdentity(BaseModel): + accessKey: Optional[str] + accountId: Optional[str] + apiKey: Optional[str] + apiKeyId: Optional[str] + caller: Optional[str] + cognitoAuthenticationProvider: Optional[str] + cognitoAuthenticationType: Optional[str] + cognitoIdentityId: Optional[str] + cognitoIdentityPoolId: Optional[str] + principalOrgId: Optional[str] + sourceIp: IPvAnyNetwork + user: Optional[str] + userAgent: Optional[str] + userArn: Optional[str] + clientCert: Optional[ApiGatewayUserCert] + + +class APIGatewayEventAuthorizer(BaseModel): + claims: Optional[Dict[str, Any]] + scopes: Optional[List[str]] + + +class APIGatewayEventRequestContext(BaseModel): + accountId: str + apiId: str + authorizer: APIGatewayEventAuthorizer + stage: str + protocol: str + identity: APIGatewayEventIdentity + requestId: str + requestTime: str + requestTimeEpoch: datetime + resourceId: Optional[str] + resourcePath: str + domainName: Optional[str] + domainPrefix: Optional[str] + extendedRequestId: Optional[str] + httpMethod: Literal["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + path: str + connectedAt: Optional[datetime] + connectionId: Optional[str] + eventType: Optional[Literal["CONNECT", "MESSAGE", "DISCONNECT"]] + eventType: Optional[str] + messageDirection: Optional[str] + messageId: Optional[str] + routeKey: Optional[str] + operationName: Optional[str] + + +class APIGatewayProxyEventModel(BaseModel): + version: str + resource: str + path: str + httpMethod: Literal["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + headers: Dict[str, str] + multiValueHeaders: Dict[str, List[str]] + queryStringParameters: Optional[Dict[str, str]] + multiValueQueryStringParameters: Optional[Dict[str, List[str]]] + requestContext: APIGatewayEventRequestContext + pathParameters: Optional[Dict[str, str]] + stageVariables: Optional[Dict[str, str]] + isBase64Encoded: bool + body: str + + @root_validator() + def check_message_id(cls, values): + message_id, event_type = values.get("messageId"), values.get("eventType") + if message_id is not None and event_type != "MESSAGE": + raise TypeError("messageId is available only when the `eventType` is `MESSAGE`") + return values + + @root_validator(pre=True) + def check_both_http_methods(cls, values): + http_method, req_ctx_http_method = values.get("httpMethod"), values.get("requestContext", {}).get( + "httpMethod", "" + ) + if http_method != req_ctx_http_method: + raise TypeError("httpMethods and requestContext.httpMethod must be equal") + return values + + @root_validator(pre=True) + def check_both_paths(cls, values): + path, req_ctx_path = values.get("path"), values.get("requestContext", {}).get("path", "") + if path != req_ctx_path: + raise TypeError("path and requestContext.path must be equal") + return values diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 7c39b1ffd0a..d7481ccb0ac 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -161,6 +161,7 @@ Parser comes with the following built-in models: | **KinesisDataStreamModel** | Lambda Event Source payload for Amazon Kinesis Data Streams | | **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | | **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | +| **APIGatewayProxyEvent** | Lambda Event Source payload for Amazon API Gateway | ### extending built-in models @@ -294,16 +295,16 @@ Here's an example of parsing a model found in an event coming from EventBridge, Parser comes with the following built-in envelopes, where `Model` in the return section is your given model. -| Envelope name | Behaviour | Return | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -| **DynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamModel`.
2. Parses records in `NewImage` and `OldImage` keys using your model.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys | `List[Dict[str, Optional[Model]]]` | -| **EventBridgeEnvelope** | 1. Parses data using `EventBridgeModel`.
2. Parses `detail` key using your model and returns it. | `Model` | -| **SqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | -| **CloudWatchLogsEnvelope** | 1. Parses data using `CloudwatchLogsModel` which will base64 decode and decompress it.
2. Parses records in `message` key using your model and return them in a list. | `List[Model]` | -| **KinesisDataStreamEnvelope** | 1. Parses data using `KinesisDataStreamModel` which will base64 decode it.
2. Parses records in in `Records` key using your model and returns them in a list. | `List[Model]` | -| **SnsEnvelope** | 1. Parses data using `SnsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | -| **SnsSqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses SNS records in `body` key using `SnsNotificationModel`.
3. Parses data in `Message` key using your model and return them in a list. | `List[Model]` | - +| Envelope name | Behaviour | Return | +| --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| **DynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamModel`.
2. Parses records in `NewImage` and `OldImage` keys using your model.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys | `List[Dict[str, Optional[Model]]]` | +| **EventBridgeEnvelope** | 1. Parses data using `EventBridgeModel`.
2. Parses `detail` key using your model and returns it. | `Model` | +| **SqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | +| **CloudWatchLogsEnvelope** | 1. Parses data using `CloudwatchLogsModel` which will base64 decode and decompress it.
2. Parses records in `message` key using your model and return them in a list. | `List[Model]` | +| **KinesisDataStreamEnvelope** | 1. Parses data using `KinesisDataStreamModel` which will base64 decode it.
2. Parses records in in `Records` key using your model and returns them in a list. | `List[Model]` | +| **SnsEnvelope** | 1. Parses data using `SnsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | +| **SnsSqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses SNS records in `body` key using `SnsNotificationModel`.
3. Parses data in `Message` key using your model and return them in a list. | `List[Model]` | +| **ApiGatewayEnvelope** 1. Parses data using `APIGatewayProxyEvent`.
2. Parses `body` key using your model and returns it. | `Model` | ### bringing your own envelope You can create your own Envelope model and logic by inheriting from `BaseEnvelope`, and implementing the `parse` method. diff --git a/tests/events/apiGatewayProxyEvent.json b/tests/events/apiGatewayProxyEvent.json index 1fed04a25bf..8bc72b7ce78 100644 --- a/tests/events/apiGatewayProxyEvent.json +++ b/tests/events/apiGatewayProxyEvent.json @@ -49,10 +49,20 @@ "cognitoIdentityId": null, "cognitoIdentityPoolId": null, "principalOrgId": null, - "sourceIp": "IP", + "sourceIp": "192.168.0.1/32", "user": null, "userAgent": "user-agent", - "userArn": null + "userArn": null, + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } }, "path": "/my/path", "protocol": "HTTP/1.1", @@ -67,4 +77,4 @@ "stageVariables": null, "body": "Hello from Lambda!", "isBase64Encoded": true -} +} \ No newline at end of file diff --git a/tests/functional/parser/schemas.py b/tests/functional/parser/schemas.py index a944b4f09c0..8ff56f703a7 100644 --- a/tests/functional/parser/schemas.py +++ b/tests/functional/parser/schemas.py @@ -81,3 +81,8 @@ class MyKinesisBusiness(BaseModel): class MyCloudWatchBusiness(BaseModel): my_message: str user: str + + +class MyApiGatewayBusiness(BaseModel): + message: str + username: str diff --git a/tests/functional/parser/test_apigw.py b/tests/functional/parser/test_apigw.py new file mode 100644 index 00000000000..333654f3f89 --- /dev/null +++ b/tests/functional/parser/test_apigw.py @@ -0,0 +1,102 @@ +from aws_lambda_powertools.utilities.parser import envelopes, event_parser +from aws_lambda_powertools.utilities.parser.models import APIGatewayProxyEventModel +from aws_lambda_powertools.utilities.typing import LambdaContext +from tests.functional.parser.schemas import MyApiGatewayBusiness +from tests.functional.parser.utils import load_event + + +@event_parser(model=MyApiGatewayBusiness, envelope=envelopes.ApiGatewayEnvelope) +def handle_apigw_with_envelope(event: MyApiGatewayBusiness, _: LambdaContext): + assert event.message == "Hello" + assert event.username == "Ran" + + +@event_parser(model=APIGatewayProxyEventModel) +def handle_apigw_event(event: APIGatewayProxyEventModel, _: LambdaContext): + assert event.body == "Hello from Lambda!" + return event + + +def test_apigw_event_with_envelope(): + event = load_event("apiGatewayProxyEvent.json") + event["body"] = '{"message": "Hello", "username": "Ran"}' + handle_apigw_with_envelope(event, LambdaContext()) + + +def test_apigw_event(): + event = load_event("apiGatewayProxyEvent.json") + parsed_event: APIGatewayProxyEventModel = handle_apigw_event(event, LambdaContext()) + assert parsed_event.version == event["version"] + assert parsed_event.resource == event["resource"] + assert parsed_event.path == event["path"] + assert parsed_event.headers == event["headers"] + assert parsed_event.multiValueHeaders == event["multiValueHeaders"] + assert parsed_event.queryStringParameters == event["queryStringParameters"] + assert parsed_event.multiValueQueryStringParameters == event["multiValueQueryStringParameters"] + + request_context = parsed_event.requestContext + assert request_context.accountId == event["requestContext"]["accountId"] + assert request_context.apiId == event["requestContext"]["apiId"] + + authorizer = request_context.authorizer + assert authorizer.claims is None + assert authorizer.scopes is None + + assert request_context.domainName == event["requestContext"]["domainName"] + assert request_context.domainPrefix == event["requestContext"]["domainPrefix"] + assert request_context.extendedRequestId == event["requestContext"]["extendedRequestId"] + assert request_context.httpMethod == event["requestContext"]["httpMethod"] + + identity = request_context.identity + assert identity.accessKey == event["requestContext"]["identity"]["accessKey"] + assert identity.accountId == event["requestContext"]["identity"]["accountId"] + assert identity.caller == event["requestContext"]["identity"]["caller"] + assert ( + identity.cognitoAuthenticationProvider == event["requestContext"]["identity"]["cognitoAuthenticationProvider"] + ) + assert identity.cognitoAuthenticationType == event["requestContext"]["identity"]["cognitoAuthenticationType"] + assert identity.cognitoIdentityId == event["requestContext"]["identity"]["cognitoIdentityId"] + assert identity.cognitoIdentityPoolId == event["requestContext"]["identity"]["cognitoIdentityPoolId"] + assert identity.principalOrgId == event["requestContext"]["identity"]["principalOrgId"] + assert str(identity.sourceIp) == event["requestContext"]["identity"]["sourceIp"] + assert identity.user == event["requestContext"]["identity"]["user"] + assert identity.userAgent == event["requestContext"]["identity"]["userAgent"] + assert identity.userArn == event["requestContext"]["identity"]["userArn"] + assert identity.clientCert is not None + assert identity.clientCert.clientCertPem == event["requestContext"]["identity"]["clientCert"]["clientCertPem"] + assert identity.clientCert.subjectDN == event["requestContext"]["identity"]["clientCert"]["subjectDN"] + assert identity.clientCert.issuerDN == event["requestContext"]["identity"]["clientCert"]["issuerDN"] + assert identity.clientCert.serialNumber == event["requestContext"]["identity"]["clientCert"]["serialNumber"] + assert ( + identity.clientCert.validity.notBefore + == event["requestContext"]["identity"]["clientCert"]["validity"]["notBefore"] + ) + assert ( + identity.clientCert.validity.notAfter + == event["requestContext"]["identity"]["clientCert"]["validity"]["notAfter"] + ) + + assert request_context.path == event["requestContext"]["path"] + assert request_context.protocol == event["requestContext"]["protocol"] + assert request_context.requestId == event["requestContext"]["requestId"] + assert request_context.requestTime == event["requestContext"]["requestTime"] + convert_time = int(round(request_context.requestTimeEpoch.timestamp() * 1000)) + assert convert_time == 1583349317135 + assert request_context.resourceId == event["requestContext"]["resourceId"] + assert request_context.resourcePath == event["requestContext"]["resourcePath"] + assert request_context.stage == event["requestContext"]["stage"] + + assert parsed_event.pathParameters == event["pathParameters"] + assert parsed_event.stageVariables == event["stageVariables"] + assert parsed_event.body == event["body"] + assert parsed_event.isBase64Encoded == event["isBase64Encoded"] + + assert request_context.connectedAt is None + assert request_context.connectionId is None + assert request_context.eventType is None + assert request_context.messageDirection is None + assert request_context.messageId is None + assert request_context.routeKey is None + assert request_context.operationName is None + assert identity.apiKey is None + assert identity.apiKeyId is None From efd429cbe70c1b3c22462a5ce2dea4239f1b785a Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Sun, 18 Apr 2021 15:06:38 +0300 Subject: [PATCH 2/6] fix renamed model --- .../utilities/parser/envelopes/apigw.py | 6 +++--- docs/utilities/parser.md | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/envelopes/apigw.py b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py index 205ba9af3a2..7e053757772 100644 --- a/aws_lambda_powertools/utilities/parser/envelopes/apigw.py +++ b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py @@ -1,7 +1,7 @@ import logging from typing import Any, Dict, Optional, Type, Union -from ..models import APIGatewayProxyEvent +from ..models import APIGatewayProxyEventModel from ..types import Model from .base import BaseEnvelope @@ -26,7 +26,7 @@ def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) Any Parsed detail payload with model provided """ - logger.debug(f"Parsing incoming data with Api Gateway model {APIGatewayProxyEvent}") - parsed_envelope = APIGatewayProxyEvent.parse_obj(data) + logger.debug(f"Parsing incoming data with Api Gateway model {APIGatewayProxyEventModel}") + parsed_envelope = APIGatewayProxyEventModel.parse_obj(data) logger.debug(f"Parsing event payload in `detail` with {model}") return self._parse(data=parsed_envelope.body, model=model) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index d7481ccb0ac..7fa78a5e372 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -295,16 +295,16 @@ Here's an example of parsing a model found in an event coming from EventBridge, Parser comes with the following built-in envelopes, where `Model` in the return section is your given model. -| Envelope name | Behaviour | Return | -| --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -| **DynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamModel`.
2. Parses records in `NewImage` and `OldImage` keys using your model.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys | `List[Dict[str, Optional[Model]]]` | -| **EventBridgeEnvelope** | 1. Parses data using `EventBridgeModel`.
2. Parses `detail` key using your model and returns it. | `Model` | -| **SqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | -| **CloudWatchLogsEnvelope** | 1. Parses data using `CloudwatchLogsModel` which will base64 decode and decompress it.
2. Parses records in `message` key using your model and return them in a list. | `List[Model]` | -| **KinesisDataStreamEnvelope** | 1. Parses data using `KinesisDataStreamModel` which will base64 decode it.
2. Parses records in in `Records` key using your model and returns them in a list. | `List[Model]` | -| **SnsEnvelope** | 1. Parses data using `SnsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | -| **SnsSqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses SNS records in `body` key using `SnsNotificationModel`.
3. Parses data in `Message` key using your model and return them in a list. | `List[Model]` | -| **ApiGatewayEnvelope** 1. Parses data using `APIGatewayProxyEvent`.
2. Parses `body` key using your model and returns it. | `Model` | +| Envelope name | Behaviour | Return | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| **DynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamModel`.
2. Parses records in `NewImage` and `OldImage` keys using your model.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys | `List[Dict[str, Optional[Model]]]` | +| **EventBridgeEnvelope** | 1. Parses data using `EventBridgeModel`.
2. Parses `detail` key using your model and returns it. | `Model` | +| **SqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | +| **CloudWatchLogsEnvelope** | 1. Parses data using `CloudwatchLogsModel` which will base64 decode and decompress it.
2. Parses records in `message` key using your model and return them in a list. | `List[Model]` | +| **KinesisDataStreamEnvelope** | 1. Parses data using `KinesisDataStreamModel` which will base64 decode it.
2. Parses records in in `Records` key using your model and returns them in a list. | `List[Model]` | +| **SnsEnvelope** | 1. Parses data using `SnsModel`.
2. Parses records in `body` key using your model and return them in a list. | `List[Model]` | +| **SnsSqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses SNS records in `body` key using `SnsNotificationModel`.
3. Parses data in `Message` key using your model and return them in a list. | `List[Model]` | +| **ApiGatewayEnvelope** 1. Parses data using `APIGatewayProxyEventModel`.
2. Parses `body` key using your model and returns it. | `Model` | ### bringing your own envelope You can create your own Envelope model and logic by inheriting from `BaseEnvelope`, and implementing the `parse` method. From 1eba236914c23a5353d2c5c05c416fd124fe364a Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Sun, 18 Apr 2021 15:40:13 +0300 Subject: [PATCH 3/6] fix literal import --- aws_lambda_powertools/utilities/parser/models/apigw.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/apigw.py b/aws_lambda_powertools/utilities/parser/models/apigw.py index d5c2f01e346..1740aee2bf8 100644 --- a/aws_lambda_powertools/utilities/parser/models/apigw.py +++ b/aws_lambda_powertools/utilities/parser/models/apigw.py @@ -1,9 +1,11 @@ from datetime import datetime -from typing import Any, Dict, List, Literal, Optional +from typing import Any, Dict, List, Optional from pydantic import BaseModel, root_validator from pydantic.networks import IPvAnyNetwork +from ..types import Literal + class ApiGatewayUserCertValidity(BaseModel): notBefore: str From a88fb8909e9fd3eefee795e9926ecea2a774ad93 Mon Sep 17 00:00:00 2001 From: Ran Isenberg <60175085+risenberg-cyberark@users.noreply.github.com> Date: Mon, 19 Apr 2021 22:51:58 +0300 Subject: [PATCH 4/6] Update aws_lambda_powertools/utilities/parser/envelopes/apigw.py Co-authored-by: Heitor Lessa --- aws_lambda_powertools/utilities/parser/envelopes/apigw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/envelopes/apigw.py b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py index 7e053757772..6b74a3037e9 100644 --- a/aws_lambda_powertools/utilities/parser/envelopes/apigw.py +++ b/aws_lambda_powertools/utilities/parser/envelopes/apigw.py @@ -9,7 +9,7 @@ class ApiGatewayEnvelope(BaseEnvelope): - """Api Gateway envelope to extract data within detail key""" + """API Gateway envelope to extract data within body key""" def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) -> Optional[Model]: """Parses data found with model provided From ee58a5c5fe6435b2a3ac260599b64dccb9baa199 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Wed, 21 Apr 2021 08:17:22 +0300 Subject: [PATCH 5/6] cr fixes --- .../utilities/parser/models/apigw.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/models/apigw.py b/aws_lambda_powertools/utilities/parser/models/apigw.py index 1740aee2bf8..679c49d9423 100644 --- a/aws_lambda_powertools/utilities/parser/models/apigw.py +++ b/aws_lambda_powertools/utilities/parser/models/apigw.py @@ -91,19 +91,3 @@ def check_message_id(cls, values): if message_id is not None and event_type != "MESSAGE": raise TypeError("messageId is available only when the `eventType` is `MESSAGE`") return values - - @root_validator(pre=True) - def check_both_http_methods(cls, values): - http_method, req_ctx_http_method = values.get("httpMethod"), values.get("requestContext", {}).get( - "httpMethod", "" - ) - if http_method != req_ctx_http_method: - raise TypeError("httpMethods and requestContext.httpMethod must be equal") - return values - - @root_validator(pre=True) - def check_both_paths(cls, values): - path, req_ctx_path = values.get("path"), values.get("requestContext", {}).get("path", "") - if path != req_ctx_path: - raise TypeError("path and requestContext.path must be equal") - return values From 5c67f4057e5d1e8ed73cbc4da93e6328729b96de Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Wed, 21 Apr 2021 19:41:01 +0300 Subject: [PATCH 6/6] remove duplicate --- aws_lambda_powertools/utilities/parser/models/apigw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/apigw.py b/aws_lambda_powertools/utilities/parser/models/apigw.py index 679c49d9423..de968e20ecf 100644 --- a/aws_lambda_powertools/utilities/parser/models/apigw.py +++ b/aws_lambda_powertools/utilities/parser/models/apigw.py @@ -63,7 +63,6 @@ class APIGatewayEventRequestContext(BaseModel): connectedAt: Optional[datetime] connectionId: Optional[str] eventType: Optional[Literal["CONNECT", "MESSAGE", "DISCONNECT"]] - eventType: Optional[str] messageDirection: Optional[str] messageId: Optional[str] routeKey: Optional[str]