Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(jmespath_util): snippets split, improved, and lint #1419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0a1d014
docs(jmespath_function): snippets split, improved, fixed and lint
leandrodamascena Aug 5, 2022
5a3a461
docs(jmespath_function): code highlights
leandrodamascena Aug 5, 2022
f9cd042
docs(jmespath_function): code highlights
leandrodamascena Aug 5, 2022
df9156b
docs(jmespath_util): include example for nested data
heitorlessa Aug 5, 2022
76d7fe0
docs(jmespath_util): fix return type
heitorlessa Aug 5, 2022
9f96bd8
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
196f3c9
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
848e660
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
986f550
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
0578971
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
b9889dc
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
b649842
docs(jmespath_function): Refactoring codes for demonstration purposes…
leandrodamascena Aug 6, 2022
0b2505d
docs(jmespath_function): Refactoring codes - base64 envelope
leandrodamascena Aug 7, 2022
c4db987
docs(jmespath_function): Refactoring examples - base64-gzip envelope
leandrodamascena Aug 7, 2022
8f05eb8
docs(jmespath_function): Refactoring examples - small fixes
leandrodamascena Aug 7, 2022
f733104
docs(jmespath_function): Refactoring examples - small fixes
leandrodamascena Aug 7, 2022
4aab491
docs(jmespath_function): Refactoring examples - custom function envelope
leandrodamascena Aug 7, 2022
c8cba95
docs(jmespath): fix highlighting
heitorlessa Aug 9, 2022
b49c3f4
docs(jmespath): simplify order factory
heitorlessa Aug 9, 2022
1a6a344
docs(jmespath): simplify wording
heitorlessa Aug 9, 2022
da920a8
docs(jmespath): correct json encoder wording
heitorlessa Aug 9, 2022
9b6ae91
docs(jmespath): safer return
heitorlessa Aug 9, 2022
d77cd80
docs(jmespath): exception order
heitorlessa Aug 9, 2022
87d8b17
docs(jmespath): adjust highlighting after refactor
heitorlessa Aug 9, 2022
9c4eb04
docs(jmespath): add exception and dummy endpoint to use fn arguments
heitorlessa Aug 9, 2022
05f4a4b
docs(jmespath): sync base64 with powertools_json refactor
heitorlessa Aug 9, 2022
aec1e71
docs(jmespath): simplify base64 gzip example
heitorlessa Aug 9, 2022
d673e58
docs(jmespath): emphasize important parts in jmespath example
heitorlessa Aug 9, 2022
e38f2a0
docs(jmespath): address old typos and wording
heitorlessa Aug 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 91 additions & 163 deletions docs/utilities/jmespath_functions.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"Records": [
{
"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
"receiptHandle": "MessageReceiptHandle",
"body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\",\"booking\":{\"id\":\"5b2c4803-330b-42b7-811a-c68689425de1\",\"reference\":\"ySz7oA\",\"outboundFlightId\":\"20c0d2f2-56a3-4068-bf20-ff7703db552d\"},\"payment\":{\"receipt\":\"https:\/\/pay.stripe.com\/receipts\/acct_1Dvn7pF4aIiftV70\/ch_3JTC14F4aIiftV700iFq2CHB\/rcpt_K7QsrFln9FgFnzUuBIiNdkkRYGxUL0X\",\"amount\":100}}",
"attributes": {
"ApproximateReceiveCount": "1",
"SentTimestamp": "1523232000000",
"SenderId": "123456789012",
"ApproximateFirstReceiveTimestamp": "1523232000001"
},
"messageAttributes": {},
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
"eventSource": "aws:sqs",
"eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
"awsRegion": "us-east-1"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope
from aws_lambda_powertools.utilities.typing import LambdaContext


def handler(event: dict, context: LambdaContext) -> dict:
payload = extract_data_from_envelope(data=event, envelope=envelopes.SQS)
leandrodamascena marked this conversation as resolved.
Show resolved Hide resolved
customer_id = payload.get("customerId") # now deserialized

return {"customer_id": customer_id, "message": "success", "statusCode": 200}
12 changes: 12 additions & 0 deletions examples/jmespath_functions/src/extract_data_from_envelope.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}",
"deeply_nested": [
{
"some_data": [
1,
2,
3
]
}
]
}
12 changes: 12 additions & 0 deletions examples/jmespath_functions/src/extract_data_from_envelope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.utilities.typing import LambdaContext


def handler(event: dict, context: LambdaContext) -> dict:
payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)")
customer_id = payload.get("customerId") # now deserialized

# also works for fetching and flattening deeply nested data
some_data = extract_data_from_envelope(data=event, envelope="deeply_nested[*].some_data[]")

return {"customer_id": customer_id, "message": "success", "context": some_data, "statusCode": 200}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import base64
import binascii
import gzip
import json

import powertools_base64_gzip_jmespath_schema as schemas
from jmespath.exceptions import JMESPathTypeError

from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate


def lambda_handler(event, context: LambdaContext) -> dict:
try:
validate(event=event, schema=schemas.INPUT, envelope="powertools_base64_gzip(payload) | powertools_json(@)")

# Alternatively, extract_data_from_envelope works here too
encoded_payload = base64.b64decode(event["payload"])
uncompressed_payload = gzip.decompress(encoded_payload).decode()
log: dict = json.loads(uncompressed_payload)

return {
"message": "Logs processed",
"log_group": log.get("logGroup"),
"owner": log.get("owner"),
"success": True,
}

except JMESPathTypeError:
return return_error_message("The powertools_base64_gzip() envelope function must match a valid path.")
except binascii.Error:
return return_error_message("Payload must be a valid base64 encoded string")
except json.JSONDecodeError:
return return_error_message("Payload must be valid JSON (base64 encoded).")
except SchemaValidationError as exception:
# SchemaValidationError indicates where a data mismatch is
return return_error_message(str(exception))


def return_error_message(message: str) -> dict:
return {"message": message, "success": False}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"payload": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA=="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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": [
{
"owner": "123456789012",
"logGroup": "/aws/lambda/powertools-example",
"logStream": "2022/08/07/[$LATEST]d3a8dcaffc7f4de2b8db132e3e106660",
"logEvents": {},
}
],
"required": ["owner", "logGroup", "logStream", "logEvents"],
"properties": {
"owner": {
"$id": "#/properties/owner",
"type": "string",
"title": "The owner",
"examples": ["123456789012"],
"maxLength": 12,
},
"logGroup": {
"$id": "#/properties/logGroup",
"type": "string",
"title": "The logGroup",
"examples": ["/aws/lambda/powertools-example"],
"maxLength": 100,
},
"logStream": {
"$id": "#/properties/logStream",
"type": "string",
"title": "The logGroup",
"examples": ["2022/08/07/[$LATEST]d3a8dcaffc7f4de2b8db132e3e106660"],
"maxLength": 100,
},
"logEvents": {
"$id": "#/properties/logEvents",
"type": "array",
"title": "The logEvents",
"examples": [
"{'id': 'eventId1', 'message': {'username': 'lessa', 'message': 'hello world'}, 'timestamp': 1440442987000}" # noqa E501
],
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import base64
import binascii
import json
from dataclasses import asdict, dataclass, field, is_dataclass
from uuid import uuid4

import powertools_base64_jmespath_schema as schemas
from jmespath.exceptions import JMESPathTypeError

from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate


@dataclass
class Order:
user_id: int
product_id: int
quantity: int
price: float
currency: str
order_id: str = field(default_factory=lambda: f"{uuid4()}")


class DataclassCustomEncoder(json.JSONEncoder):
"""A custom JSON encoder to serialize dataclass obj"""

def default(self, obj):
# Only called for values that aren't JSON serializable
# where `obj` will be an instance of Todo in this example
return asdict(obj) if is_dataclass(obj) else super().default(obj)


def lambda_handler(event, context: LambdaContext) -> dict:

# Try to validate the schema
try:
validate(event=event, schema=schemas.INPUT, envelope="powertools_json(powertools_base64(payload))")

# alternatively, extract_data_from_envelope works here too
payload_decoded = base64.b64decode(event["payload"]).decode()

order_payload: dict = json.loads(payload_decoded)

return {
"order": json.dumps(Order(**order_payload), cls=DataclassCustomEncoder),
"message": "order created",
"success": True,
}
except JMESPathTypeError:
return return_error_message(
"The powertools_json(powertools_base64()) envelope function must match a valid path."
)
except binascii.Error:
return return_error_message("Payload must be a valid base64 encoded string")
except json.JSONDecodeError:
return return_error_message("Payload must be valid JSON (base64 encoded).")
except SchemaValidationError as exception:
# SchemaValidationError indicates where a data mismatch is
return return_error_message(str(exception))


def return_error_message(message: str) -> dict:
return {"order": None, "message": message, "success": False}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"payload":"eyJ1c2VyX2lkIjogMTIzLCAicHJvZHVjdF9pZCI6IDEsICJxdWFudGl0eSI6IDIsICJwcmljZSI6IDEwLjQwLCAiY3VycmVuY3kiOiAiVVNEIn0="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
INPUT = {
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"title": "Sample order schema",
"description": "The root schema comprises the entire JSON document.",
"examples": [{"user_id": 123, "product_id": 1, "quantity": 2, "price": 10.40, "currency": "USD"}],
"required": ["user_id", "product_id", "quantity", "price", "currency"],
"properties": {
"user_id": {
"$id": "#/properties/user_id",
"type": "integer",
"title": "The unique identifier of the user",
"examples": [123],
"maxLength": 10,
},
"product_id": {
"$id": "#/properties/product_id",
"type": "integer",
"title": "The unique identifier of the product",
"examples": [1],
"maxLength": 10,
},
"quantity": {
"$id": "#/properties/quantity",
"type": "integer",
"title": "The quantity of the product",
"examples": [2],
"maxLength": 10,
},
"price": {
"$id": "#/properties/price",
"type": "number",
"title": "The individual price of the product",
"examples": [10.40],
"maxLength": 10,
},
"currency": {
"$id": "#/properties/currency",
"type": "string",
"title": "The currency",
"examples": ["The currency of the order"],
"maxLength": 100,
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Records": [
{
"user": "integration-kafka",
"datetime": "2022-01-01T00:00:00.000Z",
"log": "/QGIMjAyMi8wNi8xNiAxNjoyNTowMCBbY3JpdF0gMzA1MTg5MCMNCPBEOiAqMSBjb25uZWN0KCkg\ndG8gMTI3LjAuMC4xOjUwMDAgZmFpbGVkICgxMzogUGVybWlzc2lvbiBkZW5pZWQpIHdoaWxlEUEI\naW5nAUJAdXBzdHJlYW0sIGNsaWVudDoZVKgsIHNlcnZlcjogXywgcmVxdWVzdDogIk9QVElPTlMg\nLyBIVFRQLzEuMSIsFUckOiAiaHR0cDovLzabABQvIiwgaG8FQDAxMjcuMC4wLjE6ODEi\n"
},
{
"user": "integration-kafka",
"datetime": "2022-01-01T00:00:01.000Z",
"log": "tQHwnDEyNy4wLjAuMSAtIC0gWzE2L0p1bi8yMDIyOjE2OjMwOjE5ICswMTAwXSAiT1BUSU9OUyAv\nIEhUVFAvMS4xIiAyMDQgMCAiLSIgIk1vemlsbGEvNS4wIChYMTE7IExpbnV4IHg4Nl82NCkgQXBw\nbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEwMi4BmUwwIFNhZmFy\naS81MzcuMzYiICItIg==\n"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import base64
import binascii

import snappy
from jmespath.exceptions import JMESPathTypeError
from jmespath.functions import signature

from aws_lambda_powertools.utilities.jmespath_utils import PowertoolsFunctions, extract_data_from_envelope


class CustomFunctions(PowertoolsFunctions):
# only decode if value is a string
# see supported data types: https://jmespath.org/specification.html#built-in-functions
@signature({"types": ["string"]})
def _func_decode_snappy_compression(self, payload: str):
decoded: bytes = base64.b64decode(payload)
return snappy.uncompress(decoded)


custom_jmespath_options = {"custom_functions": CustomFunctions()}


def lambda_handler(event, context) -> dict:

try:
logs = []
logs.append(
extract_data_from_envelope(
data=event,
# NOTE: Use the prefix `_func_` before the name of the function
envelope="Records[*].decode_snappy_compression(log)",
jmespath_options=custom_jmespath_options,
)
)
return {"logs": logs, "message": "Extracted messages", "success": True}
except JMESPathTypeError:
return return_error_message("The envelope function must match a valid path.")
except snappy.UncompressError:
return return_error_message("Log must be a valid snappy compressed binary")
except binascii.Error:
return return_error_message("Log must be a valid base64 encoded string")


def return_error_message(message: str) -> dict:
return {"logs": None, "message": message, "success": False}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version":"2.0",
"routeKey":"ANY /createpayment",
"rawPath":"/createpayment",
"rawQueryString":"",
"headers": {
"Header1": "value1",
"Header2": "value2"
},
"requestContext":{
"accountId":"123456789012",
"apiId":"api-id",
"domainName":"id.execute-api.us-east-1.amazonaws.com",
"domainPrefix":"id",
"http":{
"method":"POST",
"path":"/createpayment",
"protocol":"HTTP/1.1",
"sourceIp":"ip",
"userAgent":"agent"
},
"requestId":"id",
"routeKey":"ANY /createpayment",
"stage":"$default",
"time":"10/Feb/2021:13:40:43 +0000",
"timeEpoch":1612964443723
},
"body":"{\"user\":\"xyz\",\"product_id\":\"123456789\"}",
"isBase64Encoded":false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
from uuid import uuid4

import requests

from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer, IdempotencyConfig, idempotent

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")

# Treat everything under the "body" key
# in the event json object as our payload
config = IdempotencyConfig(event_key_jmespath="powertools_json(body)")


class PaymentError(Exception):
...


@idempotent(config=config, persistence_store=persistence_layer)
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}
except requests.HTTPError as e:
raise PaymentError("Unable to create payment subscription") from e


def create_subscription_payment(user: str, product_id: str) -> dict:
payload = {"user": user, "product_id": product_id}
ret: requests.Response = requests.post(url="https://httpbin.org/anything", data=payload)
ret.raise_for_status()

return {"id": f"{uuid4()}", "message": "paid"}
Loading