From 597f2cf796d2ae7d9f8cfa5d3e5ff3a9673a7523 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 13 Aug 2022 13:25:54 +0100 Subject: [PATCH 01/10] docs(middleware-factory): Refactoring examples - before logic --- docs/utilities/middleware_factory.md | 33 ++++++++----- ...tarted_middleware_before_logic_function.py | 47 +++++++++++++++++++ ...arted_middleware_before_logic_payload.json | 14 ++++++ 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 examples/middleware_factory/src/getting_started_middleware_before_logic_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 6133fb3c8af..96e7cd1632f 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -3,6 +3,8 @@ title: Middleware factory description: Utility --- + + Middleware factory provides a decorator factory to create your own middleware to run logic before, and after each Lambda invocation synchronously. ## Key features @@ -10,6 +12,13 @@ Middleware factory provides a decorator factory to create your own middleware to * Run logic before, after, and handle exceptions * Trace each middleware when requested +## Getting started + +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. + +You might need a middleware factory to abstract non-functional code and focus on business logic or even validate the payload before the lambda run, among other cases. + ## Middleware with no params You can create your own middleware using `lambda_handler_decorator`. The decorator factory expects 3 arguments in your function signature: @@ -18,20 +27,18 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat * **event** - Lambda function invocation event * **context** - Lambda function context object -```python hl_lines="3-4 10" title="Creating your own middleware for before/after logic" -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +### Creating your own middleware for before logic -@lambda_handler_decorator -def middleware_before_after(handler, event, context): - # logic_before_handler_execution() - response = handler(event, context) - # logic_after_handler_execution() - return response +=== "getting_started_middleware_before_logic_function.py" + ```python hl_lines="5 23 24 29 30 32 37 38" + --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_function.py" + ``` -@middleware_before_after -def lambda_handler(event, context): - ... -``` +=== "getting_started_middleware_before_logic_payload.json" + + ```json hl_lines="9-13" + --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json" + ``` ## Middleware with params @@ -73,6 +80,8 @@ def lambda_handler(event, context): When executed, your middleware name will [appear in AWS X-Ray Trace details as](../core/tracer.md) `## middleware_name`. +## Advanced + For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights. ```python hl_lines="6-8" title="Add custom tracing insights before/after in your middlware" diff --git a/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py new file mode 100644 index 00000000000..793d0170932 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass, field +from typing import Callable +from uuid import uuid4 + +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +@dataclass +class Payment: + user_id: str + order_id: str + amount: float + status_id: str + payment_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class PaymentError(Exception): + ... + + +@lambda_handler_decorator +def middleware_before(handler, event, context) -> Callable: + # extract payload from a EventBridge event + detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + + # check if status_id exists in payload, otherwise add default state before processing payment + if not detail.get("status_id"): + event["detail"]["status_id"] = "pending" + + response = handler(event, context) + + return response + + +@middleware_before +def lambda_handler(event, context: LambdaContext) -> dict: + try: + payment_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + return { + "order": Payment(**payment_payload).__dict__, + "message": "payment created", + "success": True, + } + except Exception as e: + raise PaymentError("Unable to create payment") from e diff --git a/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json b/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json new file mode 100644 index 00000000000..21fa5d9b6c7 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json @@ -0,0 +1,14 @@ +{ + "version": "0", + "id": "9c95e8e4-96a4-ef3f-b739-b6aa5b193afb", + "detail-type": "PaymentCreated", + "source": "app.payment", + "account": "0123456789012", + "time": "2022-08-08T20:41:53Z", + "region": "eu-east-1", + "detail": { + "amount": "150.00", + "order_id": "8f1f1710-1b30-48a5-a6bd-153fd23b866b", + "user_id": "f80e3c51-5b8c-49d5-af7d-c7804966235f" + } + } From ec90c5daad74b9b2e12b40f493f04191154b8443 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 13 Aug 2022 15:20:16 +0100 Subject: [PATCH 02/10] docs(middleware-factory): Refactoring examples - after logic --- docs/utilities/middleware_factory.md | 13 +++++++ ...started_middleware_after_logic_function.py | 39 +++++++++++++++++++ ...tarted_middleware_after_logic_payload.json | 6 +++ 3 files changed, 58 insertions(+) create mode 100644 examples/middleware_factory/src/getting_started_middleware_after_logic_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 96e7cd1632f..a7c294e3135 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -40,6 +40,19 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json" ``` +### Creating your own middleware for after logic + +=== "getting_started_middleware_after_logic_function.py" + ```python hl_lines="7 14 15 21-23 37" + --8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_function.py" + ``` + +=== "getting_started_middleware_after_logic_payload.json" + + ```json + --8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json" + ``` + ## Middleware with params You can also have your own keyword arguments after the mandatory arguments. diff --git a/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py b/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py new file mode 100644 index 00000000000..e77328ca8f7 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py @@ -0,0 +1,39 @@ +import time +from typing import Callable + +import requests +from requests import Response + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver() + + +@lambda_handler_decorator +def middleware_after(handler, event, context) -> Callable: + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.post("/todos") +def create_todo() -> dict: + todo_data: dict = app.current_event.json_body # deserialize json str to dict + todo: Response = requests.post("https://jsonplaceholder.typicode.com/todos", data=todo_data) + todo.raise_for_status() + + return {"todo": todo.json()} + + +@middleware_after +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json b/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json new file mode 100644 index 00000000000..e0f775d72df --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json @@ -0,0 +1,6 @@ +{ + "resource": "/todos", + "path": "/todos", + "httpMethod": "POST", + "body": "{\"title\": \"foo\", \"userId\": 1, \"completed\": false}" +} From 5cdf6c62207a009c7d8c90aa5287a2e17ec77042 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 14 Aug 2022 16:47:04 +0100 Subject: [PATCH 03/10] docs(middleware-factory): Refactoring examples - params example --- docs/utilities/middleware_factory.md | 21 +++---- ...started_middleware_with_params_function.py | 59 +++++++++++++++++++ ...tarted_middleware_with_params_payload.json | 23 ++++++++ 3 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 examples/middleware_factory/src/getting_started_middleware_with_params_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_with_params_payload.json diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index a7c294e3135..8cf4897c59b 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -57,21 +57,16 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat You can also have your own keyword arguments after the mandatory arguments. -```python hl_lines="2 12" title="Accepting arbitrary keyword arguments" -@lambda_handler_decorator -def obfuscate_sensitive_data(handler, event, context, fields: List = None): - # Obfuscate email before calling Lambda handler - if fields: - for field in fields: - if field in event: - event[field] = obfuscate(event[field]) +=== "getting_started_middleware_with_params_function.py" + ```python hl_lines="6 27 28 29 33 49" + --8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_function.py" + ``` - return handler(event, context) +=== "getting_started_middleware_with_params_payload.json" -@obfuscate_sensitive_data(fields=["email"]) -def lambda_handler(event, context): - ... -``` + ```json hl_lines="18 19 20" + --8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_payload.json" + ``` ## Tracing middleware execution diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py new file mode 100644 index 00000000000..75b2ae1b925 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py @@ -0,0 +1,59 @@ +import base64 +from dataclasses import dataclass, field +from typing import Callable, List +from uuid import uuid4 + +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +@dataclass +class Booking: + days: int + date_from: str + date_to: str + hotel_id: int + country: str + city: str + guest: dict + booking_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class BookingError(Exception): + ... + + +@lambda_handler_decorator +def obfuscate_sensitive_data(handler, event, context, fields: List = None) -> Callable: + # extracting payload from a EventBridge event + detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + guest_data = detail.get("guest") + + # Obfuscate fields (email, vat, passport) before calling Lambda handler + if fields: + for guest_field in fields: + if guest_field in guest_data: + event["detail"]["guest"][guest_field] = obfuscate_data(guest_data.get(guest_field)) + + response = handler(event, context) + + return response + + +def obfuscate_data(value: str) -> str: + # base64 is not effective for obfuscation, this is an example + return base64.b64encode(value.encode("ascii")) + + +@obfuscate_sensitive_data(fields=["email", "passport", "vat"]) +def lambda_handler(event, context: LambdaContext) -> dict: + try: + booking_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + return { + "book": Booking(**booking_payload).__dict__, + "message": "booking created", + "success": True, + } + except Exception as e: + raise BookingError("Unable to create booking") from e diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json b/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json new file mode 100644 index 00000000000..de6dbc626d3 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json @@ -0,0 +1,23 @@ +{ + "version": "0", + "id": "9c95e8e4-96a4-ef3f-b739-b6aa5b193afb", + "detail-type": "BookingCreated", + "source": "app.booking", + "account": "0123456789012", + "time": "2022-08-08T20:41:53Z", + "region": "eu-east-1", + "detail": { + "days": 5, + "date_from": "2020-08-08", + "date_to": "2020-08-13", + "hotel_id": "1", + "country": "Portugal", + "city": "Lisbon", + "guest": { + "name": "Lambda", + "email": "lambda@powertool.tools", + "passport": "AA123456", + "vat": "123456789" + } + } +} From 42f03c95a2ccbdf87a4a48be1f471df8c75da164 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 14 Aug 2022 18:59:10 +0100 Subject: [PATCH 04/10] docs(middleware-factory): Refactoring examples - advanced tracer --- docs/media/middleware_factory_tracer_1.png | Bin 0 -> 51968 bytes docs/media/middleware_factory_tracer_2.png | Bin 0 -> 80366 bytes docs/utilities/middleware_factory.md | 47 ++++++++++-------- .../advanced_middleware_tracer_function.py | 44 ++++++++++++++++ .../advanced_middleware_tracer_payload.json | 5 ++ ...ting_started_middleware_tracer_function.py | 38 ++++++++++++++ ...ing_started_middleware_tracer_payload.json | 5 ++ 7 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 docs/media/middleware_factory_tracer_1.png create mode 100644 docs/media/middleware_factory_tracer_2.png create mode 100644 examples/middleware_factory/src/advanced_middleware_tracer_function.py create mode 100644 examples/middleware_factory/src/advanced_middleware_tracer_payload.json create mode 100644 examples/middleware_factory/src/getting_started_middleware_tracer_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_tracer_payload.json diff --git a/docs/media/middleware_factory_tracer_1.png b/docs/media/middleware_factory_tracer_1.png new file mode 100644 index 0000000000000000000000000000000000000000..70c1a0c1da15583ccf0722ef273aac187ac8b341 GIT binary patch literal 51968 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 U{{QhX?SH)M|3Ch``sdhx037G94FCWD literal 0 HcmV?d00001 diff --git a/docs/media/middleware_factory_tracer_2.png b/docs/media/middleware_factory_tracer_2.png new file mode 100644 index 0000000000000000000000000000000000000000..54f8917956533ddcaf1eac95484532132e4750a8 GIT binary patch literal 80366 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 Callable: + + tracer.put_metadata(key="resource", value=event.get("resource")) + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + tracer.put_annotation(key="TotalExecutionTime", value=execution_time) + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.get("/products") +def create_product() -> dict: + product: Response = requests.get("https://dummyjson.com/products/1") + product.raise_for_status() + + return {"product": product.json()} + + +@middleware_with_advanced_tracing +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/advanced_middleware_tracer_payload.json b/examples/middleware_factory/src/advanced_middleware_tracer_payload.json new file mode 100644 index 00000000000..d8a89bcfc67 --- /dev/null +++ b/examples/middleware_factory/src/advanced_middleware_tracer_payload.json @@ -0,0 +1,5 @@ +{ + "resource": "/products", + "path": "/products", + "httpMethod": "GET" + } diff --git a/examples/middleware_factory/src/getting_started_middleware_tracer_function.py b/examples/middleware_factory/src/getting_started_middleware_tracer_function.py new file mode 100644 index 00000000000..0c461592254 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_tracer_function.py @@ -0,0 +1,38 @@ +import time +from typing import Callable + +import requests +from requests import Response + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver() + + +@lambda_handler_decorator(trace_execution=True) +def middleware_with_tracing(handler, event, context) -> Callable: + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.get("/products") +def create_product() -> dict: + product: Response = requests.get("https://dummyjson.com/products/1") + product.raise_for_status() + + return {"product": product.json()} + + +@middleware_with_tracing +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json b/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json new file mode 100644 index 00000000000..d8a89bcfc67 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json @@ -0,0 +1,5 @@ +{ + "resource": "/products", + "path": "/products", + "httpMethod": "GET" + } From 4c2a1e7fa8140d8e296a51ed24c04ec72626a058 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 14 Aug 2022 19:18:21 +0100 Subject: [PATCH 05/10] docs(middleware-factory): Refactoring examples - fix typing --- .../src/advanced_middleware_tracer_function.py | 2 +- ...etting_started_middleware_with_params_function.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/middleware_factory/src/advanced_middleware_tracer_function.py b/examples/middleware_factory/src/advanced_middleware_tracer_function.py index 7b183fa0919..05aa65d33c4 100644 --- a/examples/middleware_factory/src/advanced_middleware_tracer_function.py +++ b/examples/middleware_factory/src/advanced_middleware_tracer_function.py @@ -22,7 +22,7 @@ def middleware_with_advanced_tracing(handler, event, context) -> Callable: response = handler(event, context) execution_time = time.time() - start_time - tracer.put_annotation(key="TotalExecutionTime", value=execution_time) + tracer.put_annotation(key="TotalExecutionTime", value=str(execution_time)) # adding custom headers in response object after lambda executing response["headers"]["execution_time"] = execution_time diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py index 75b2ae1b925..f647d6f20c9 100644 --- a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py @@ -1,6 +1,6 @@ import base64 from dataclasses import dataclass, field -from typing import Callable, List +from typing import Any, Callable, List from uuid import uuid4 from aws_lambda_powertools.middleware_factory import lambda_handler_decorator @@ -25,23 +25,23 @@ class BookingError(Exception): @lambda_handler_decorator -def obfuscate_sensitive_data(handler, event, context, fields: List = None) -> Callable: +def obfuscate_sensitive_data(handler, event, context, fields: List) -> Callable: # extracting payload from a EventBridge event detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) - guest_data = detail.get("guest") + guest_data: Any = detail.get("guest") # Obfuscate fields (email, vat, passport) before calling Lambda handler if fields: for guest_field in fields: - if guest_field in guest_data: - event["detail"]["guest"][guest_field] = obfuscate_data(guest_data.get(guest_field)) + if guest_data.get(guest_field): + event["detail"]["guest"][guest_field] = obfuscate_data(str(guest_data.get(guest_field))) response = handler(event, context) return response -def obfuscate_data(value: str) -> str: +def obfuscate_data(value: str) -> bytes: # base64 is not effective for obfuscation, this is an example return base64.b64encode(value.encode("ascii")) From b2e19452258689beab2e56b4619a6f8844231a2b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 15:05:56 +0200 Subject: [PATCH 06/10] fix: navigation and old wording --- docs/utilities/middleware_factory.md | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 614ff7a3207..7618b7273be 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -10,16 +10,16 @@ Middleware factory provides a decorator factory to create your own middleware to ## Key features * Run logic before, after, and handle exceptions -* Trace each middleware when requested +* Built-in tracing opt-in capability ## Getting started ???+ tip All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. -You might need a middleware factory to abstract non-functional code and focus on business logic or even validate the payload before the lambda run, among other cases. +You might need a custom middleware to abstract non-functional code. These are often custom authorization or any reusable logic you might need to run before/after a Lambda function invocation. -## Middleware with no params +### Middleware with no params You can create your own middleware using `lambda_handler_decorator`. The decorator factory expects 3 arguments in your function signature: @@ -27,7 +27,7 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat * **event** - Lambda function invocation event * **context** - Lambda function context object -### Creating your own middleware for before logic +### Middleware with before logic === "getting_started_middleware_before_logic_function.py" ```python hl_lines="5 23 24 29 30 32 37 38" @@ -40,7 +40,7 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json" ``` -### Creating your own middleware for after logic +### Middleware with after logic === "getting_started_middleware_after_logic_function.py" ```python hl_lines="7 14 15 21-23 37" @@ -53,7 +53,7 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat --8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json" ``` -## Middleware with params +### Middleware with params You can also have your own keyword arguments after the mandatory arguments. @@ -68,7 +68,24 @@ You can also have your own keyword arguments after the mandatory arguments. --8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_payload.json" ``` -## Tracing middleware execution +## Advanced + +For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights. + +=== "advanced_middleware_tracer_function.py" + ```python hl_lines="7 9 12 16 17 19 25 42" + --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_function.py" + ``` + +=== "advanced_middleware_tracer_payload.json" + + ```json + --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_payload.json" + ``` + +![Middleware avanced Tracer](../media/middleware_factory_tracer_2.png) + +### Tracing middleware **execution** If you are making use of [Tracer](../core/tracer.md), you can trace the execution of your middleware to ease operations. @@ -92,23 +109,6 @@ When executed, your middleware name will [appear in AWS X-Ray Trace details as]( ![Middleware simple Tracer](../media/middleware_factory_tracer_1.png) -## Advanced - -For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights. - -=== "advanced_middleware_tracer_function.py" - ```python hl_lines="7 9 12 16 17 19 25 42" - --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_function.py" - ``` - -=== "advanced_middleware_tracer_payload.json" - - ```json - --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_payload.json" - ``` - -![Middleware avanced Tracer](../media/middleware_factory_tracer_2.png) - ## Tips * Use `trace_execution` to quickly understand the performance impact of your middlewares, and reduce or merge tasks when necessary From 54a0600ab846a15124472bad3008b6bfdb8ebc70 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 20 Aug 2022 19:28:48 +0100 Subject: [PATCH 07/10] docs(middleware-factory): Refactoring examples - combining utilities --- docs/utilities/middleware_factory.md | 35 ++++- ...mbining_powertools_utilities_template.yaml | 136 ++++++++++++++++++ .../combining_powertools_utilities_event.json | 79 ++++++++++ ...combining_powertools_utilities_function.py | 121 ++++++++++++++++ .../combining_powertools_utilities_schema.py | 25 ++++ 5 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 examples/middleware_factory/sam/combining_powertools_utilities_template.yaml create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_event.json create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_function.py create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_schema.py diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 7618b7273be..75901771c71 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -83,7 +83,7 @@ For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside y --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_payload.json" ``` -![Middleware avanced Tracer](../media/middleware_factory_tracer_2.png) +![Middleware advanced Tracer](../media/middleware_factory_tracer_2.png) ### Tracing middleware **execution** @@ -109,6 +109,39 @@ When executed, your middleware name will [appear in AWS X-Ray Trace details as]( ![Middleware simple Tracer](../media/middleware_factory_tracer_1.png) +### Combining Powertools utilities + +You create your own middleware and combine many features of Lambda Powertools such as [trace](../core/logger.md), [logs](../core/logger.md), [feature flags](feature_flags.md), [validation](validation.md), [jmespath_functions](jmespath_functions.md) and others to abstract non-functional code. + +In the example below, we create a Middleware with the following features: + +* Logs and traces +* Validate if payload contain a specific header +* Extract specific keys from event +* Add automatically security headers in response of all executions +* Validate if a feature flag is enabled +* Generate execution history and save to a DynamoDB table + +=== "combining_powertools_utilities_function.py" + ```python hl_lines="8 14 15 36" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_function.py" + ``` + +=== "combining_powertools_utilities_schema.py" + ```python hl_lines="12 14" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_schema.py" + ``` + +=== "combining_powertools_utilities_event.json" + ```python hl_lines="10" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_event.json" + ``` + +=== "SAM TEMPLATE" + ```python hl_lines="8 14 15 36" + --8<-- "examples/middleware_factory/sam/combining_powertools_utilities_template.yaml" + ``` + ## Tips * Use `trace_execution` to quickly understand the performance impact of your middlewares, and reduce or merge tasks when necessary diff --git a/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml new file mode 100644 index 00000000000..aa20ae615d8 --- /dev/null +++ b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml @@ -0,0 +1,136 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Middleware-powertools-utilities example + +Globals: + Function: + Timeout: 5 + Runtime: python3.9 + Tracing: Active + Architectures: + - x86_64 + Environment: + Variables: + LOG_LEVEL: DEBUG + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_SERVICE_NAME: middleware + +Resources: + MiddlewareFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: middleware/ + Handler: app.lambda_handler + Description: Middleware function + Policies: + - AWSLambdaBasicExecutionRole # Managed Policy + - Version: '2012-10-17' # Policy Document + Statement: + - Effect: Allow + Action: + - dynamodb:PutItem + Resource: !GetAtt HistoryTable.Arn + - Effect: Allow + Action: # https://docs.aws.amazon.com/appconfig/latest/userguide/getting-started-with-appconfig-permissions.html + - ssm:GetDocument + - ssm:ListDocuments + - appconfig:GetLatestConfiguration + - appconfig:StartConfigurationSession + - appconfig:ListApplications + - appconfig:GetApplication + - appconfig:ListEnvironments + - appconfig:GetEnvironment + - appconfig:ListConfigurationProfiles + - appconfig:GetConfigurationProfile + - appconfig:ListDeploymentStrategies + - appconfig:GetDeploymentStrategy + - appconfig:GetConfiguration + - appconfig:ListDeployments + - appconfig:GetDeployment + Resource: "*" + Events: + GetComments: + Type: Api + Properties: + Path: /comments + Method: GET + GetCommentsById: + Type: Api + Properties: + Path: /comments/{comment_id} + Method: GET + + # DYNAMODB TABLE FOR HISTORIC + HistoryTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: "HistoryTable" + AttributeDefinitions: + - AttributeName: customer_id + AttributeType: S + - AttributeName: request_id + AttributeType: S + KeySchema: + - AttributeName: customer_id + KeyType: HASH + - AttributeName: request_id + KeyType: "RANGE" + BillingMode: PAY_PER_REQUEST + + # APPCONFIG FOR FEATURE FLAGS + FeatureCommentApp: + Type: AWS::AppConfig::Application + Properties: + Description: "Comments Application for feature toggles" + Name: comments + + FeatureCommentDevEnv: + Type: AWS::AppConfig::Environment + Properties: + ApplicationId: !Ref FeatureCommentApp + Description: "Development Environment for the App Config Comments" + Name: dev + + FeatureCommentConfigProfile: + Type: AWS::AppConfig::ConfigurationProfile + Properties: + ApplicationId: !Ref FeatureCommentApp + Name: features + LocationUri: "hosted" + + HostedConfigVersion: + Type: AWS::AppConfig::HostedConfigurationVersion + Properties: + ApplicationId: !Ref FeatureCommentApp + ConfigurationProfileId: !Ref FeatureCommentConfigProfile + Description: 'A sample hosted configuration version' + Content: | + { + "save_history": { + "default": true + } + } + ContentType: 'application/json' + + # this is just an example + # change this values according your deployment strategy + BasicDeploymentStrategy: + Type: AWS::AppConfig::DeploymentStrategy + Properties: + Name: "Deployment" + Description: "Deployment strategy for comments app." + DeploymentDurationInMinutes: 1 + FinalBakeTimeInMinutes: 1 + GrowthFactor: 100 + GrowthType: LINEAR + ReplicateTo: NONE + + ConfigDeployment: + Type: AWS::AppConfig::Deployment + Properties: + ApplicationId: !Ref FeatureCommentApp + ConfigurationProfileId: !Ref FeatureCommentConfigProfile + ConfigurationVersion: !Ref HostedConfigVersion + DeploymentStrategyId: !Ref BasicDeploymentStrategy + EnvironmentId: !Ref FeatureCommentDevEnv diff --git a/examples/middleware_factory/src/combining_powertools_utilities_event.json b/examples/middleware_factory/src/combining_powertools_utilities_event.json new file mode 100644 index 00000000000..74257f56411 --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_event.json @@ -0,0 +1,79 @@ +{ + "body":"None", + "headers":{ + "Accept":"*/*", + "Accept-Encoding":"gzip, deflate, br", + "Connection":"keep-alive", + "Host":"127.0.0.1:3001", + "Postman-Token":"a9d49365-ebe1-4bb0-8627-d5e37cdce86d", + "User-Agent":"PostmanRuntime/7.29.0", + "X-Customer-Id":"1", + "X-Forwarded-Port":"3001", + "X-Forwarded-Proto":"http" + }, + "httpMethod":"GET", + "isBase64Encoded":false, + "multiValueHeaders":{ + "Accept":[ + "*/*" + ], + "Accept-Encoding":[ + "gzip, deflate, br" + ], + "Connection":[ + "keep-alive" + ], + "Host":[ + "127.0.0.1:3001" + ], + "Postman-Token":[ + "a9d49365-ebe1-4bb0-8627-d5e37cdce86d" + ], + "User-Agent":[ + "PostmanRuntime/7.29.0" + ], + "X-Customer-Id":[ + "1" + ], + "X-Forwarded-Port":[ + "3001" + ], + "X-Forwarded-Proto":[ + "http" + ] + }, + "multiValueQueryStringParameters":"None", + "path":"/comments", + "pathParameters":"None", + "queryStringParameters":"None", + "requestContext":{ + "accountId":"123456789012", + "apiId":"1234567890", + "domainName":"127.0.0.1:3001", + "extendedRequestId":"None", + "httpMethod":"GET", + "identity":{ + "accountId":"None", + "apiKey":"None", + "caller":"None", + "cognitoAuthenticationProvider":"None", + "cognitoAuthenticationType":"None", + "cognitoIdentityPoolId":"None", + "sourceIp":"127.0.0.1", + "user":"None", + "userAgent":"Custom User Agent String", + "userArn":"None" + }, + "path":"/comments", + "protocol":"HTTP/1.1", + "requestId":"56d1a102-6d9d-4f13-b4f7-26751c10a131", + "requestTime":"20/Aug/2022:18:18:58 +0000", + "requestTimeEpoch":1661019538, + "resourceId":"123456", + "resourcePath":"/comments", + "stage":"Prod" + }, + "resource":"/comments", + "stageVariables":"None", + "version":"1.0" + } diff --git a/examples/middleware_factory/src/combining_powertools_utilities_function.py b/examples/middleware_factory/src/combining_powertools_utilities_function.py new file mode 100644 index 00000000000..3b546ea240e --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_function.py @@ -0,0 +1,121 @@ +import json +from typing import Callable + +import boto3 +import combining_powertools_utilities_schema as schemas +import requests + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.event_handler.exceptions import InternalServerError +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.shared.types import JSONType +from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags +from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + +app = APIGatewayRestResolver() +tracer = Tracer() +logger = Logger() + +table_historic = boto3.resource("dynamodb").Table("HistoricTable") + +app_config = AppConfigStore(environment="dev", application="comments", name="features") +feature_flags = FeatureFlags(store=app_config) + + +@lambda_handler_decorator(trace_execution=True) +def middleware_custom(handler: Callable, event: dict, context: LambdaContext): + + # validating the INPUT with the given schema + # X-Customer-Id header must be informed in all requests + try: + validate(event=event, schema=schemas.INPUT) + except SchemaValidationError as e: + return { + "statusCode": 400, + "body": json.dumps(str(e)), + } + + # extracting headers and requestContext from event + headers = extract_data_from_envelope(data=event, envelope="headers") + request_context = extract_data_from_envelope(data=event, envelope="requestContext") + + logger.debug(f"X-Customer-Id => {headers.get('X-Customer-Id')}") + tracer.put_annotation(key="CustomerId", value=headers.get("X-Customer-Id")) + + response = handler(event, context) + + # automatically adding security headers to all responses + # see: https://securityheaders.com/ + logger.info("Injecting security headers") + response["headers"]["Referrer-Policy"] = "no-referrer" + response["headers"]["Strict-Transport-Security"] = "max-age=15552000; includeSubDomains; preload" + response["headers"]["X-DNS-Prefetch-Control"] = "off" + response["headers"]["X-Content-Type-Options"] = "nosniff" + response["headers"]["X-Permitted-Cross-Domain-Policies"] = "none" + response["headers"]["X-Download-Options"] = "noopen" + + logger.info("Saving api call in history table") + save_api_execution_history(str(event.get("path")), headers, request_context) + + # return lambda execution + return response + + +@tracer.capture_method +def save_api_execution_history(path: str, headers: dict, request_context: dict) -> None: + + try: + # using the feature flags utility to check if the new feature "save api call to history" is enabled by default + # see: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/feature_flags/#static-flags + save_history: JSONType = feature_flags.evaluate(name="save_history", default=False) + if save_history: + # saving history in dynamodb table + tracer.put_metadata(key="execution detail", value=request_context) + table_historic.put_item( + Item={ + "customer_id": headers.get("X-Customer-Id"), + "request_id": request_context.get("requestId"), + "path": path, + "request_time": request_context.get("requestTime"), + "source_ip": request_context.get("identity", {}).get("sourceIp"), + "http_method": request_context.get("httpMethod"), + } + ) + + return None + except Exception: + # you can add more logic here to handle exceptions or even save this to a DLQ + # but not to make this example too long, we just return None since the Lambda has been successfully executed + return None + + +@app.get("/comments") +@tracer.capture_method +def get_comments(): + try: + comments: requests.Response = requests.get("https://jsonplaceholder.typicode.com/comments") + comments.raise_for_status() + + return {"comments": comments.json()[:10]} + except Exception as exc: + raise InternalServerError(str(exc)) + + +@app.get("/comments/") +@tracer.capture_method +def get_comments_by_id(comment_id: str): + try: + comments: requests.Response = requests.get(f"https://jsonplaceholder.typicode.com/comments/{comment_id}") + comments.raise_for_status() + + return {"comments": comments.json()} + except Exception as exc: + raise InternalServerError(str(exc)) + + +@middleware_custom +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/combining_powertools_utilities_schema.py b/examples/middleware_factory/src/combining_powertools_utilities_schema.py new file mode 100644 index 00000000000..7a1978a71a3 --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_schema.py @@ -0,0 +1,25 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1661012141.json", + "title": "Root", + "type": "object", + "required": ["headers"], + "properties": { + "headers": { + "$id": "#root/headers", + "title": "Headers", + "type": "object", + "required": ["X-Customer-Id"], + "properties": { + "X-Customer-Id": { + "$id": "#root/headers/X-Customer-Id", + "title": "X-customer-id", + "type": "string", + "default": "", + "examples": ["1"], + "pattern": "^.*$", + } + }, + } + }, +} From fbe67c771fe21dd1e342ca6c75d04053f427f53c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 21 Aug 2022 17:38:52 +0100 Subject: [PATCH 08/10] docs(middleware-factory): Refactoring examples - combining utilities --- docs/utilities/middleware_factory.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 75901771c71..989cfaf1ae5 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -111,19 +111,19 @@ When executed, your middleware name will [appear in AWS X-Ray Trace details as]( ### Combining Powertools utilities -You create your own middleware and combine many features of Lambda Powertools such as [trace](../core/logger.md), [logs](../core/logger.md), [feature flags](feature_flags.md), [validation](validation.md), [jmespath_functions](jmespath_functions.md) and others to abstract non-functional code. +You can create your own middleware and combine many features of Lambda Powertools such as [trace](../core/logger.md), [logs](../core/logger.md), [feature flags](feature_flags.md), [validation](validation.md), [jmespath_functions](jmespath_functions.md) and others to abstract non-functional code. In the example below, we create a Middleware with the following features: * Logs and traces -* Validate if payload contain a specific header +* Validate if the payload contains a specific header * Extract specific keys from event -* Add automatically security headers in response of all executions -* Validate if a feature flag is enabled -* Generate execution history and save to a DynamoDB table +* Automatically add security headers on every execution +* Validate if a specific feature flag is enabled +* Save execution history to a DynamoDB table === "combining_powertools_utilities_function.py" - ```python hl_lines="8 14 15 36" + ```python hl_lines="11 28 29 119 52 61 73" --8<-- "examples/middleware_factory/src/combining_powertools_utilities_function.py" ``` From 79b7b63d1054160d1bc2756147b8a04430529a96 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 21 Aug 2022 18:03:14 +0100 Subject: [PATCH 09/10] docs(middleware-factory): Refactoring examples - combining utilities - highlights --- docs/utilities/middleware_factory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 989cfaf1ae5..731beb9e088 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -138,7 +138,7 @@ In the example below, we create a Middleware with the following features: ``` === "SAM TEMPLATE" - ```python hl_lines="8 14 15 36" + ```python hl_lines="66 83 89 96 103 108-113 119 130" --8<-- "examples/middleware_factory/sam/combining_powertools_utilities_template.yaml" ``` From 78e7bd25f1b5206ee347f5b9d8be552c289f6ac1 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 22 Aug 2022 11:22:47 +0100 Subject: [PATCH 10/10] docs(middleware-factory): Refactoring examples - minor changes --- docs/utilities/middleware_factory.md | 2 +- .../sam/combining_powertools_utilities_template.yaml | 4 ++-- .../getting_started_middleware_before_logic_function.py | 2 +- .../src/getting_started_middleware_with_params_function.py | 7 +++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/utilities/middleware_factory.md b/docs/utilities/middleware_factory.md index 731beb9e088..70157ca1286 100644 --- a/docs/utilities/middleware_factory.md +++ b/docs/utilities/middleware_factory.md @@ -92,7 +92,7 @@ If you are making use of [Tracer](../core/tracer.md), you can trace the executio This makes use of an existing Tracer instance that you may have initialized anywhere in your code. ???+ warning - You must enable Active Tracing in your Lambda function when using this feature, otherwise Lambda will not be able to send traces to XRay.. + You must [enable Active Tracing](../core/tracer/#permissions) in your Lambda function when using this feature, otherwise Lambda cannot send traces to XRay. === "getting_started_middleware_tracer_function.py" ```python hl_lines="8 14 15 36" diff --git a/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml index aa20ae615d8..4ee87e379cd 100644 --- a/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml +++ b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml @@ -61,7 +61,7 @@ Resources: Path: /comments/{comment_id} Method: GET - # DYNAMODB TABLE FOR HISTORIC + # DynamoDB table to store historical data HistoryTable: Type: AWS::DynamoDB::Table Properties: @@ -78,7 +78,7 @@ Resources: KeyType: "RANGE" BillingMode: PAY_PER_REQUEST - # APPCONFIG FOR FEATURE FLAGS + # Feature flags using AppConfig FeatureCommentApp: Type: AWS::AppConfig::Application Properties: diff --git a/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py index 793d0170932..7d5ee035e7b 100644 --- a/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py +++ b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py @@ -26,7 +26,7 @@ def middleware_before(handler, event, context) -> Callable: detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) # check if status_id exists in payload, otherwise add default state before processing payment - if not detail.get("status_id"): + if "status_id" not in detail: event["detail"]["status_id"] = "pending" response = handler(event, context) diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py index f647d6f20c9..ce800e9162f 100644 --- a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py @@ -31,10 +31,9 @@ def obfuscate_sensitive_data(handler, event, context, fields: List) -> Callable: guest_data: Any = detail.get("guest") # Obfuscate fields (email, vat, passport) before calling Lambda handler - if fields: - for guest_field in fields: - if guest_data.get(guest_field): - event["detail"]["guest"][guest_field] = obfuscate_data(str(guest_data.get(guest_field))) + for guest_field in fields: + if guest_data.get(guest_field): + event["detail"]["guest"][guest_field] = obfuscate_data(str(guest_data.get(guest_field))) response = handler(event, context)