From 13558d9d001a76d91e3be0400dae184b12ef2ea0 Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:08:00 +0000 Subject: [PATCH 01/36] chore(ci): update changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b85db5304d..c6f5fe65e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Bug Fixes +* **ci:** move from pip-tools to poetry on layers to fix conflicts * **ci:** typo and bust gh actions cache * **ci:** use poetry to resolve layer deps; pip for CDK * **ci:** disable poetry venv for layer workflow as cdk ignores venv @@ -17,10 +18,14 @@ ## Documentation +* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) +* **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 ## Maintenance +* **ci:** reduce payload and only send prod notification +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From 40ce5091a8fb0ee49a3c441d97333ebeb080e3fd Mon Sep 17 00:00:00 2001 From: Peter Schutt Date: Wed, 10 Aug 2022 00:09:15 +1000 Subject: [PATCH 02/36] docs(apigateway): removes duplicate admonition (#1426) --- docs/core/event_handler/api_gateway.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 1358f545eb8..f4f45a051f8 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -135,9 +135,6 @@ Each dynamic route you set must be part of your function signature. This allows ???+ tip You can also nest dynamic paths, for example `/todos//`. -???+ tip - You can also nest dynamic paths, for example `/todos//`. - #### Catch-all routes ???+ note From 5951ab0210a8f33ed30bbffd0587f3353ea8c06f Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:09:54 +0000 Subject: [PATCH 03/36] chore(ci): update changelog with latest changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f5fe65e71..9fc78d3dae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,12 +18,14 @@ ## Documentation +* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) * **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 ## Maintenance +* **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From 6b2390356d56428b2a35ef0810bb2b15e579bec8 Mon Sep 17 00:00:00 2001 From: Peter Schutt Date: Wed, 10 Aug 2022 00:11:08 +1000 Subject: [PATCH 04/36] docs(parser): minor grammar fix (#1427) --- docs/utilities/parser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 8756725d1e0..cb69cf9699b 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -524,7 +524,7 @@ Parser is best suited for those looking for a trade-off between defining their m We export most common classes, exceptions, and utilities from Pydantic as part of parser e.g. `from aws_lambda_powertools.utilities.parser import BaseModel`. -If what's your trying to use isn't available as part of the high level import system, use the following escape hatch mechanism: +If what you're trying to use isn't available as part of the high level import system, use the following escape hatch mechanism: ```python title="Pydantic import escape hatch" from aws_lambda_powertools.utilities.parser.pydantic import From 6380b6365176ed3140febdefff7e739a81811a8a Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:11:42 +0000 Subject: [PATCH 05/36] chore(ci): update changelog with latest changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc78d3dae1..9a738104460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,11 @@ * **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 +* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) ## Maintenance +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes From 8e24ae3feee39ae0d51bb47033d147bce2027846 Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:59:06 +0000 Subject: [PATCH 06/36] chore(ci): update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a738104460..27d160ec75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ## Maintenance +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification From 9f45be92efaeb2e468be69b2a220cb3c7938b66c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 11:35:41 +0100 Subject: [PATCH 07/36] docs(validation): Refactoring examples - validator decorator --- docs/utilities/validation.md | 36 +++++------ ...ng_started_validator_decorator_function.py | 26 ++++++++ ...g_started_validator_decorator_payload.json | 6 ++ ...ting_started_validator_decorator_schema.py | 60 +++++++++++++++++++ 4 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 examples/validation/src/getting_started_validator_decorator_function.py create mode 100644 examples/validation/src/getting_started_validator_decorator_payload.json create mode 100644 examples/validation/src/getting_started_validator_decorator_schema.py diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index ec795c99bef..90def9052f4 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -3,6 +3,8 @@ title: Validation description: Utility --- + + This utility provides JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. ## Key features @@ -18,6 +20,9 @@ This utility provides JSON Schema validation for events and responses, including You can validate inbound and outbound events using [`validator` decorator](#validator-decorator). +???+ 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 can also use the standalone `validate` function, if you want more control over the validation process such as handling a validation error. We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/){target="_blank"} library. @@ -31,31 +36,22 @@ We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.g It will fail fast with `SchemaValidationError` exception if event or response doesn't conform with given JSON Schema. -=== "validator_decorator.py" +=== "getting_started_validator_decorator_function.py" - ```python hl_lines="3 5" - from aws_lambda_powertools.utilities.validation import validator + ```python hl_lines="1 6" + --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" + ``` - import schemas +=== "getting_started_validator_decorator_schema.py" - @validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) - def handler(event, context): - return event - ``` + ```python hl_lines="1 6" + --8<-- "examples/validation/src/getting_started_validator_decorator_schema.py" + ``` -=== "event.json" +=== "getting_started_validator_decorator_payload.json" - ```json - { - "message": "hello world", - "username": "lessa" - } - ``` - -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json hl_lines="6 15" + --8<-- "examples/validation/src/getting_started_validator_decorator_payload.json" ``` ???+ note diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py new file mode 100644 index 00000000000..484e102dbb5 --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass, field +from uuid import uuid4 + +import getting_start_validator_schema as schemas +from faker import Faker + +from aws_lambda_powertools.utilities.validation import validator + + +@dataclass +class User: + ip: str + permissions: list + user_id: str = field(default_factory=lambda: f"{uuid4()}") + name: str = field(default_factory=lambda: f"{Faker().name()}") + + +@validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) +def lambda_handler(event, context) -> dict: + + user_details: dict = {} + + if event.get("username") == "lambda" and event.get("password") == "powertools": + user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ + + return {"body": user_details or None, "statusCode": 200 if user_details else 204} diff --git a/examples/validation/src/getting_started_validator_decorator_payload.json b/examples/validation/src/getting_started_validator_decorator_payload.json new file mode 100644 index 00000000000..d5dc512d984 --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_payload.json @@ -0,0 +1,6 @@ + +{ + "username": "lambda", + "password": "powertools", + "ip": "192.168.0.1" +} diff --git a/examples/validation/src/getting_started_validator_decorator_schema.py b/examples/validation/src/getting_started_validator_decorator_schema.py new file mode 100644 index 00000000000..ddee0b13352 --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_schema.py @@ -0,0 +1,60 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"username": "hello world", "password": "lessa", "ip": "192.168.0.1"}], + "required": ["username", "password", "ip"], + "properties": { + "username": { + "$id": "#/properties/username", + "type": "string", + "title": "The username", + "examples": ["lambda"], + "maxLength": 30, + }, + "password": { + "$id": "#/properties/password", + "type": "string", + "title": "The password", + "examples": ["powertools"], + "maxLength": 30, + }, + "ip": { + "$id": "#/properties/ip", + "type": "string", + "title": "The ip", + "format": "ipv4", + "examples": ["192.168.0.1"], + "maxLength": 30, + }, + }, +} + +OUTPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample outgoing schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"statusCode": 200, "body": {}}], + "required": ["statusCode", "body"], + "properties": { + "statusCode": { + "$id": "#/properties/statusCode", + "type": "integer", + "title": "The statusCode", + "examples": [200], + "maxLength": 3, + }, + "body": { + "$id": "#/properties/body", + "type": "object", + "title": "The body", + "examples": [ + '{"ip": "192.168.0.1", "permissions": ["read", "write"], "user_id": "7576b683-295e-4f69-b558-70e789de1b18", "name": "Christopher Dunn"}' # noqa E501 + ], + }, + }, +} From 4af1ddd70a426673539b863092b1abdb3d6bc290 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 11:38:42 +0100 Subject: [PATCH 08/36] docs(validation): Refactoring examples - validator decorator --- docs/utilities/validation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 90def9052f4..ce250ecb38f 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -38,19 +38,19 @@ It will fail fast with `SchemaValidationError` exception if event or response do === "getting_started_validator_decorator_function.py" - ```python hl_lines="1 6" + ```python hl_lines="4 7 18" --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" ``` === "getting_started_validator_decorator_schema.py" - ```python hl_lines="1 6" + ```python hl_lines="7 8 10 12 17 19 24 26 28 41 42 44 46 51 53" --8<-- "examples/validation/src/getting_started_validator_decorator_schema.py" ``` === "getting_started_validator_decorator_payload.json" - ```json hl_lines="6 15" + ```json --8<-- "examples/validation/src/getting_started_validator_decorator_payload.json" ``` From 460b68db2df2adae0dcea2d2ad6f389e889ee066 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 12:11:47 +0100 Subject: [PATCH 09/36] docs(validation): Refactoring examples - validator standalone --- docs/utilities/validation.md | 35 ++++++------------- ...ng_started_validator_decorator_function.py | 2 ++ ...g_started_validator_standalone_function.py | 23 ++++++++++++ ..._started_validator_standalone_payload.json | 6 ++++ ...ing_started_validator_standalone_schema.py | 33 +++++++++++++++++ 5 files changed, 74 insertions(+), 25 deletions(-) create mode 100644 examples/validation/src/getting_started_validator_standalone_function.py create mode 100644 examples/validation/src/getting_started_validator_standalone_payload.json create mode 100644 examples/validation/src/getting_started_validator_standalone_schema.py diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index ce250ecb38f..42bf9827a6c 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -63,37 +63,22 @@ It will fail fast with `SchemaValidationError` exception if event or response do You can also gracefully handle schema validation errors by catching `SchemaValidationError` exception. -=== "validator_decorator.py" +=== "getting_started_validator_standalone_function.py" - ```python hl_lines="8" - from aws_lambda_powertools.utilities.validation import validate - from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError + ```python hl_lines="4 7 18" + --8<-- "examples/validation/src/getting_started_validator_standalone_function.py" + ``` - import schemas +=== "getting_started_validator_standalone_schema.py" - def handler(event, context): - try: - validate(event=event, schema=schemas.INPUT) - except SchemaValidationError as e: - # do something before re-raising - raise + ```python hl_lines="7 8 10 12 17 19 24 26 28 41 42 44 46 51 53" + --8<-- "examples/validation/src/getting_started_validator_standalone_schema.py" + ``` - return event - ``` - -=== "event.json" +=== "getting_started_validator_standalone_payload.json" ```json - { - "data": "hello world", - "username": "lessa" - } - ``` - -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + --8<-- "examples/validation/src/getting_started_validator_standalone_payload.json" ``` ### Unwrapping events prior to validation diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index 484e102dbb5..e02ce36ac40 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -15,6 +15,7 @@ class User: name: str = field(default_factory=lambda: f"{Faker().name()}") +# using a decorator to validate input and output data @validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) def lambda_handler(event, context) -> dict: @@ -23,4 +24,5 @@ def lambda_handler(event, context) -> dict: if event.get("username") == "lambda" and event.get("password") == "powertools": user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ + # the body must be a object because must match OUTPUT schema, otherwise it fails return {"body": user_details or None, "statusCode": 200 if user_details else 204} diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py new file mode 100644 index 00000000000..2dffb056924 --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -0,0 +1,23 @@ +import getting_started_validator_standalone_schema as schemas + +from aws_lambda_powertools.utilities.validation import validate +from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError + + +def lambda_handler(event, context) -> dict: + + try: + + user_authenticated: str = "" + + # using standalone function to validate input data only + validate(event=event, schema=schemas.INPUT) + + if event.get("username") == "lambda" and event.get("password") == "powertools": + user_authenticated = "Authenticated" + + # in this example the body can be a string because we are not validating the OUTPUT + return {"body": user_authenticated, "statusCode": 200 if user_authenticated else 204} + except SchemaValidationError as exception: + # SchemaValidationError indicates where a data mismatch is + return {"body": str(exception), "statusCode": 400} diff --git a/examples/validation/src/getting_started_validator_standalone_payload.json b/examples/validation/src/getting_started_validator_standalone_payload.json new file mode 100644 index 00000000000..d5dc512d984 --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_payload.json @@ -0,0 +1,6 @@ + +{ + "username": "lambda", + "password": "powertools", + "ip": "192.168.0.1" +} diff --git a/examples/validation/src/getting_started_validator_standalone_schema.py b/examples/validation/src/getting_started_validator_standalone_schema.py new file mode 100644 index 00000000000..2a2429172d4 --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_schema.py @@ -0,0 +1,33 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"username": "hello world", "password": "lessa", "ip": "192.168.0.1"}], + "required": ["username", "password", "ip"], + "properties": { + "username": { + "$id": "#/properties/username", + "type": "string", + "title": "The username", + "examples": ["lambda"], + "maxLength": 30, + }, + "password": { + "$id": "#/properties/password", + "type": "string", + "title": "The password", + "examples": ["powertools"], + "maxLength": 30, + }, + "ip": { + "$id": "#/properties/ip", + "type": "string", + "title": "The ip", + "format": "ipv4", + "examples": ["192.168.0.1"], + "maxLength": 30, + }, + }, +} From 310cb3fe4f79db18a7ba3d0f12a894e19f33123c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 12:15:23 +0100 Subject: [PATCH 10/36] docs(validation): Refactoring examples - validator standalone --- docs/utilities/validation.md | 6 +++--- .../src/getting_started_validator_standalone_function.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 42bf9827a6c..65c4b8c3a63 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -38,7 +38,7 @@ It will fail fast with `SchemaValidationError` exception if event or response do === "getting_started_validator_decorator_function.py" - ```python hl_lines="4 7 18" + ```python hl_lines="4 7 19 27" --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" ``` @@ -65,13 +65,13 @@ You can also gracefully handle schema validation errors by catching `SchemaValid === "getting_started_validator_standalone_function.py" - ```python hl_lines="4 7 18" + ```python hl_lines="3 4 12 17" --8<-- "examples/validation/src/getting_started_validator_standalone_function.py" ``` === "getting_started_validator_standalone_schema.py" - ```python hl_lines="7 8 10 12 17 19 24 26 28 41 42 44 46 51 53" + ```python hl_lines="7 8 10 12 17 19 24 26 28" --8<-- "examples/validation/src/getting_started_validator_standalone_schema.py" ``` diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py index 2dffb056924..56103db0a79 100644 --- a/examples/validation/src/getting_started_validator_standalone_function.py +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -5,9 +5,7 @@ def lambda_handler(event, context) -> dict: - try: - user_authenticated: str = "" # using standalone function to validate input data only From 42a9bfd21f1a86f5128613573929defec24795b9 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 12:21:26 +0100 Subject: [PATCH 11/36] docs(jmespath_utils): Highlights --- docs/utilities/jmespath_functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/jmespath_functions.md b/docs/utilities/jmespath_functions.md index 45250ea0fcd..209bf4fffe9 100644 --- a/docs/utilities/jmespath_functions.md +++ b/docs/utilities/jmespath_functions.md @@ -134,7 +134,7 @@ This sample will decode the base64 value within the `data` key, and deserialize === "powertools_base64_jmespath_function.py" - ```python hl_lines="7 10 37 48 52 54 56" + ```python hl_lines="7 10 37 49 53 55 57" --8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_function.py" ``` From e80bf63bae622449cb72ddcd04024fde82252ab8 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 16:10:05 +0100 Subject: [PATCH 12/36] docs(validation): Refactoring examples - validator standalone --- .../src/getting_started_validator_standalone_function.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py index 56103db0a79..07988406828 100644 --- a/examples/validation/src/getting_started_validator_standalone_function.py +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -1,7 +1,6 @@ import getting_started_validator_standalone_schema as schemas -from aws_lambda_powertools.utilities.validation import validate -from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate def lambda_handler(event, context) -> dict: From cb85138e823804c23ccbb4eaba43a092e3f63e64 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 16:17:48 +0100 Subject: [PATCH 13/36] docs(validation): Refactoring examples - validator unwrap data --- docs/utilities/validation.md | 32 ++++------ ...ng_started_validator_unwraping_function.py | 33 +++++++++++ ...g_started_validator_unwraping_payload.json | 20 +++++++ ...ting_started_validator_unwraping_schema.py | 59 +++++++++++++++++++ 4 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 examples/validation/src/getting_started_validator_unwraping_function.py create mode 100644 examples/validation/src/getting_started_validator_unwraping_payload.json create mode 100644 examples/validation/src/getting_started_validator_unwraping_schema.py diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 65c4b8c3a63..ca07ca7effc 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -65,7 +65,7 @@ You can also gracefully handle schema validation errors by catching `SchemaValid === "getting_started_validator_standalone_function.py" - ```python hl_lines="3 4 12 17" + ```python hl_lines="3 10 16 18" --8<-- "examples/validation/src/getting_started_validator_standalone_function.py" ``` @@ -89,30 +89,22 @@ Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extr Here is a sample custom EventBridge event, where we only validate what's inside the `detail` key: -=== "unwrapping_events.py" +=== "getting_started_validator_unwraping_function.py" - We use the `envelope` parameter to extract the payload inside the `detail` key before validating. - - ```python hl_lines="5" - from aws_lambda_powertools.utilities.validation import validator - - import schemas - - @validator(inbound_schema=schemas.INPUT, envelope="detail") - def handler(event, context): - return event - ``` + ```python hl_lines="8 10 12 13 24 26" + --8<-- "examples/validation/src/getting_started_validator_unwraping_function.py" + ``` -=== "sample_wrapped_event.json" +=== "getting_started_validator_unwraping_schema.py" - ```python hl_lines="11-14" - --8<-- "docs/shared/validation_basic_eventbridge_event.json" - ``` + ```python hl_lines="8 10 12 24 26 " + --8<-- "examples/validation/src/getting_started_validator_unwraping_schema.py" + ``` -=== "schemas.py" +=== "getting_started_validator_unwraping_payload.json" - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json hl_lines="11-18" + --8<-- "examples/validation/src/getting_started_validator_unwraping_payload.json" ``` This is quite powerful because you can use JMESPath Query language to extract records from [arrays](https://jmespath.org/tutorial.html#list-and-slice-projections), combine [pipe](https://jmespath.org/tutorial.html#pipe-expressions) and [function expressions](https://jmespath.org/tutorial.html#functions). diff --git a/examples/validation/src/getting_started_validator_unwraping_function.py b/examples/validation/src/getting_started_validator_unwraping_function.py new file mode 100644 index 00000000000..3b8e03f1fbf --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwraping_function.py @@ -0,0 +1,33 @@ +import boto3 +import getting_started_validator_unwraping_schema as schemas + +from aws_lambda_powertools.utilities.validation import validator + +s3_client = boto3.resource("s3") + + +# using a decorator to validate input data +@validator(inbound_schema=schemas.INPUT, envelope="data") +def lambda_handler(event, context): + + try: + data_detail: dict = event.get("detail", {}) + s3_object = s3_client.Object(data_detail.get("s3_bucket"), data_detail.get("s3_key")) + content = s3_object.get()["Body"].read().decode("utf-8") + + # return data processed + return {"message": process_data_object(content), "success": True} + + except s3_client.meta.client.exceptions.NoSuchBucket as exception: + return return_error_message(str(exception)) + except s3_client.meta.client.exceptions.NoSuchKey as exception: + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"message": message, "success": False} + + +def process_data_object(content: str) -> str: + # insert logic here + return "Data OK" diff --git a/examples/validation/src/getting_started_validator_unwraping_payload.json b/examples/validation/src/getting_started_validator_unwraping_payload.json new file mode 100644 index 00000000000..47544db3d60 --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwraping_payload.json @@ -0,0 +1,20 @@ + +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" + ], + "data": { + "detail": { + "s3_bucket": "aws-lambda-powertools", + "s3_key": "folder/event.txt", + "file_size": 200, + "file_type": "text/plain" + } + } +} diff --git a/examples/validation/src/getting_started_validator_unwraping_schema.py b/examples/validation/src/getting_started_validator_unwraping_schema.py new file mode 100644 index 00000000000..baa02ad66ad --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwraping_schema.py @@ -0,0 +1,59 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660222326.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [ + { + "detail": { + "s3_bucket": "aws-lambda-powertools", + "s3_key": "event.txt", + "file_size": 200, + "file_type": "text/plain", + } + } + ], + "required": ["detail"], + "properties": { + "detail": { + "$id": "#root/detail", + "title": "Detail", + "type": "object", + "required": ["s3_bucket", "s3_key", "file_size", "file_type"], + "properties": { + "s3_bucket": { + "$id": "#root/detail/s3_bucket", + "title": "The S3 Bucker", + "type": "string", + "default": "", + "examples": ["aws-lambda-powertools"], + "pattern": "^.*$", + }, + "s3_key": { + "$id": "#root/detail/s3_key", + "title": "The S3 Key", + "type": "string", + "default": "", + "examples": ["folder/event.txt"], + "pattern": "^.*$", + }, + "file_size": { + "$id": "#root/detail/file_size", + "title": "The file size", + "type": "integer", + "examples": [200], + "default": 0, + }, + "file_type": { + "$id": "#root/detail/file_type", + "title": "The file type", + "type": "string", + "default": "", + "examples": ["text/plain"], + "pattern": "^.*$", + }, + }, + } + }, +} From 6b099ec6d23fdd2171a81c7f7ec4d9852a7ad255 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 16:18:54 +0100 Subject: [PATCH 14/36] docs(validation): Refactoring examples - validator unwrap data --- docs/utilities/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index ca07ca7effc..c3dc79536e3 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -91,7 +91,7 @@ Here is a sample custom EventBridge event, where we only validate what's inside === "getting_started_validator_unwraping_function.py" - ```python hl_lines="8 10 12 13 24 26" + ```python hl_lines="2 4 9 10 21 23" --8<-- "examples/validation/src/getting_started_validator_unwraping_function.py" ``` From 073ab160b6f9b3640c37ce3a610bfd46ef814d17 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 16:22:57 +0100 Subject: [PATCH 15/36] docs(validation): Refactoring examples - validator unwrap data - payload --- .../getting_started_validator_unwraping_payload.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/validation/src/getting_started_validator_unwraping_payload.json b/examples/validation/src/getting_started_validator_unwraping_payload.json index 47544db3d60..dde10c1fcf2 100644 --- a/examples/validation/src/getting_started_validator_unwraping_payload.json +++ b/examples/validation/src/getting_started_validator_unwraping_payload.json @@ -1,14 +1,4 @@ - { - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "detail-type": "Scheduled Event", - "source": "aws.events", - "account": "123456789012", - "time": "1970-01-01T00:00:00Z", - "region": "us-east-1", - "resources": [ - "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" - ], "data": { "detail": { "s3_bucket": "aws-lambda-powertools", From 1640d0a1476fca3752c1138bb5c8c913d1591bd4 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 17:16:48 +0100 Subject: [PATCH 16/36] docs(validation): Refactoring examples - validator unwrap popular events --- docs/utilities/validation.md | 32 ++++++++----------- ...nwrapping_popular_event_source_function.py | 20 ++++++++++++ ...wrapping_popular_event_source_payload.json | 16 ++++++++++ .../unwrapping_popular_event_source_schema.py | 26 +++++++++++++++ 4 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 examples/validation/src/unwrapping_popular_event_source_function.py create mode 100644 examples/validation/src/unwrapping_popular_event_source_payload.json create mode 100644 examples/validation/src/unwrapping_popular_event_source_schema.py diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index c3dc79536e3..c6169cde8d4 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -97,13 +97,13 @@ Here is a sample custom EventBridge event, where we only validate what's inside === "getting_started_validator_unwraping_schema.py" - ```python hl_lines="8 10 12 24 26 " + ```python hl_lines="9-14 23 25 28 33 36 41 44 48 51" --8<-- "examples/validation/src/getting_started_validator_unwraping_schema.py" ``` === "getting_started_validator_unwraping_payload.json" - ```json hl_lines="11-18" + ```json --8<-- "examples/validation/src/getting_started_validator_unwraping_payload.json" ``` @@ -115,28 +115,22 @@ When combined, these features allow you to extract what you need before validati This utility comes with built-in envelopes to easily extract the payload from popular event sources. -=== "unwrapping_popular_event_sources.py" - - ```python hl_lines="5 7" - from aws_lambda_powertools.utilities.validation import envelopes, validator +=== "unwrapping_popular_event_source_function.py" - import schemas - - @validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) - def handler(event, context): - return event - ``` + ```python hl_lines="2 5 8 9 19" + --8<-- "examples/validation/src/unwrapping_popular_event_source_function.py" + ``` -=== "sample_wrapped_event.json" +=== "unwrapping_popular_event_source_schema.py" - ```python hl_lines="11-14" - --8<-- "docs/shared/validation_basic_eventbridge_event.json" - ``` + ```python hl_lines="7 9 12 17 20" + --8<-- "examples/validation/src/unwrapping_popular_event_source_schema.py" + ``` -=== "schemas.py" +=== "unwrapping_popular_event_source_payload.json" - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json hl_lines="12 13" + --8<-- "examples/validation/src/unwrapping_popular_event_source_payload.json" ``` Here is a handy table with built-in envelopes along with their JMESPath expressions in case you want to build your own. diff --git a/examples/validation/src/unwrapping_popular_event_source_function.py b/examples/validation/src/unwrapping_popular_event_source_function.py new file mode 100644 index 00000000000..3dc271bc043 --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_function.py @@ -0,0 +1,20 @@ +import boto3 +import unwrapping_popular_event_source_schema as schemas +from botocore.exceptions import ClientError + +from aws_lambda_powertools.utilities.validation import envelopes, validator + + +# extracting detail from eventbridge custom event +@validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) +def lambda_handler(event, context): + try: + ec2_client = boto3.resource("ec2", region_name=event.get("region")) + instance_id = event.get("instance_id") + instance = ec2_client.Instance(instance_id) + instance.stop() + + return {"message": f"Successfully stopped {instance_id}", "success": True} + + except ClientError as exception: + return {"message": str(exception), "success": False} diff --git a/examples/validation/src/unwrapping_popular_event_source_payload.json b/examples/validation/src/unwrapping_popular_event_source_payload.json new file mode 100644 index 00000000000..271e0be5b27 --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_payload.json @@ -0,0 +1,16 @@ + +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" + ], + "detail": { + "instance_id": "i-042dd005362091826", + "region": "us-east-2" + } +} diff --git a/examples/validation/src/unwrapping_popular_event_source_schema.py b/examples/validation/src/unwrapping_popular_event_source_schema.py new file mode 100644 index 00000000000..0c5cc746250 --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_schema.py @@ -0,0 +1,26 @@ +INPUT = { + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660233148.json", + "title": "Root", + "type": "object", + "required": ["instance_id", "region"], + "properties": { + "instance_id": { + "$id": "#root/instance_id", + "title": "Instance_id", + "type": "string", + "default": "", + "examples": ["i-042dd005362091826"], + "pattern": "^.*$", + }, + "region": { + "$id": "#root/region", + "title": "Region", + "type": "string", + "default": "", + "examples": ["us-east-1"], + "pattern": "^.*$", + }, + }, +} From 18a3c55c88de0cfcd8f1fa696c754bfcf75f74af Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 19:29:23 +0100 Subject: [PATCH 17/36] fix(jmespath_util): snappy as dev dep and typing example (#1446) --- .../data_classes/lambda_function_url_event.py | 2 +- .../powertools_json_idempotency_jmespath.py | 4 +- mypy.ini | 5 +- poetry.lock | 64 ++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py b/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py index 2b88918f17b..01c1a83f5db 100644 --- a/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py +++ b/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py @@ -7,7 +7,7 @@ class LambdaFunctionUrlEvent(APIGatewayProxyEventV2): Notes: ----- Lambda Function URL follows the API Gateway HTTP APIs Payload Format Version 2.0. - + Keys related to API Gateway features not available in Function URL use a sentinel value (e.g.`routeKey`, `stage`). Documentation: diff --git a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py index 15880dedff3..aaf5724b54b 100644 --- a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py +++ b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py @@ -20,8 +20,8 @@ class PaymentError(Exception): def handler(event, context) -> dict: body = json.loads(event["body"]) try: - payment = create_subscription_payment(user=body["user"], product_id=body["product_id"]) - return {"payment_id": payment.id, "message": "success", "statusCode": 200} + payment: dict = create_subscription_payment(user=body["user"], product_id=body["product_id"]) + return {"payment_id": payment.get("id"), "message": "success", "statusCode": 200} except requests.HTTPError as e: raise PaymentError("Unable to create payment subscription") from e diff --git a/mypy.ini b/mypy.ini index 8274442fe4b..4da15d3898a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -42,4 +42,7 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-aiohttp] -ignore_missing_imports = True \ No newline at end of file +ignore_missing_imports = True + +[mypy-snappy] +ignore_missing_imports = True diff --git a/poetry.lock b/poetry.lock index 96d9c4e560b..8719b1c9a07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -645,8 +645,8 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -test = ["flake8 (>=3.0)", "coverage"] -dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] +dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] +test = ["coverage", "flake8 (>=3.0)"] [[package]] name = "mkdocs" @@ -1087,6 +1087,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-snappy" +version = "0.6.1" +description = "Python library for the snappy compression library from Google" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "pyyaml" version = "5.4.1" @@ -1328,7 +1336,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "575cd7f9ff3a989898ec6f9944aab56b4e08964a37173d49b34e1e1bbc6a3d39" +content-hash = "70c0562fda81dd5aa851acd9176d62387d696e100456403e2f165db542ca8833" [metadata.files] atomicwrites = [ @@ -1846,6 +1854,56 @@ 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"}, ] +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"}, + {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ec533a8c1f8df797bded662ec3e494d225b37855bb63eb0d75464a07947477c"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6f8bf4708a11b47517baf962f9a02196478bbb10fdb9582add4aa1459fa82380"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d0c019ee7dcf2c60e240877107cddbd95a5b1081787579bf179938392d66480"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb18d9cd7b3f35a2f5af47bb8ed6a5bdbf4f3ddee37f3daade4ab7864c292f5b"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b265cde49774752aec9ca7f5d272e3f98718164afc85521622a8a5394158a2b5"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d017775851a778ec9cc32651c4464079d06d927303c2dde9ae9830ccf6fe94e1"}, + {file = "python_snappy-0.6.1-cp310-cp310-win32.whl", hash = "sha256:8277d1f6282463c40761f802b742f833f9f2449fcdbb20a96579aa05c8feb614"}, + {file = "python_snappy-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2aaaf618c68d8c9daebc23a20436bd01b09ee70d7fbf7072b7f38b06d2fab539"}, + {file = "python_snappy-0.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:277757d5dad4e239dc1417438a0871b65b1b155beb108888e7438c27ffc6a8cc"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e066a0586833d610c4bbddba0be5ba0e3e4f8e0bc5bb6d82103d8f8fc47bb59a"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0d489b50f49433494160c45048fe806de6b3aeab0586e497ebd22a0bab56e427"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463fd340a499d47b26ca42d2f36a639188738f6e2098c6dbf80aef0e60f461e1"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9837ac1650cc68d22a3cf5f15fb62c6964747d16cecc8b22431f113d6e39555d"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e973e637112391f05581f427659c05b30b6843bc522a65be35ac7b18ce3dedd"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:c20498bd712b6e31a4402e1d027a1cd64f6a4a0066a3fe3c7344475886d07fdf"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59e975be4206cc54d0a112ef72fa3970a57c2b1bcc2c97ed41d6df0ebe518228"}, + {file = "python_snappy-0.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2a7e528ab6e09c0d67dcb61a1730a292683e5ff9bb088950638d3170cf2a0a54"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39692bedbe0b717001a99915ac0eb2d9d0bad546440d392a2042b96d813eede1"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a7620404da966f637b9ce8d4d3d543d363223f7a12452a575189c5355fc2d25"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7778c224efc38a40d274da4eb82a04cac27aae20012372a7db3c4bbd8926c4d4"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d029f7051ec1bbeaa3e03030b6d8ed47ceb69cae9016f493c802a08af54e026"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ad38bc98d0b0497a0b0dbc29409bcabfcecff4511ed7063403c86de16927bc"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:5a453c45178d7864c1bdd6bfe0ee3ed2883f63b9ba2c9bb967c6b586bf763f96"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9f0c0d88b84259f93c3aa46398680646f2c23e43394779758d9f739c34e15295"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb05c28298803a74add08ba496879242ef159c75bc86a5406fac0ffc7dd021b"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9eac51307c6a1a38d5f86ebabc26a889fddf20cbba7a116ccb54ba1446601d5b"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:88b6ea78b83d2796f330b0af1b70cdd3965dbdab02d8ac293260ec2c8fe340ee"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c07220408d3268e8268c9351c5c08041bc6f8c6172e59d398b71020df108541"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4038019b1bcaadde726a57430718394076c5a21545ebc5badad2c045a09546cf"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc96668d9c7cc656609764275c5f8da58ef56d89bdd6810f6923d36497468ff7"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf5bb9254e1c38aacf253d510d3d9be631bba21f3d068b17672b38b5cbf2fff5"}, + {file = "python_snappy-0.6.1-cp38-cp38-win32.whl", hash = "sha256:eaf905a580f2747c4a474040a5063cd5e0cc3d1d2d6edb65f28196186493ad4a"}, + {file = "python_snappy-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:546c1a7470ecbf6239101e9aff0f709b68ca0f0268b34d9023019a55baa1f7c6"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3a013895c64352b49d0d8e107a84f99631b16dbab156ded33ebf0becf56c8b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fb9a88a4dd6336488f3de67ce75816d0d796dce53c2c6e4d70e0b565633c7fd"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:735cd4528c55dbe4516d6d2b403331a99fc304f8feded8ae887cf97b67d589bb"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:90b0186516b7a101c14764b0c25931b741fb0102f21253eff67847b4742dfc72"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a993dc8aadd901915a510fe6af5f20ae4256f527040066c22a154db8946751f"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:530bfb9efebcc1aab8bb4ebcbd92b54477eed11f6cf499355e882970a6d3aa7d"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5843feb914796b1f0405ccf31ea0fb51034ceb65a7588edfd5a8250cb369e3b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-win32.whl", hash = "sha256:66c80e9b366012dbee262bb1869e4fc5ba8786cda85928481528bc4a72ec2ee8"}, + {file = "python_snappy-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d3cafdf454354a621c8ab7408e45aa4e9d5c0b943b61ff4815f71ca6bdf0130"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586724a0276d7a6083a17259d0b51622e492289a9998848a1b01b6441ca12b2f"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2be4f4550acd484912441f5f1209ba611ac399aac9355fee73611b9a0d4f949c"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdb6942180660bda7f7d01f4c0def3cfc72b1c6d99aad964801775a3e379aba"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:03bb511380fca2a13325b6f16fe8234c8e12da9660f0258cd45d9a02ffc916af"}, +] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, diff --git a/pyproject.toml b/pyproject.toml index 1b04b223f00..c85786c40db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ mypy-boto3-lambda = "^1.24.0" mypy-boto3-xray = "^1.24.0" types-requests = "^2.28.7" typing-extensions = { version = "^4.3.0", python = ">=3.7" } +python-snappy = "^0.6.1" [tool.poetry.extras] pydantic = ["pydantic", "email-validator"] From 998b414fd38d5ac10acccd78c87ca8a13a5c48a5 Mon Sep 17 00:00:00 2001 From: Release bot Date: Thu, 11 Aug 2022 18:35:56 +0000 Subject: [PATCH 18/36] chore(ci): update changelog with latest changes --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d160ec75f..ae964561674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,16 @@ ## Bug Fixes +* **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers to fix conflicts * **ci:** typo and bust gh actions cache * **ci:** use poetry to resolve layer deps; pip for CDK * **ci:** disable poetry venv for layer workflow as cdk ignores venv * **ci:** add cdk v2 dep for layers workflow -* **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers * **ci:** temporarily disable changelog upon release * **ci:** add explicit origin to fix release detached head +* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) ## Documentation @@ -29,6 +30,7 @@ * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From 643363dd59911fc77729b4fb65a100bec5e89859 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 19:57:44 +0100 Subject: [PATCH 19/36] docs(validation): Refactoring examples - fix classes --- .../src/getting_started_validator_decorator_function.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index e02ce36ac40..81eeb78646d 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -2,7 +2,6 @@ from uuid import uuid4 import getting_start_validator_schema as schemas -from faker import Faker from aws_lambda_powertools.utilities.validation import validator @@ -12,7 +11,7 @@ class User: ip: str permissions: list user_id: str = field(default_factory=lambda: f"{uuid4()}") - name: str = field(default_factory=lambda: f"{Faker().name()}") + name: str = "Project Lambda Powertools" # using a decorator to validate input and output data From 08df88728e3f96ed5cfa3b12d5dcb6498670809c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 12 Aug 2022 01:49:52 +0100 Subject: [PATCH 20/36] docs(validation): Refactoring examples - custom format --- docs/utilities/validation.md | 240 ++---------------- .../getting_started_custom_format_function.py | 35 +++ ...getting_started_custom_format_payload.json | 4 + .../getting_started_custom_format_schema.py | 26 ++ ...ng_started_validator_decorator_function.py | 2 +- 5 files changed, 82 insertions(+), 225 deletions(-) create mode 100644 examples/validation/src/getting_started_custom_format_function.py create mode 100644 examples/validation/src/getting_started_custom_format_payload.json create mode 100644 examples/validation/src/getting_started_custom_format_schema.py diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index c6169cde8d4..413ded15324 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -38,7 +38,7 @@ It will fail fast with `SchemaValidationError` exception if event or response do === "getting_started_validator_decorator_function.py" - ```python hl_lines="4 7 19 27" + ```python hl_lines="4 6 18 26" --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" ``` @@ -153,243 +153,35 @@ Here is a handy table with built-in envelopes along with their JMESPath expressi ???+ note JSON Schema DRAFT 7 [has many new built-in formats](https://json-schema.org/understanding-json-schema/reference/string.html#format){target="_blank"} such as date, time, and specifically a regex format which might be a better replacement for a custom format, if you do have control over the schema. -JSON Schemas with custom formats like `int64` will fail validation. If you have these, you can pass them using `formats` parameter: +JSON Schemas with custom formats like `awsaccountid` will fail validation. If you have these, you can pass them using `formats` parameter: ```json title="custom_json_schema_type_format.json" { - "lastModifiedTime": { - "format": "int64", - "type": "integer" + "accountid": { + "format": "awsaccountid", + "type": "string" } } ``` For each format defined in a dictionary key, you must use a regex, or a function that returns a boolean to instruct the validator on how to proceed when encountering that type. -=== "validate_custom_format.py" +=== "getting_started_custom_format_function.py" - ```python hl_lines="5-8 10" - from aws_lambda_powertools.utilities.validation import validate - - import schema - - custom_format = { - "int64": True, # simply ignore it, - "positive": lambda x: False if x < 0 else True - } + ```python hl_lines="6 8 10 11 16 17 28 30" + --8<-- "examples/validation/src/getting_started_custom_format_function.py" + ``` - validate(event=event, schema=schemas.INPUT, formats=custom_format) - ``` +=== "getting_started_custom_format_schema.py" -=== "schemas.py" - - ```python hl_lines="68" 91 93" - INPUT = { - "$schema": "http://json-schema.org/draft-04/schema#", - "definitions": { - "AWSAPICallViaCloudTrail": { - "properties": { - "additionalEventData": {"$ref": "#/definitions/AdditionalEventData"}, - "awsRegion": {"type": "string"}, - "errorCode": {"type": "string"}, - "errorMessage": {"type": "string"}, - "eventID": {"type": "string"}, - "eventName": {"type": "string"}, - "eventSource": {"type": "string"}, - "eventTime": {"format": "date-time", "type": "string"}, - "eventType": {"type": "string"}, - "eventVersion": {"type": "string"}, - "recipientAccountId": {"type": "string"}, - "requestID": {"type": "string"}, - "requestParameters": {"$ref": "#/definitions/RequestParameters"}, - "resources": {"items": {"type": "object"}, "type": "array"}, - "responseElements": {"type": ["object", "null"]}, - "sourceIPAddress": {"type": "string"}, - "userAgent": {"type": "string"}, - "userIdentity": {"$ref": "#/definitions/UserIdentity"}, - "vpcEndpointId": {"type": "string"}, - "x-amazon-open-api-schema-readOnly": {"type": "boolean"}, - }, - "required": [ - "eventID", - "awsRegion", - "eventVersion", - "responseElements", - "sourceIPAddress", - "eventSource", - "requestParameters", - "resources", - "userAgent", - "readOnly", - "userIdentity", - "eventType", - "additionalEventData", - "vpcEndpointId", - "requestID", - "eventTime", - "eventName", - "recipientAccountId", - ], - "type": "object", - }, - "AdditionalEventData": { - "properties": { - "objectRetentionInfo": {"$ref": "#/definitions/ObjectRetentionInfo"}, - "x-amz-id-2": {"type": "string"}, - }, - "required": ["x-amz-id-2"], - "type": "object", - }, - "Attributes": { - "properties": { - "creationDate": {"format": "date-time", "type": "string"}, - "mfaAuthenticated": {"type": "string"}, - }, - "required": ["mfaAuthenticated", "creationDate"], - "type": "object", - }, - "LegalHoldInfo": { - "properties": { - "isUnderLegalHold": {"type": "boolean"}, - "lastModifiedTime": {"format": "int64", "type": "integer"}, - }, - "type": "object", - }, - "ObjectRetentionInfo": { - "properties": { - "legalHoldInfo": {"$ref": "#/definitions/LegalHoldInfo"}, - "retentionInfo": {"$ref": "#/definitions/RetentionInfo"}, - }, - "type": "object", - }, - "RequestParameters": { - "properties": { - "bucketName": {"type": "string"}, - "key": {"type": "string"}, - "legal-hold": {"type": "string"}, - "retention": {"type": "string"}, - }, - "required": ["bucketName", "key"], - "type": "object", - }, - "RetentionInfo": { - "properties": { - "lastModifiedTime": {"format": "int64", "type": "integer"}, - "retainUntilMode": {"type": "string"}, - "retainUntilTime": {"format": "int64", "type": "integer"}, - }, - "type": "object", - }, - "SessionContext": { - "properties": {"attributes": {"$ref": "#/definitions/Attributes"}}, - "required": ["attributes"], - "type": "object", - }, - "UserIdentity": { - "properties": { - "accessKeyId": {"type": "string"}, - "accountId": {"type": "string"}, - "arn": {"type": "string"}, - "principalId": {"type": "string"}, - "sessionContext": {"$ref": "#/definitions/SessionContext"}, - "type": {"type": "string"}, - }, - "required": ["accessKeyId", "sessionContext", "accountId", "principalId", "type", "arn"], - "type": "object", - }, - }, - "properties": { - "account": {"type": "string"}, - "detail": {"$ref": "#/definitions/AWSAPICallViaCloudTrail"}, - "detail-type": {"type": "string"}, - "id": {"type": "string"}, - "region": {"type": "string"}, - "resources": {"items": {"type": "string"}, "type": "array"}, - "source": {"type": "string"}, - "time": {"format": "date-time", "type": "string"}, - "version": {"type": "string"}, - }, - "required": ["detail-type", "resources", "id", "source", "time", "detail", "region", "version", "account"], - "title": "AWSAPICallViaCloudTrail", - "type": "object", - "x-amazon-events-detail-type": "AWS API Call via CloudTrail", - "x-amazon-events-source": "aws.s3", - } - ``` + ```python hl_lines="7 9 12 13 17 20" + --8<-- "examples/validation/src/getting_started_custom_format_schema.py" + ``` -=== "event.json" +=== "getting_started_custom_format_payload.json" - ```json - { - "account": "123456789012", - "detail": { - "additionalEventData": { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 0, - "x-amz-id-2": "ejUr9Nd/4IO1juF/a6GOcu+PKrVX6dOH6jDjQOeCJvtARUqzxrhHGrhEt04cqYtAZVqcSEXYqo0=", - }, - "awsRegion": "us-west-1", - "eventCategory": "Data", - "eventID": "be4fdb30-9508-4984-b071-7692221899ae", - "eventName": "HeadObject", - "eventSource": "s3.amazonaws.com", - "eventTime": "2020-12-22T10:05:29Z", - "eventType": "AwsApiCall", - "eventVersion": "1.07", - "managementEvent": False, - "readOnly": True, - "recipientAccountId": "123456789012", - "requestID": "A123B1C123D1E123", - "requestParameters": { - "Host": "lambda-artifacts-deafc19498e3f2df.s3.us-west-1.amazonaws.com", - "bucketName": "lambda-artifacts-deafc19498e3f2df", - "key": "path1/path2/path3/file.zip", - }, - "resources": [ - { - "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df/path1/path2/path3/file.zip", - "type": "AWS::S3::Object", - }, - { - "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df", - "accountId": "123456789012", - "type": "AWS::S3::Bucket", - }, - ], - "responseElements": None, - "sourceIPAddress": "AWS Internal", - "userAgent": "AWS Internal", - "userIdentity": { - "accessKeyId": "ABCDEFGHIJKLMNOPQR12", - "accountId": "123456789012", - "arn": "arn:aws:sts::123456789012:assumed-role/role-name1/1234567890123", - "invokedBy": "AWS Internal", - "principalId": "ABCDEFGHIJKLMN1OPQRST:1234567890123", - "sessionContext": { - "attributes": {"creationDate": "2020-12-09T09:58:24Z", "mfaAuthenticated": "false"}, - "sessionIssuer": { - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:role/role-name1", - "principalId": "ABCDEFGHIJKLMN1OPQRST", - "type": "Role", - "userName": "role-name1", - }, - }, - "type": "AssumedRole", - }, - "vpcEndpointId": "vpce-a123cdef", - }, - "detail-type": "AWS API Call via CloudTrail", - "id": "e0bad426-0a70-4424-b53a-eb902ebf5786", - "region": "us-west-1", - "resources": [], - "source": "aws.s3", - "time": "2020-12-22T10:05:29Z", - "version": "0", - } + ```json hl_lines="12 13" + --8<-- "examples/validation/src/getting_started_custom_format_payload.json" ``` ### Built-in JMESPath functions diff --git a/examples/validation/src/getting_started_custom_format_function.py b/examples/validation/src/getting_started_custom_format_function.py new file mode 100644 index 00000000000..698e6ae80ee --- /dev/null +++ b/examples/validation/src/getting_started_custom_format_function.py @@ -0,0 +1,35 @@ +import json +import re + +import boto3 +import botocore +import getting_started_custom_format_schema as schemas + +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + +# awsaccountid must have 12 digits +custom_format = {"awsaccountid": lambda value: re.match(r"^(\d{12})$", value)} + + +def lambda_handler(event, context) -> dict: + try: + # validate input using custom json format + validate(event=event, schema=schemas.INPUT, formats=custom_format) + + client_organization = boto3.client("organizations", region_name=event.get("region")) + account_data = client_organization.describe_account(AccountId=event.get("accountid")) + + return { + "account": json.dumps(account_data.get("Account"), default=str), + "message": "Success", + "statusCode": 200, + } + + except botocore.exceptions.ClientError as exception: + return return_error_message(str(exception)) + except SchemaValidationError as exception: + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"account": None, "message": message, "statusCode": 400} diff --git a/examples/validation/src/getting_started_custom_format_payload.json b/examples/validation/src/getting_started_custom_format_payload.json new file mode 100644 index 00000000000..8f0607f94b0 --- /dev/null +++ b/examples/validation/src/getting_started_custom_format_payload.json @@ -0,0 +1,4 @@ +{ + "accountid": "200984112386", + "region": "us-east-1" +} diff --git a/examples/validation/src/getting_started_custom_format_schema.py b/examples/validation/src/getting_started_custom_format_schema.py new file mode 100644 index 00000000000..e06a4b35e2d --- /dev/null +++ b/examples/validation/src/getting_started_custom_format_schema.py @@ -0,0 +1,26 @@ +INPUT = { + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660245931.json", + "title": "Root", + "type": "object", + "required": ["accountid", "region"], + "properties": { + "accountid": { + "$id": "#root/accountid", + "title": "The accountid", + "type": "string", + "format": "awsaccountid", + "default": "", + "examples": ["123456789012"], + }, + "region": { + "$id": "#root/region", + "title": "The region", + "type": "string", + "default": "", + "examples": ["us-east-1"], + "pattern": "^.*$", + }, + }, +} diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index 81eeb78646d..26bd1e0c73a 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from uuid import uuid4 -import getting_start_validator_schema as schemas +import getting_started_validator_decorator_schema as schemas from aws_lambda_powertools.utilities.validation import validator From 82d7dcb8b70ed94808146892f387d48df3f011af Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 12 Aug 2022 09:19:21 +0100 Subject: [PATCH 21/36] docs(validation): Refactoring examples - custom format --- .../validation/src/getting_started_custom_format_function.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/validation/src/getting_started_custom_format_function.py b/examples/validation/src/getting_started_custom_format_function.py index 698e6ae80ee..69c692660f6 100644 --- a/examples/validation/src/getting_started_custom_format_function.py +++ b/examples/validation/src/getting_started_custom_format_function.py @@ -2,7 +2,6 @@ import re import boto3 -import botocore import getting_started_custom_format_schema as schemas from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate @@ -25,10 +24,10 @@ def lambda_handler(event, context) -> dict: "statusCode": 200, } - except botocore.exceptions.ClientError as exception: - return return_error_message(str(exception)) except SchemaValidationError as exception: return return_error_message(str(exception)) + except Exception as exception: + return return_error_message(str(exception)) def return_error_message(message: str) -> dict: From f90a3ca419b6e6a89ab7379e78b0fc7a0e170d21 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 13 Aug 2022 13:29:32 +0100 Subject: [PATCH 22/36] docs(validation): Fixing typing --- .../validation/src/getting_started_custom_format_function.py | 3 ++- .../src/getting_started_validator_decorator_function.py | 3 ++- .../src/getting_started_validator_standalone_function.py | 3 ++- .../src/getting_started_validator_unwraping_function.py | 3 ++- .../validation/src/unwrapping_popular_event_source_function.py | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/validation/src/getting_started_custom_format_function.py b/examples/validation/src/getting_started_custom_format_function.py index 69c692660f6..02dd16e97e0 100644 --- a/examples/validation/src/getting_started_custom_format_function.py +++ b/examples/validation/src/getting_started_custom_format_function.py @@ -4,13 +4,14 @@ import boto3 import getting_started_custom_format_schema as schemas +from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate # awsaccountid must have 12 digits custom_format = {"awsaccountid": lambda value: re.match(r"^(\d{12})$", value)} -def lambda_handler(event, context) -> dict: +def lambda_handler(event, context: LambdaContext) -> dict: try: # validate input using custom json format validate(event=event, schema=schemas.INPUT, formats=custom_format) diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index 26bd1e0c73a..ee8034ba3e9 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -3,6 +3,7 @@ import getting_started_validator_decorator_schema as schemas +from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import validator @@ -16,7 +17,7 @@ class User: # using a decorator to validate input and output data @validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) -def lambda_handler(event, context) -> dict: +def lambda_handler(event, context: LambdaContext) -> dict: user_details: dict = {} diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py index 07988406828..0519147d27a 100644 --- a/examples/validation/src/getting_started_validator_standalone_function.py +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -1,9 +1,10 @@ import getting_started_validator_standalone_schema as schemas +from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate -def lambda_handler(event, context) -> dict: +def lambda_handler(event, context: LambdaContext) -> dict: try: user_authenticated: str = "" diff --git a/examples/validation/src/getting_started_validator_unwraping_function.py b/examples/validation/src/getting_started_validator_unwraping_function.py index 3b8e03f1fbf..ead88c8ba64 100644 --- a/examples/validation/src/getting_started_validator_unwraping_function.py +++ b/examples/validation/src/getting_started_validator_unwraping_function.py @@ -1,6 +1,7 @@ import boto3 import getting_started_validator_unwraping_schema as schemas +from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import validator s3_client = boto3.resource("s3") @@ -8,7 +9,7 @@ # using a decorator to validate input data @validator(inbound_schema=schemas.INPUT, envelope="data") -def lambda_handler(event, context): +def lambda_handler(event, context: LambdaContext) -> dict: try: data_detail: dict = event.get("detail", {}) diff --git a/examples/validation/src/unwrapping_popular_event_source_function.py b/examples/validation/src/unwrapping_popular_event_source_function.py index 3dc271bc043..7ee40a2b01b 100644 --- a/examples/validation/src/unwrapping_popular_event_source_function.py +++ b/examples/validation/src/unwrapping_popular_event_source_function.py @@ -2,12 +2,13 @@ import unwrapping_popular_event_source_schema as schemas from botocore.exceptions import ClientError +from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import envelopes, validator # extracting detail from eventbridge custom event @validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) -def lambda_handler(event, context): +def lambda_handler(event, context: LambdaContext) -> dict: try: ec2_client = boto3.resource("ec2", region_name=event.get("region")) instance_id = event.get("instance_id") From d6cb2234bc53dfc5de201955b697fd680a062f73 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 10:38:17 +0200 Subject: [PATCH 23/36] chore: make examples tip consistent with other docs Signed-off-by: heitorlessa --- docs/utilities/validation.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 413ded15324..f627bafd0bb 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -15,17 +15,17 @@ This utility provides JSON Schema validation for events and responses, including ## Getting started -???+ tip "Tip: Using JSON Schemas for the first time?" - Check this [step-by-step tour in the official JSON Schema website](https://json-schema.org/learn/getting-started-step-by-step.html){target="_blank"}. - -You can validate inbound and outbound events using [`validator` decorator](#validator-decorator). - ???+ 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 can validate inbound and outbound events using [`validator` decorator](#validator-decorator). + You can also use the standalone `validate` function, if you want more control over the validation process such as handling a validation error. -We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/){target="_blank"} library. +???+ tip "Tip: Using JSON Schemas for the first time?" + Check this [step-by-step tour in the official JSON Schema website](https://json-schema.org/learn/getting-started-step-by-step.html){target="_blank"}. + + We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/){target="_blank"} library. ???+ warning Both `validator` decorator and `validate` standalone function expects your JSON Schema to be a **dictionary**, not a filename. From d3d7d5b14bd16f74d7594b39615c073929a5f90e Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:10:34 +0200 Subject: [PATCH 24/36] fix: correct eventbridge payload --- ...ing_started_validator_unwraping_function.py | 17 ++++++++++------- ...ng_started_validator_unwraping_payload.json | 13 ++++++++++--- ...tting_started_validator_unwraping_schema.py | 18 +++++++++--------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/examples/validation/src/getting_started_validator_unwraping_function.py b/examples/validation/src/getting_started_validator_unwraping_function.py index ead88c8ba64..c99c2534451 100644 --- a/examples/validation/src/getting_started_validator_unwraping_function.py +++ b/examples/validation/src/getting_started_validator_unwraping_function.py @@ -1,24 +1,27 @@ import boto3 import getting_started_validator_unwraping_schema as schemas +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import EventBridgeEvent from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import validator +from aws_lambda_powertools.utilities.validation.envelopes import EVENTBRIDGE s3_client = boto3.resource("s3") # using a decorator to validate input data -@validator(inbound_schema=schemas.INPUT, envelope="data") -def lambda_handler(event, context: LambdaContext) -> dict: +@validator(inbound_schema=schemas.INPUT, envelope=EVENTBRIDGE) +def lambda_handler(event: dict, context: LambdaContext) -> dict: + my_event = EventBridgeEvent(event) + data = my_event.detail.get("data", {}) + s3_bucket, s3_key = data.get("s3_bucket"), data.get("s3_key") try: - data_detail: dict = event.get("detail", {}) - s3_object = s3_client.Object(data_detail.get("s3_bucket"), data_detail.get("s3_key")) - content = s3_object.get()["Body"].read().decode("utf-8") + s3_object = s3_client.Object(bucket_name=s3_bucket, key=s3_key) + payload = s3_object.get()["Body"] + content = payload.read().decode("utf-8") - # return data processed return {"message": process_data_object(content), "success": True} - except s3_client.meta.client.exceptions.NoSuchBucket as exception: return return_error_message(str(exception)) except s3_client.meta.client.exceptions.NoSuchKey as exception: diff --git a/examples/validation/src/getting_started_validator_unwraping_payload.json b/examples/validation/src/getting_started_validator_unwraping_payload.json index dde10c1fcf2..7757085361b 100644 --- a/examples/validation/src/getting_started_validator_unwraping_payload.json +++ b/examples/validation/src/getting_started_validator_unwraping_payload.json @@ -1,10 +1,17 @@ { - "data": { - "detail": { + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "CustomEvent", + "source": "mycompany.service", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [], + "detail": { + "data": { "s3_bucket": "aws-lambda-powertools", "s3_key": "folder/event.txt", "file_size": 200, "file_type": "text/plain" } } -} +} \ No newline at end of file diff --git a/examples/validation/src/getting_started_validator_unwraping_schema.py b/examples/validation/src/getting_started_validator_unwraping_schema.py index baa02ad66ad..2db9b8a4ab9 100644 --- a/examples/validation/src/getting_started_validator_unwraping_schema.py +++ b/examples/validation/src/getting_started_validator_unwraping_schema.py @@ -6,7 +6,7 @@ "description": "The root schema comprises the entire JSON document.", "examples": [ { - "detail": { + "data": { "s3_bucket": "aws-lambda-powertools", "s3_key": "event.txt", "file_size": 200, @@ -14,16 +14,16 @@ } } ], - "required": ["detail"], + "required": ["data"], "properties": { - "detail": { - "$id": "#root/detail", - "title": "Detail", + "data": { + "$id": "#root/data", + "title": "Root", "type": "object", "required": ["s3_bucket", "s3_key", "file_size", "file_type"], "properties": { "s3_bucket": { - "$id": "#root/detail/s3_bucket", + "$id": "#root/data/s3_bucket", "title": "The S3 Bucker", "type": "string", "default": "", @@ -31,7 +31,7 @@ "pattern": "^.*$", }, "s3_key": { - "$id": "#root/detail/s3_key", + "$id": "#root/data/s3_key", "title": "The S3 Key", "type": "string", "default": "", @@ -39,14 +39,14 @@ "pattern": "^.*$", }, "file_size": { - "$id": "#root/detail/file_size", + "$id": "#root/data/file_size", "title": "The file size", "type": "integer", "examples": [200], "default": 0, }, "file_type": { - "$id": "#root/detail/file_type", + "$id": "#root/data/file_type", "title": "The file type", "type": "string", "default": "", From ae500f112e89e0b9c21796a764b575097c2f507e Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:14:37 +0200 Subject: [PATCH 25/36] fix: name typo and highlight --- docs/utilities/validation.md | 14 +++++++------- ...tting_started_validator_unwrapping_function.py} | 0 ...ting_started_validator_unwrapping_payload.json} | 0 ...getting_started_validator_unwrapping_schema.py} | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename examples/validation/src/{getting_started_validator_unwraping_function.py => getting_started_validator_unwrapping_function.py} (100%) rename examples/validation/src/{getting_started_validator_unwraping_payload.json => getting_started_validator_unwrapping_payload.json} (100%) rename examples/validation/src/{getting_started_validator_unwraping_schema.py => getting_started_validator_unwrapping_schema.py} (100%) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index f627bafd0bb..850c2eda1c0 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -89,22 +89,22 @@ Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extr Here is a sample custom EventBridge event, where we only validate what's inside the `detail` key: -=== "getting_started_validator_unwraping_function.py" +=== "getting_started_validator_unwrapping_function.py" - ```python hl_lines="2 4 9 10 21 23" - --8<-- "examples/validation/src/getting_started_validator_unwraping_function.py" + ```python hl_lines="2 6-7 13" + --8<-- "examples/validation/src/getting_started_validator_unwrapping_function.py" ``` -=== "getting_started_validator_unwraping_schema.py" +=== "getting_started_validator_unwrapping_schema.py" ```python hl_lines="9-14 23 25 28 33 36 41 44 48 51" - --8<-- "examples/validation/src/getting_started_validator_unwraping_schema.py" + --8<-- "examples/validation/src/getting_started_validator_unwrapping_schema.py" ``` -=== "getting_started_validator_unwraping_payload.json" +=== "getting_started_validator_unwrapping_payload.json" ```json - --8<-- "examples/validation/src/getting_started_validator_unwraping_payload.json" + --8<-- "examples/validation/src/getting_started_validator_unwrapping_payload.json" ``` This is quite powerful because you can use JMESPath Query language to extract records from [arrays](https://jmespath.org/tutorial.html#list-and-slice-projections), combine [pipe](https://jmespath.org/tutorial.html#pipe-expressions) and [function expressions](https://jmespath.org/tutorial.html#functions). diff --git a/examples/validation/src/getting_started_validator_unwraping_function.py b/examples/validation/src/getting_started_validator_unwrapping_function.py similarity index 100% rename from examples/validation/src/getting_started_validator_unwraping_function.py rename to examples/validation/src/getting_started_validator_unwrapping_function.py diff --git a/examples/validation/src/getting_started_validator_unwraping_payload.json b/examples/validation/src/getting_started_validator_unwrapping_payload.json similarity index 100% rename from examples/validation/src/getting_started_validator_unwraping_payload.json rename to examples/validation/src/getting_started_validator_unwrapping_payload.json diff --git a/examples/validation/src/getting_started_validator_unwraping_schema.py b/examples/validation/src/getting_started_validator_unwrapping_schema.py similarity index 100% rename from examples/validation/src/getting_started_validator_unwraping_schema.py rename to examples/validation/src/getting_started_validator_unwrapping_schema.py From d764e45b63b5fc7dfba182edec21e0a7dcf45004 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:16:55 +0200 Subject: [PATCH 26/36] chore: remove EventBridge built-in envelope from earlier example --- docs/utilities/validation.md | 2 +- .../src/getting_started_validator_unwrapping_function.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 850c2eda1c0..a5123a0eca1 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -91,7 +91,7 @@ Here is a sample custom EventBridge event, where we only validate what's inside === "getting_started_validator_unwrapping_function.py" - ```python hl_lines="2 6-7 13" + ```python hl_lines="2 6 8 12" --8<-- "examples/validation/src/getting_started_validator_unwrapping_function.py" ``` diff --git a/examples/validation/src/getting_started_validator_unwrapping_function.py b/examples/validation/src/getting_started_validator_unwrapping_function.py index c99c2534451..498d7097caf 100644 --- a/examples/validation/src/getting_started_validator_unwrapping_function.py +++ b/examples/validation/src/getting_started_validator_unwrapping_function.py @@ -1,16 +1,15 @@ import boto3 -import getting_started_validator_unwraping_schema as schemas +import getting_started_validator_unwrapping_schema as schemas from aws_lambda_powertools.utilities.data_classes.event_bridge_event import EventBridgeEvent from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import validator -from aws_lambda_powertools.utilities.validation.envelopes import EVENTBRIDGE s3_client = boto3.resource("s3") # using a decorator to validate input data -@validator(inbound_schema=schemas.INPUT, envelope=EVENTBRIDGE) +@validator(inbound_schema=schemas.INPUT, envelope="detail") def lambda_handler(event: dict, context: LambdaContext) -> dict: my_event = EventBridgeEvent(event) data = my_event.detail.get("data", {}) From b048d7a9a0a48a451f2cd760dd0b25e599bc5deb Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:17:37 +0200 Subject: [PATCH 27/36] fix: old typo --- docs/utilities/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index a5123a0eca1..d2eb2e4147b 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -83,7 +83,7 @@ You can also gracefully handle schema validation errors by catching `SchemaValid ### Unwrapping events prior to validation -You might want to validate only a portion of your event - This is where the `envelope` parameter is for. +You might want to validate only a portion of your event - This is what the `envelope` parameter is for. Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extract a portion of JSON you want before applying JSON Schema validation. From 0e98a3b2b0fd57ead9c7957ea3538c28aa93117b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:18:26 +0200 Subject: [PATCH 28/36] chore: improve old wording --- docs/utilities/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index d2eb2e4147b..c991ef42492 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -113,7 +113,7 @@ When combined, these features allow you to extract what you need before validati ### Built-in envelopes -This utility comes with built-in envelopes to easily extract the payload from popular event sources. +We provide built-in envelopes to easily extract the payload from popular event sources. === "unwrapping_popular_event_source_function.py" From 55df2530736c7a9da2ddecb40a3865a89d22014a Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:27:02 +0200 Subject: [PATCH 29/36] fix: event data access, highlight --- docs/utilities/validation.md | 4 ++-- .../src/unwrapping_popular_event_source_function.py | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index c991ef42492..fccab748acc 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -91,7 +91,7 @@ Here is a sample custom EventBridge event, where we only validate what's inside === "getting_started_validator_unwrapping_function.py" - ```python hl_lines="2 6 8 12" + ```python hl_lines="2 6 12" --8<-- "examples/validation/src/getting_started_validator_unwrapping_function.py" ``` @@ -117,7 +117,7 @@ We provide built-in envelopes to easily extract the payload from popular event s === "unwrapping_popular_event_source_function.py" - ```python hl_lines="2 5 8 9 19" + ```python hl_lines="2 7 11" --8<-- "examples/validation/src/unwrapping_popular_event_source_function.py" ``` diff --git a/examples/validation/src/unwrapping_popular_event_source_function.py b/examples/validation/src/unwrapping_popular_event_source_function.py index 7ee40a2b01b..b6894a5a529 100644 --- a/examples/validation/src/unwrapping_popular_event_source_function.py +++ b/examples/validation/src/unwrapping_popular_event_source_function.py @@ -2,20 +2,22 @@ import unwrapping_popular_event_source_schema as schemas from botocore.exceptions import ClientError +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import EventBridgeEvent from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import envelopes, validator -# extracting detail from eventbridge custom event +# extracting detail from EventBridge custom event @validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) -def lambda_handler(event, context: LambdaContext) -> dict: +def lambda_handler(event: dict, context: LambdaContext) -> dict: + my_event = EventBridgeEvent(event) + ec2_client = boto3.resource("ec2", region_name=my_event.region) + try: - ec2_client = boto3.resource("ec2", region_name=event.get("region")) - instance_id = event.get("instance_id") + instance_id = my_event.detail.get("instance_id") instance = ec2_client.Instance(instance_id) instance.stop() return {"message": f"Successfully stopped {instance_id}", "success": True} - except ClientError as exception: return {"message": str(exception), "success": False} From c8fbed1d14e43262c2c9b7fc1d1aa7ebad7740b5 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:33:51 +0200 Subject: [PATCH 30/36] fix: snippet name and highlight --- docs/utilities/validation.md | 14 +++++++------- ...ormat_function.py => custom_format_function.py} | 3 +-- ...mat_payload.json => custom_format_payload.json} | 0 ...om_format_schema.py => custom_format_schema.py} | 0 4 files changed, 8 insertions(+), 9 deletions(-) rename examples/validation/src/{getting_started_custom_format_function.py => custom_format_function.py} (95%) rename examples/validation/src/{getting_started_custom_format_payload.json => custom_format_payload.json} (100%) rename examples/validation/src/{getting_started_custom_format_schema.py => custom_format_schema.py} (100%) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index fccab748acc..8091ddb9233 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -166,22 +166,22 @@ JSON Schemas with custom formats like `awsaccountid` will fail validation. If yo For each format defined in a dictionary key, you must use a regex, or a function that returns a boolean to instruct the validator on how to proceed when encountering that type. -=== "getting_started_custom_format_function.py" +=== "custom_format_function.py" - ```python hl_lines="6 8 10 11 16 17 28 30" - --8<-- "examples/validation/src/getting_started_custom_format_function.py" + ```python hl_lines="5 8 10 11 17 28" + --8<-- "examples/validation/src/custom_format_function.py" ``` -=== "getting_started_custom_format_schema.py" +=== "custom_format_schema.py" ```python hl_lines="7 9 12 13 17 20" - --8<-- "examples/validation/src/getting_started_custom_format_schema.py" + --8<-- "examples/validation/src/custom_format_schema.py" ``` -=== "getting_started_custom_format_payload.json" +=== "custom_format_payload.json" ```json hl_lines="12 13" - --8<-- "examples/validation/src/getting_started_custom_format_payload.json" + --8<-- "examples/validation/src/custom_format_payload.json" ``` ### Built-in JMESPath functions diff --git a/examples/validation/src/getting_started_custom_format_function.py b/examples/validation/src/custom_format_function.py similarity index 95% rename from examples/validation/src/getting_started_custom_format_function.py rename to examples/validation/src/custom_format_function.py index 02dd16e97e0..bf589018c5c 100644 --- a/examples/validation/src/getting_started_custom_format_function.py +++ b/examples/validation/src/custom_format_function.py @@ -2,7 +2,7 @@ import re import boto3 -import getting_started_custom_format_schema as schemas +import custom_format_schema as schemas from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate @@ -24,7 +24,6 @@ def lambda_handler(event, context: LambdaContext) -> dict: "message": "Success", "statusCode": 200, } - except SchemaValidationError as exception: return return_error_message(str(exception)) except Exception as exception: diff --git a/examples/validation/src/getting_started_custom_format_payload.json b/examples/validation/src/custom_format_payload.json similarity index 100% rename from examples/validation/src/getting_started_custom_format_payload.json rename to examples/validation/src/custom_format_payload.json diff --git a/examples/validation/src/getting_started_custom_format_schema.py b/examples/validation/src/custom_format_schema.py similarity index 100% rename from examples/validation/src/getting_started_custom_format_schema.py rename to examples/validation/src/custom_format_schema.py From 7872e5680f16570045d06ab09e8b61fc4452dfca Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 17 Aug 2022 11:34:42 +0200 Subject: [PATCH 31/36] fix: highlight SchemaValidationError --- docs/utilities/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 8091ddb9233..062429b4cb7 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -168,7 +168,7 @@ For each format defined in a dictionary key, you must use a regex, or a function === "custom_format_function.py" - ```python hl_lines="5 8 10 11 17 28" + ```python hl_lines="5 8 10 11 17 27" --8<-- "examples/validation/src/custom_format_function.py" ``` From 542693e4cdbddc3a3673011cd376217e94028539 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 17 Aug 2022 13:45:58 +0100 Subject: [PATCH 32/36] docs(validation): Refactoring insecure example --- docs/utilities/validation.md | 2 +- ...ng_started_validator_decorator_function.py | 31 ++++++++++++++----- ...g_started_validator_decorator_payload.json | 4 +-- ...ting_started_validator_decorator_schema.py | 22 ++++++------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 062429b4cb7..e2d7afd1f44 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -38,7 +38,7 @@ It will fail fast with `SchemaValidationError` exception if event or response do === "getting_started_validator_decorator_function.py" - ```python hl_lines="4 6 18 26" + ```python hl_lines="8 27 28 42" --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" ``` diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index ee8034ba3e9..95d67e1031c 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -3,9 +3,18 @@ import getting_started_validator_decorator_schema as schemas +from aws_lambda_powertools.utilities import parameters from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import validator +# we can get list of allowed IPs from AWS Parameter Store using Parameters Utility +# See: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/ +ALLOWED_IPS = parameters.get_parameter("/lambda-powertools/allowed_ips") + + +class UserPermissionsError(Exception): + ... + @dataclass class User: @@ -19,10 +28,18 @@ class User: @validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) def lambda_handler(event, context: LambdaContext) -> dict: - user_details: dict = {} - - if event.get("username") == "lambda" and event.get("password") == "powertools": - user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ - - # the body must be a object because must match OUTPUT schema, otherwise it fails - return {"body": user_details or None, "statusCode": 200 if user_details else 204} + try: + user_details: dict = {} + + # get permissions by user_id and project + if ( + event.get("user_id") == "0d44b083-8206-4a3a-aa95-5d392a99be4a" + and event.get("project") == "powertools" + and event.get("ip") in ALLOWED_IPS + ): + user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ + + # the body must be a object because must match OUTPUT schema, otherwise it fails + return {"body": user_details or None, "statusCode": 200 if user_details else 204} + except Exception as e: + raise UserPermissionsError(str(e)) diff --git a/examples/validation/src/getting_started_validator_decorator_payload.json b/examples/validation/src/getting_started_validator_decorator_payload.json index d5dc512d984..0e8bb8b752b 100644 --- a/examples/validation/src/getting_started_validator_decorator_payload.json +++ b/examples/validation/src/getting_started_validator_decorator_payload.json @@ -1,6 +1,6 @@ { - "username": "lambda", - "password": "powertools", + "user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", + "project": "powertools", "ip": "192.168.0.1" } diff --git a/examples/validation/src/getting_started_validator_decorator_schema.py b/examples/validation/src/getting_started_validator_decorator_schema.py index ddee0b13352..1f74a2cc711 100644 --- a/examples/validation/src/getting_started_validator_decorator_schema.py +++ b/examples/validation/src/getting_started_validator_decorator_schema.py @@ -4,20 +4,20 @@ "type": "object", "title": "Sample schema", "description": "The root schema comprises the entire JSON document.", - "examples": [{"username": "hello world", "password": "lessa", "ip": "192.168.0.1"}], - "required": ["username", "password", "ip"], + "examples": [{"user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", "project": "powertools", "ip": "192.168.0.1"}], + "required": ["user_id", "project", "ip"], "properties": { - "username": { - "$id": "#/properties/username", + "user_id": { + "$id": "#/properties/user_id", "type": "string", - "title": "The username", - "examples": ["lambda"], - "maxLength": 30, + "title": "The user_id", + "examples": ["0d44b083-8206-4a3a-aa95-5d392a99be4a"], + "maxLength": 50, }, - "password": { - "$id": "#/properties/password", + "project": { + "$id": "#/properties/project", "type": "string", - "title": "The password", + "title": "The project", "examples": ["powertools"], "maxLength": 30, }, @@ -53,7 +53,7 @@ "type": "object", "title": "The body", "examples": [ - '{"ip": "192.168.0.1", "permissions": ["read", "write"], "user_id": "7576b683-295e-4f69-b558-70e789de1b18", "name": "Christopher Dunn"}' # noqa E501 + '{"ip": "192.168.0.1", "permissions": ["read", "write"], "user_id": "7576b683-295e-4f69-b558-70e789de1b18", "name": "Project Lambda Powertools"}' # noqa E501 ], }, }, From 7e77f76757b8eebed539c85d4d28af496f5ee778 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 17 Aug 2022 15:47:02 +0100 Subject: [PATCH 33/36] docs(validation): Refactoring validate example --- docs/utilities/validation.md | 2 +- ...g_started_validator_standalone_function.py | 15 +++++++++++--- ..._started_validator_standalone_payload.json | 4 ++-- ...ing_started_validator_standalone_schema.py | 20 +++++++++---------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index e2d7afd1f44..d1187f93df8 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -65,7 +65,7 @@ You can also gracefully handle schema validation errors by catching `SchemaValid === "getting_started_validator_standalone_function.py" - ```python hl_lines="3 10 16 18" + ```python hl_lines="5 16 17 26" --8<-- "examples/validation/src/getting_started_validator_standalone_function.py" ``` diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py index 0519147d27a..1680511766b 100644 --- a/examples/validation/src/getting_started_validator_standalone_function.py +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -1,8 +1,13 @@ import getting_started_validator_standalone_schema as schemas +from aws_lambda_powertools.utilities import parameters from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate +# we can get list of allowed IPs from AWS Parameter Store using Parameters Utility +# See: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/ +ALLOWED_IPS = parameters.get_parameter("/lambda-powertools/allowed_ips") + def lambda_handler(event, context: LambdaContext) -> dict: try: @@ -11,10 +16,14 @@ def lambda_handler(event, context: LambdaContext) -> dict: # using standalone function to validate input data only validate(event=event, schema=schemas.INPUT) - if event.get("username") == "lambda" and event.get("password") == "powertools": - user_authenticated = "Authenticated" + if ( + event.get("user_id") == "0d44b083-8206-4a3a-aa95-5d392a99be4a" + and event.get("project") == "powertools" + and event.get("ip") in ALLOWED_IPS + ): + user_authenticated = "Allowed" - # in this example the body can be a string because we are not validating the OUTPUT + # in this example the body can be of any type because we are not validating the OUTPUT return {"body": user_authenticated, "statusCode": 200 if user_authenticated else 204} except SchemaValidationError as exception: # SchemaValidationError indicates where a data mismatch is diff --git a/examples/validation/src/getting_started_validator_standalone_payload.json b/examples/validation/src/getting_started_validator_standalone_payload.json index d5dc512d984..0e8bb8b752b 100644 --- a/examples/validation/src/getting_started_validator_standalone_payload.json +++ b/examples/validation/src/getting_started_validator_standalone_payload.json @@ -1,6 +1,6 @@ { - "username": "lambda", - "password": "powertools", + "user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", + "project": "powertools", "ip": "192.168.0.1" } diff --git a/examples/validation/src/getting_started_validator_standalone_schema.py b/examples/validation/src/getting_started_validator_standalone_schema.py index 2a2429172d4..28711157196 100644 --- a/examples/validation/src/getting_started_validator_standalone_schema.py +++ b/examples/validation/src/getting_started_validator_standalone_schema.py @@ -4,20 +4,20 @@ "type": "object", "title": "Sample schema", "description": "The root schema comprises the entire JSON document.", - "examples": [{"username": "hello world", "password": "lessa", "ip": "192.168.0.1"}], - "required": ["username", "password", "ip"], + "examples": [{"user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", "powertools": "lessa", "ip": "192.168.0.1"}], + "required": ["user_id", "project", "ip"], "properties": { - "username": { - "$id": "#/properties/username", + "user_id": { + "$id": "#/properties/user_id", "type": "string", - "title": "The username", - "examples": ["lambda"], - "maxLength": 30, + "title": "The user_id", + "examples": ["0d44b083-8206-4a3a-aa95-5d392a99be4a"], + "maxLength": 50, }, - "password": { - "$id": "#/properties/password", + "project": { + "$id": "#/properties/project", "type": "string", - "title": "The password", + "title": "The project", "examples": ["powertools"], "maxLength": 30, }, From 2367b791a2e501d6363fdbae5eadc4709c0d0344 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 18 Aug 2022 15:25:07 +0100 Subject: [PATCH 34/36] docs(validation): small things --- docs/utilities/validation.md | 2 +- .../src/getting_started_validator_unwrapping_function.py | 2 +- .../validation/src/unwrapping_popular_event_source_function.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index d1187f93df8..2d3efa1ef67 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -44,7 +44,7 @@ It will fail fast with `SchemaValidationError` exception if event or response do === "getting_started_validator_decorator_schema.py" - ```python hl_lines="7 8 10 12 17 19 24 26 28 41 42 44 46 51 53" + ```python hl_lines="10 12 17 19 24 26 28 44 46 51 53" --8<-- "examples/validation/src/getting_started_validator_decorator_schema.py" ``` diff --git a/examples/validation/src/getting_started_validator_unwrapping_function.py b/examples/validation/src/getting_started_validator_unwrapping_function.py index 498d7097caf..96c66a6f2d3 100644 --- a/examples/validation/src/getting_started_validator_unwrapping_function.py +++ b/examples/validation/src/getting_started_validator_unwrapping_function.py @@ -8,7 +8,7 @@ s3_client = boto3.resource("s3") -# using a decorator to validate input data +# we use the 'envelope' parameter to extract the payload inside the 'detail' key before validating @validator(inbound_schema=schemas.INPUT, envelope="detail") def lambda_handler(event: dict, context: LambdaContext) -> dict: my_event = EventBridgeEvent(event) diff --git a/examples/validation/src/unwrapping_popular_event_source_function.py b/examples/validation/src/unwrapping_popular_event_source_function.py index b6894a5a529..8afbb5c727f 100644 --- a/examples/validation/src/unwrapping_popular_event_source_function.py +++ b/examples/validation/src/unwrapping_popular_event_source_function.py @@ -8,6 +8,7 @@ # extracting detail from EventBridge custom event +# see: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/jmespath_functions/#built-in-envelopes @validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) def lambda_handler(event: dict, context: LambdaContext) -> dict: my_event = EventBridgeEvent(event) From 43f4ca8b1cfb5fcbc489a942aa503dcb284e6f5d Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 18 Aug 2022 15:26:18 +0100 Subject: [PATCH 35/36] docs(validation): small things --- docs/utilities/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 2d3efa1ef67..c9cd5813086 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -117,7 +117,7 @@ We provide built-in envelopes to easily extract the payload from popular event s === "unwrapping_popular_event_source_function.py" - ```python hl_lines="2 7 11" + ```python hl_lines="2 7 12" --8<-- "examples/validation/src/unwrapping_popular_event_source_function.py" ``` From 49be843a9f42c3646506d38be7fb497f82706875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Fonseca?= Date: Fri, 19 Aug 2022 10:56:40 +0200 Subject: [PATCH 36/36] fix(docs): typo --- .../src/getting_started_validator_decorator_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py index 95d67e1031c..bc371742860 100644 --- a/examples/validation/src/getting_started_validator_decorator_function.py +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -39,7 +39,7 @@ def lambda_handler(event, context: LambdaContext) -> dict: ): user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ - # the body must be a object because must match OUTPUT schema, otherwise it fails + # the body must be an object because must match OUTPUT schema, otherwise it fails return {"body": user_details or None, "statusCode": 200 if user_details else 204} except Exception as e: raise UserPermissionsError(str(e))