A developer toolkit to implement Serverless best practices and increase developer velocity.
We are pleased to announce our first security feature: Data Masking. You can now encrypt, decrypt, or irreversibly erase sensitive information to protect data confidentiality.
We also made enhancements to our OpenAPI utility, and fixed some bugs!
β Huge thanks to our new contributor: @maauk
You can now encrypt, decrypt, or irreversibly erase sensitive information to protect data confidentiality.
We partnered with the AWS Crypto team to offer a thin layer on top of the AWS Encryption SDK and Amazon KMS, optimized to run on AWS Lambda ephemeral environments.
At launch, Data Masking solves three common use cases, with a fourth one (field encryption) coming.
It wouldn't be awesome if we didn't mention that we spent a few months crafting several code snippets, use cases, diagrams, and a simplified terminology to help you digest common industry security practices.
Enough with the talk :) Here's a working code snippet with these use cases combined.
from __future__ import annotations
import os
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_masking import DataMasking
from aws_lambda_powertools.utilities.data_masking.provider.kms.aws_encryption_sdk import (
AWSEncryptionSDKProvider,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
KEY_ONE = os.getenv("KMS_KEY_ARN", "")
KEY_TWO = os.getenv("KMS_KEY_TWO_ARN", "")
logger = Logger()
encryption_provider = AWSEncryptionSDKProvider(keys=[KEY_ONE, KEY_TWO]) # encrypt/decrypt operations
data_masker = DataMasking(provider=encryption_provider)
@logger.inject_lambda_context
def lambda_handler(event: dict, context: LambdaContext) -> dict:
data: dict = event.get("body", {})
logger.info("Erasing fields email, address.street, and company_address")
erased: dict = data_masker.erase(data, fields=["email", "address.street", "company_address"]) # values become '*****'
# tenant_id being optional metadata that must match in decrypt for further protection
encrypted: str = data_masker.encrypt(data, tenant_id=event.get("tenant_id", ""))
decrypted: dict = data_masker.decrypt(data, tenant_id=event.get("tenant_id", ""))
return erased
ββ Huge thanks to @seshubaws for the extensive work on this feature!
Our enhanced OpenAPI utility now enables you to seamlessly incorporate headers into your API specifications.
from typing import List
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.params import Header
from aws_lambda_powertools.shared.types import Annotated
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver(enable_validation=True)
@app.get("/hello")
def get_hello(header2: Annotated[List[str], Header()], header1: Annotated[str, Header()]):
print(header2)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
9aad7af
to a4a2029
in /docs (#3679) by @dependabot58eef6c
to 9aad7af
in /docs (#3670) by @dependabot9aad7af
to a4a2029
in /docs (#3679) by @dependabot58eef6c
to 9aad7af
in /docs (#3670) by @dependabot@dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @leandrodamascena, @maauk and @seshubaws
We're excited to introduce two significant new features in our Idempotency utility:
We also made enhancements to our OpenAPI and Event Sources utilities, and fixed some bugs!
β Huge thanks to our new contributors: @sbailliez, @Thomas-McKanna, and @dastra!
You can now seamlessly switch between DynamoDB and Redis as a persistence storage layer. This allows you to leverage the Idempotency utility in previously unavailable scenarios, such as handling responses over 400kb.
We remain committed to making efforts to introduce new backends, and we would love to hear from you on which backend we should prioritize next!
β β Huge thanks to @roger-zhangg for your help!
AWS has recently introduced support for ReturnValuesOnConditionCheckFailure
, a feature designed to streamline conditional write operations and reducing costs in Amazon DynamoDB. With this enhancement, Powertools for AWS Lambda now optimistically attempts to write items to DynamoDB. If the item already exists, it seamlessly returns it from DynamoDB without requiring an additional operation.
S3 Batch Operations are now supported on the Event Source utility. When using S3 Batch Operations, a Lambda function can be used to execute various operations. For each task, you can choose to either:
A) Return the task as a success (default) B) Return the task as temporarily failed (e.g., due to a timeout when connecting to other services) and enable automatic retries. C) Return the task as permanently failed, resulting in job failure.
This example illustrates how you can return different status based on your specific execution logic.
You can now define additional response models within the OpenAPI schema utility, allowing you to leverage existing Pydantic data models and classes.
Last but not least, you can now effortlessly download the OpenAPI schema file directly from the SwaggerUI.
2f29d71
to 58eef6c
in /docs (#3633) by @dependabot2f29d71
to 58eef6c
in /docs (#3633) by @dependabot@Thomas-McKanna, @dastra, @dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @leandrodamascena, @roger-zhangg, @rubenfonseca and @sbailliez
This release introduces enhanced top-level Tags in the OpenAPI schema, and adds Lambda layer support in AWS Canada region (ca-west-1). It also includes improvements to documentation and bug fixes.
β Huge thanks to our new contributors: @kamilturek (Middleware factory docs), and @troyswanson (Parameters).
Docs: Event Handler OpenAPI
The OpenAPI schema now supports more comprehensive top-level Tags, simplifying the organization and categorization of API operations.
Last but not least, kudos to @Mavtti for bringing attention to an issue in our error handling within the Parser utility, thereby improving the development experience.
9af3b7e
to 2f29d71
in /docs (#3559) by @dependabot9af3b7e
to 2f29d71
in /docs (#3559) by @dependabot@dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @kamilturek, @leandrodamascena, @rubenfonseca and @troyswanson
This patch release fixes three bugs related to the OpenAPI and Swagger features:
β Huge thanks to @MCR2019 and @danikenan for reporting those issues! β
@dependabot, @dependabot[bot], @github-actions, @github-actions[bot] and @rubenfonseca
This patch release fixes a bug when using the event handler Router together with OpenAPI metadata.
Huge thanks to @MCR2019 for finding and reporting this out!
@dependabot, @dependabot[bot], @github-actions, @github-actions[bot] and @rubenfonseca
This release follows the newly announced Python 3.12 runtime in AWS Lambda π
876b39c
to 9af3b7e
in /docs (#3486) by @dependabot876b39c
to 9af3b7e
in /docs (#3486) by @dependabot@dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @heitorlessa, @leandrodamascena and @rubenfonseca
This patch release fixes a bug in the Logger utility, ensuring it prints non-ASCII values as-is instead of escaping them. Previously, non-ASCII characters were displayed in an encoded format.
Huge thanks to @bml1g12 for reporting this! :star:
876b39c
to 9af3b7e
in /docs (#3486) by @dependabot20241c6
to 876b39c
in /docs (#3477) by @dependabot8c72011
to 20241c6
in /docs (#3470) by @dependabot876b39c
to 9af3b7e
in /docs (#3486) by @dependabot20241c6
to 876b39c
in /docs (#3477) by @dependabot8c72011
to 20241c6
in /docs (#3470) by @dependabot@dependabot, @dependabot[bot], @github-actions, @github-actions[bot] and @heitorlessa
Standardized Retrieval of API Gateway Authorizer Context
This release standardizes how to retrieve the API Gateway authorizer context in both v1 and v2 of the Lambda handler. Previously, there were inconsistencies in how customers retrieved authorization context between V1 and V2 payloads.
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools import Logger
app = APIGatewayRestResolver()
logger = Logger(level="INFO")
@app.get("/")
def initialize():
context: dict = app.current_event.request_context.authorizer.get_context()
logger.info(context.get("tenantId"))
def lambda_handler(event, context):
return app.resolve(event, context)
We also fixed an issue with dependency injection when using decorators in the Lambda handler function. Previously, dependencies were not properly injected when decorators were present, leading to runtime errors.
fc42bac
to 8c72011
in /docs (#3416) by @dependabotfc42bac
to 8c72011
in /docs (#3416) by @dependabot@dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @heitorlessa, @leandrodamascena and @sthulb
This patch release fixes a bug on the event handler utility, where using the compress option was causing an error.
Huge thanks to @dacianf for reporting this!
@github-actions, @github-actions[bot] and @rubenfonseca
This release adds support for Data Validation and automatic OpenAPI generation in Event Handler.
Even better, it works with your existing resolver (API Gateway REST/HTTP, ALB, Lambda Function URL, VPC Lattice)!
Did you read that correctly? Yes, you did! Look at this:
Docs: Data validation
By adding enable_validation=True
to your resolver constructor, youβll change the way the resolver works. We will:
This moves data validation responsibilities to Event Handler resolvers, reducing a ton of boilerplate code. You can now focus on just writing your business logic, and leave the validation to us!
from typing import List, Optional
import requests
from pydantic import BaseModel, Field
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver(enable_validation=True)
class Todo(BaseModel):
userId: int
id_: Optional[int] = Field(alias="id", default=None)
title: str
completed: bool
@app.post("/todos")
def create_todo(todo: Todo) -> str:
response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
response.raise_for_status()
return response.json()["id"]
@app.get("/todos")
@tracer.capture_method
def get_todos() -> List[Todo]:
todo = requests.get("https://jsonplaceholder.typicode.com/todos")
todo.raise_for_status()
return todo.json()
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
Docs: OpenAPI generation
When you enable data validation, we automatically inspect your API in a way that makes it possible to generate OpenAPI specifications automatically!
You can export the OpenAPI spec for customization, manipulation, merging micro-functions, etc., in two ways:
app.get_openapi_schema()
app.get_openapi_json_schema()
Hereβs one way to print the schema if you were to run your Python Lambda handler locally:
import requests
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.models import Contact, Server
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver(enable_validation=True)
@app.get("/todos/<todo_id>")
def get_todo_title(todo_id: int) -> str:
todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
todo.raise_for_status()
return todo.json()["title"]
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
if __name__ == "__main__":
print(
app.get_openapi_json_schema(
title="TODO's API",
version="1.21.3",
summary="API to manage TODOs",
description="This API implements all the CRUD operations for the TODO app",
tags=["todos"],
servers=[Server(url="https://stg.example.org/orders", description="Staging server")],
contact=Contact(name="John Smith", email="[email protected]"),
),
)
Can you see where this is going? Keep reading :)
Docs: Swagger UI
Last but not least... you can now enable an embedded Swagger UI to visualize and interact with your newly auto-documented API!
from typing import List, Optional
import requests
from pydantic import BaseModel, Field
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver(enable_validation=True)
app.enable_swagger() # by default, path="/swagger"
@app.get("/todos")
@tracer.capture_method
def get_todos() -> List[Todo]:
todo = requests.get("https://jsonplaceholder.typicode.com/todos")
todo.raise_for_status()
return todo.json()
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
The Swagger UI appears by default at the /swagger
path, but you can customize this to serve the documentation from another path, and specify the source for Swagger UI assets.
We canβt wait for you try this new features!
@dependabot, @dependabot[bot], @github-actions, @github-actions[bot], @heitorlessa, and @rubenfonseca