NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #997 most downloaded on PyPI
Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity.
Last release 14 days ago
15 Sep 2026
Ships fairly regularly
a new release about every 3 weeks
Nearly every release is documented
notes for 58 of the last 60 stable releases
3 versions withdrawn
withdrawn after publishing
7 years old
530 releases · first in 2019
This patch release addresses three areas:
This patch release addresses three areas:
Big welcome to our new contributors: @ashutoshojha5, @meckhardt
MIT No Attribution (previously MIT-0 was unrecognized) - huge thanks to @meckhardt.@ashutoshojha5, @dependabot, @dependabot[bot], @leandrodamascena, @meckhardt, @rubenfonseca and Release bot
One column per quarter.
This release adds support for time-based Feature Flags, and simplified support for returning a custom HTTP status code on the event handler.
This release adds support for time-based Feature Flags, and simplified support for returning a custom HTTP status code on the event handler.
This release enhanced the Feature Flags utility with a new time-based feature. You can now enable certain features in your application during the weekends, at a certain period or day, across one or more time zones. Big thanks to @ran-isenberg! Check out this sample that enables a feature on weekends in NYC TZ:
We also have a nice addition to our event handler utility. You can now add a second return argument if you want to customize the returned HTTP status code. ⭐ Big thanks to @theoutsider24 for your first contribution! ⭐
@dependabot, @dependabot[bot], @leandrodamascena, @ran-isenberg, @rubenfonseca, @theoutsider24 and Release bot
In this release, we added new specialised routers for all supported event handlers. This way you'll get type hints from your IDE as you access the cur
In this release, we added new specialised routers for all supported event handlers. This way you'll get type hints from your IDE as you access the current_event property.
We also improved the exception logging in the idempotency utility with additional context taken from the raised downstream exception.
⭐ Finally, we thank our two new contributors @aidansteele and @pawosty for helping us iron out some important bugs! ⭐
@aidansteele, @dependabot, @dependabot[bot], @leandrodamascena, @mploski, @pawosty, @rubenfonseca and Release bot
This release improves logging incoming events that might be wrapped in common containers such as Event Source Data Classes, Pydantic/Parser BaseModels
This release improves logging incoming events that might be wrapped in common containers such as Event Source Data Classes, Pydantic/Parser BaseModels, and Dataclasses.
Also we addressed an issue when a header is set explicit to None.
Big thanks to @jasmarc for the bug report!
Example:
import json
from aws_lambda_powertools.event_handler import (
APIGatewayRestResolver,
Response,
)
app = APIGatewayRestResolver()
@app.get("/none")
def hello():
headers = {"This-Header-Will-Be-Omitted": None}
response = {"hello": "world"}
return Response(body=json.dumps(response), headers=headers, status_code=200)
def lambda_handler(event, context):
return app.resolve(event, context)
@dependabot, @dependabot[bot], @heitorlessa, @leandrodamascena and Release bot
This release introduces a brand new streaming utility to handle datasets larger than the available memory.
This release introduces a brand new streaming utility to handle datasets larger than the available memory.
This addresses scenarios where you want to process Amazon S3 objects as seekable data streams, with minimal memory consumption.
We've added support for popular data transformations to decompress and deserialize data (gzip, CSV and ZIP). This way you can transform the data while streaming, and only use the minimum amount of memory necessary.
You can also create your own data transformations and add them to the pipeline. For example, here's how to use the ijson library to parse JSON as a data stream:
We plan to support more data transformations in the future too!
🌟 Would you like to see a specific data transformation supported in Powertools? Drop us a line in the python channel of our Discord.
@dependabot, @dependabot[bot], @heitorlessa, @rubenfonseca and Release bot
This patch release address an issue in Event Handler REST API to support dynamic routes with an equal sign (=).
This patch release address an issue in Event Handler REST API to support dynamic routes with an equal sign (=).
Big thanks to @baraksalomon for the bug report!
Previously, a dynamic route containing = would result in a not found response (HTTP 404).
Example
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver()
# NOTE: `/token/bXl0b2tlbg==` should be matched and not return 404 now
@app.get("/token/<token>")
def get_token(token: str):
return {"received": f"{token}"}
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
@dependabot, @dependabot[bot], @heitorlessa and Release bot
This release features: (1) Extract CloudWatch Logs sent to Kinesis streams, (2) Handling multiple exceptions in Event Handler REST, and (3) Log uncaug
This release features: (1) Extract CloudWatch Logs sent to Kinesis streams, (2) Handling multiple exceptions in Event Handler REST, and (3) Log uncaught exceptions in Logger.
Big thanks to new contributors: @ascopes (logger static typing), @bnsouza (expand testing docs in Event Handler), @kt-hr (bugfix multiple routes in Event Handler), @mangoes-git (bugfix for fetching SecretBinary)
This address a common use case of centralizing CloudWatch Logs from multiple regions or accounts using Kinesis Data Streams.
You can now easily extract CloudWatch Logs (decompress, decode, etc.) using either Event Source Data Classes or Parser. It also seamless integrates with Batch so you can benefit from partial failure handling among other benefits, when processing large batch of logs from Kinesis.
Event Source Data Classes
from aws_lambda_powertools.utilities.batch import (BatchProcessor, EventType,
batch_processor)
from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import (
KinesisStreamRecord, extract_cloudwatch_logs_from_record)
processor = BatchProcessor(event_type=EventType.KinesisDataStreams)
def record_handler(record: KinesisStreamRecord):
log = extract_cloudwatch_logs_from_record(record)
return log.message_type == "DATA_MESSAGE"
@batch_processor(record_handler=record_handler, processor=processor)
def lambda_handler(event, context):
return processor.response()
Parser
from aws_lambda_powertools.utilities.batch import (BatchProcessor, EventType,
batch_processor)
from aws_lambda_powertools.utilities.parser.models import (
KinesisDataStreamModel, KinesisDataStreamRecord)
from aws_lambda_powertools.utilities.parser.models.kinesis import \
extract_cloudwatch_logs_from_record
processor = BatchProcessor(event_type=EventType.KinesisDataStreams, model=KinesisDataStreamModel)
def record_handler(record: KinesisDataStreamRecord):
log = extract_cloudwatch_logs_from_record(record)
return log.messageType == "DATA_MESSAGE"
@batch_processor(record_handler=record_handler, processor=processor)
def lambda_handler(event, context):
return processor.response()
You can now catch multiple exceptions when registering an exception handler.
This is useful when you have related exceptions you want to handle the same way.
import requests
from aws_lambda_powertools.event_handler import (
APIGatewayRestResolver,
Response,
content_types,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver()
@app.exception_handler([ValueError, requests.HTTPError])
def handle_invalid_limit_qs(ex: ValueError | requests.HTTPError): # receives exception raised
metadata = {"path": app.current_event.path, "query_strings": app.current_event.query_string_parameters}
logger.error(f"Malformed request: {ex}", extra=metadata)
return Response(
status_code=400,
content_type=content_types.TEXT_PLAIN,
body="Invalid request parameters.",
)
@app.get("/todos")
def get_todos():
max_results: int = int(app.current_event.get_query_string_value(name="limit", default_value=0))
todos: requests.Response = requests.get(f"https://jsonplaceholder.typicode.com/todos?limit={max_results}")
todos.raise_for_status()
return {"todos": todos.json()}
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
You can now automatically log uncaught exceptions using Logger via log_uncaught_exceptions=True.
Logger will use Python's exception hook to log such exception with your pre-configured Logger instance for maximum context.
import requests
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext
ENDPOINT = "http://httpbin.org/status/500"
logger = Logger(log_uncaught_exceptions=True)
def handler(event: dict, context: LambdaContext) -> str:
ret = requests.get(ENDPOINT)
# HTTP 4xx/5xx status will lead to requests.HTTPError
# Logger will log this exception before this program exits non-successfully
ret.raise_for_status()
return "hello world"
This would generate the following output in CloudWatch Logs
{
"level": "ERROR",
"location": "log_uncaught_exception_hook:756",
"message": "500 Server Error: INTERNAL SERVER ERROR for url: http://httpbin.org/status/500",
"timestamp": "2022-11-16 13:51:29,198+0100",
"service": "payment",
"exception": "Traceback (most recent call last):\n File \"<input>\", line 52, in <module>\n handler({}, {})\n File \"<input>\", line 17, in handler\n ret.raise_for_status()\n File \"<input>/lib/python3.9/site-packages/requests/models.py\", line 1021, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 500 Server Error: INTERNAL SERVER ERROR for url: http://httpbin.org/status/500",
"exception_name": "HTTPError"
}
POWERTOOLS_DEV (#1695) by @dreamorosi@ascopes, @bnsouza, @dependabot, @dependabot[bot], @dreamorosi, @heitorlessa, @kt-hr, @mangoes-git, @sprkem and Release bot
This release features: (1) a new `get_parameters_by_name` function to fetch multiple Parameter Store parameters from distinct paths, and (2) a new `Ep
This release features: (1) a new get_parameters_by_name function to fetch multiple Parameter Store parameters from distinct paths, and (2) a new EphemeralMetrics class for multi-tenancy and ISV use cases that require multiple metrics namespace.
You can now fetch distinct parameters by their full name with get_parameters_by_name.
Previously, you could only fetch multiple parameters by path via get_parameters. We also heard from customers they'd like more flexibility when fetching parameters.
This new high level function has the following in mind:
Quick flow on how get_parameters_by_name decides which System Manager Parameter Store to use based on per parameter config (or global):
┌────────────────────────┐
┌───▶ Decrypt entire batch │─────┐
│ └────────────────────────┘ │ ┌────────────────────┐
│ ├─────▶ GetParameters API │
┌──────────────────┐ │ ┌────────────────────────┐ │ └────────────────────┘
│ Split batch │───┼──▶│ No decryption required │─────┘
└──────────────────┘ │ └────────────────────────┘
│ ┌────────────────────┐
│ ┌────────────────────────┐ │ GetParameter API │
└──▶│Decrypt some but not all│──────────▶├────────────────────┤
└────────────────────────┘ │ GetParameters API │
└────────────────────┘
You can use EphemeralMetrics class when looking to isolate multiple instances of metrics with distinct namespaces and/or dimensions.
from aws_lambda_powertools.metrics import EphemeralMetrics, MetricUnit
from aws_lambda_powertools.utilities.typing import LambdaContext
metrics = EphemeralMetrics()
@metrics.log_metrics
def lambda_handler(event: dict, context: LambdaContext):
metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1)
By default, Metrics share metrics data across instances. This prevents common data deduplication due to CloudWatch EMF constraints on metric dimensions. However, this limits use cases where you want to create metrics for different applications within the same Lambda function, namely multi-tenancy - whether you use distinct metrics dimensions per metric or multiple metric namespaces.
There are subtle differences between the two:
| Feature | Metrics | EphemeralMetrics |
|---|---|---|
| Share data across instances (metrics, dimensions, metadata, etc.) | Yes | - |
| Default dimensions that persists across Lambda invocations (metric flush) | Yes | - |
@dependabot, @dependabot[bot], @heitorlessa, Release bot
This release features: (1) Logger UX enhancements, (2) New docs community section (We Made This), and (3) New issue form to accept customer references
This release features: (1) Logger UX enhancements, (2) New docs community section (We Made This), and (3) New issue form to accept customer references in our docs.
Big thanks to new contributors 👏: @nayaverdier (logger mypy fix), @pierskarsenbarg (Pulumi docs), and @dmwesterhoff (idempotency_function bug finding)
You can now pass any arbitrary and ephemeral metadata as keyword-arguments when logging: logging.info("Incoming payment", customer_id="...")
Previously, you had to use the extras parameter. This ergonomics improvement makes it easier to surface keys at the root of your structured log on a per log statement basis.
This has been a long overdue and we're so so excited to have this finally out!
You can now set use_rfc3339=True to instruct Logger to change the timestamp key to a value compliant with both RFC3339 and ISO8601: 2022-10-27T16:27:43.738+02:00
Big thanks to @kishaningithub @JJSphar for the discussion and UX here!
<img width="1745" alt="image" src="https://user-images.githubusercontent.com/3340292/198994466-92ae9eb6-a9e0-4067-9741-c28be46c4f92.png">
You can now submit blog posts, workshops, videos, and sample projects featuring Lambda Powertools.
Please check out the early submitted content from our awesome community!
PS: We accept content in any language!
<img width="1765" alt="image" src="https://user-images.githubusercontent.com/3340292/198994589-bb019964-d21b-4aa5-90de-0b814d581345.png">
You can now give us permission to list your organization name in the Lambda Powertools documentation..
This helps us tremendously and new customers looking to learn who's using it. This permission is explicit for our documentation.
<img width="1483" alt="image" src="https://user-images.githubusercontent.com/3340292/198995133-1fb23027-edf5-4319-8ae3-c094ded9de3d.png">
♥️
@dependabot, @dependabot[bot], @heitorlessa, @leandrodamascena, @nayaverdier, @pierskarsenbarg, @ran-isenberg, @rubenfonseca and Release bot
We’ve spent the past 2 months optimizing as much as we could. We kept breaking changes to a minimum to make your transition to v2 as smooth as possibl…
We are extremely happy to announce the smallest release version ever - v2.0.0 🎉🎉!
The number one customer request was reducing package size. Some customers use a different tracing provider. Others used Parser (Pydantic) for data modelling and deep data validation instead of JSON Schema Validation. A few brought large machine learning models and every bit of space they can save matters.
The number two request was ARM64 support. Customers want to benefit from performance and cost savings for their workloads. However, they don’t want to invest additional time testing and compiling dependencies they use daily for ARM64.
We’ve spent the past 2 months optimizing as much as we could. We kept breaking changes to a minimum to make your transition to v2 as smooth as possible.
For backwards incompatible changes, we’ve spent considerable effort making sure they are visible, why we implemented them, and and what to do if they affect you.
Our sincerest appreciation for the community help and patience while we’ve worked on this release - Thank you ❤️!
Table of contents
We dropped support for Python 3.6. We highly recommend moving to the latest Python available in AWS Lambda (3.9).
We now rely on AWS SDK available in the Lambda runtime, and all other dependencies are marked as optional.
There are subtle but important differences in the installation experience - PyPi (all optional) and Lambda Layer (all included).
The compressed package size is now approximately 240K (previously 10M).
We updated each documentation to emphasize when an additional dependency is required (Tracer, Validation and Parser). If you prefer to install all of them, use pip install aws-lambda-powertools[all].
Note. If you need to use newer AWS services or features not available in the Lambda runtime provided SDK, you'll need to bundle boto3 as a dependency.
The compressed package size is now approximately 2M (previously 17M).
Lambda Layer includes all dependencies. They're also optimized for package size and speed (Pydantic now compiled with Cython for ~30-50% speed gain).
We now offer full support to ARM via Lambda Layer and SAR installation. All C-extension dependencies are compiled, optimized and tested against Lambda environment.
You can now safely subscribe to GitHub Release event to know when PyPi and Lambda Layer ARNs have the latest version.
Previously, PyPi, Lambda Layers, and docs were updated after a release notes went live to GitHub (~15m completion). Due to Lambda Layer compilation (x86/ARM64), this process takes on average 30m longer.
As such, we make releases prior to sharing release notes, guaranteeing for customers watching for GitHub Releases that Layer ARNs available in the documentation match PyPi releases (no more delays).
When returning custom responses, you now can add a list of values for a header, including cookies.
Event Handler will seamless adapt to the expected format based on resolver used, whether that is REST API, HTTP API, ALB, or Function URL.
from http import HTTPStatus
from uuid import uuid4
import requests
from aws_lambda_powertools.event_handler import (
APIGatewayRestResolver,
Response,
content_types,
)
from aws_lambda_powertools.shared.cookies import Cookie
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver()
@app.get("/todos")
def get_todos():
todos: requests.Response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos.raise_for_status()
dummy_transactions = [f"{uuid4()}", f"{uuid4()}", f"{uuid4()}"]
multi_value_header = {"X-Transaction-Id": dummy_transactions}
return Response(
status_code=HTTPStatus.OK.value, # 200
content_type=content_types.APPLICATION_JSON,
body=todos.json()[:10],
headers=multi_value_header, # NEW
cookies=[Cookie(name="session_id", value="12345")], # NEW
)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
NOTE. For ALB, you have to explicitly enable multi-value headers in the target configuration.
APIGatewayRestResolver now seamless support route resolution with trailing slashes: /todos/.
Previously, requests with a trailing slash would return HTTP 404 due to route mismatch.
Note. This was not necessary for HTTP API and ALB.
tracer.capture_method or tracer.capture_lambda_handler decorators now use the decorated function or method fully qualified name as the subsegment name.
This enables accurate traces for ABC or Protocol classes, where their method name will be the same but for distinct classes.
Here’s a quick view of deprecated features removed and backwards incompatible changes.
| Area | Change | Code change required | IAM Permissions change required |
|---|---|---|---|
| Batch | Removed legacy SQS batch processor in favour of BatchProcessor. |
Yes | - |
| Environment variables | Removed legacy POWERTOOLS_EVENT_HANDLER_DEBUG in favour of POWERTOOLS_DEV. |
- | - |
| Event Handler | Updated headers response format due to multi-value headers and cookie support. | Tests only | - |
| Event Source Data Classes | Replaced DynamoDBStreamEvent AttributeValue with native Python types. |
Yes | - |
| Feature Flags / Parameters | Updated AppConfig API calls due to GetConfiguration API deprecation. |
- | Yes |
| Idempotency | Updated partition key to include fully qualified function/method names. | - | - |
You will now receive native Python types when accessing DynamoDB records via keys, new_image, and old_image attributes in DynamoDBStreamEvent.
Previously, you'd receive an AttributeValue instance. For most customers, this made it difficult to handle Change Data Capture use cases due to having an additional step to deserialize data to Python native types (str, dict etc) when processing them.
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
DynamoDBStreamEvent,
DynamoDBRecordEventName
)
def send_to_sqs(data: Dict):
body = json.dumps(data)
...
@event_source(data_class=DynamoDBStreamEvent)
def lambda_handler(event: DynamoDBStreamEvent, context):
for record in event.records:
# BEFORE
new_image: Dict[str, AttributeValue] = record.dynamodb.new_image
event_type: AttributeValue = new_image["eventType"].get_value
if event_type == "PENDING":
# deserialize attribute value into Python native type
# NOTE: nested objects would need additional logic
data = {k: v.get_value for k, v in image.items()}
send_to_sqs(data)
# AFTER
new_image: Dict[str, Any] = record.dynamodb.new_image
if new_image.get("eventType") == "PENDING":
send_to_sqs(new_image) # Here new_image is just a Python Dict type
Upgrade guide. No code changes required.
We replaced GetConfiguration API (now deprecated) with GetLatestConfiguration and StartConfigurationSession.
As such, you must update your IAM Role permissions to allow the following IAM actions:
appconfig:GetLatestConfigurationappconfig:StartConfigurationSessionUpgrade guide. No code changes required
Similar to Tracer, we also updated Idempotency to use fully qualified name. This means that recent non-expired idempotent transactions will be ignored.
We removed the deprecated PartialSQSProcessor class and sqs_batch_processor decorator in favour of BatchProcessor launched 11 months ago.
BatchProcessor provides nearly the same developer experience while improving speed and security, as it natively integrates with Lambda ReportBachItemFailures feature.
We removed the POWERTOOLS_EVENT_HANDLER_DEBUG environment variable in favour of POWERTOOLS_DEV.
POWERTOOLS_DEV consolidate additional features to ease prototyping against local or non-production environment.
@barreeeiroo, @dependabot, @dependabot[bot], @heitorlessa, @kt-hr, @leandrodamascena, @rubenfonseca, @ryandeivert, @shanab, @walmsles, @ran-isenberg and Release bot
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This patch release is focused on Parser with SNS FIFO.
This patch release is focused on Parser with SNS FIFO.
When using SNS FIFO topics, SNS removes three fields, namely: SigningCertUrl, Signature, and SignatureVersion. As such, we're marking these fields as Optional to prevent ValidationError. Thanks to @plondino for flagging this.
NOTE: In the future, we'll be looking at creating separate FIFO Models.
Big thanks to @digitalisx and @senmm who helped addresses typos in the documentation. Thank you for helping make the documentation better for everyone 💞!
NOTE: This release will be available in PyPi in roughly 5m, and Lambda Layers across all AWS commercial regions in ~15m.
@dependabot, @dependabot[bot], @digitalisx, @eldritchideen, @heitorlessa, @rubenfonseca, @senmm and Release bot
This release fixes a bug in Metrics where subsequent instances didn't share a dimensions set - Huge thanks to @cfchou for flagging it and for being su
This release fixes a bug in Metrics where subsequent instances didn't share a dimensions set - Huge thanks to @cfchou for flagging it and for being super proactive in creating a test ahead of time.
This also adds support for Amazon Kinesis Firehose in Parser - big thanks to @ran-isenberg!
<img width="837" alt="image" src="https://user-images.githubusercontent.com/3340292/194909969-a4a5e1f8-c18c-4fc6-bfd1-e00dcf2c292a.png">
@am29d, @dependabot, @dependabot[bot], @heitorlessa, @ran-isenberg and Release bot
This release adds support for (1) Lambda context in Batch processing, (2) Context sharing support in REST and GraphQL APIs, and (3) Consolidate featur
This release adds support for (1) Lambda context in Batch processing, (2) Context sharing support in REST and GraphQL APIs, and (3) Consolidate features to make prototyping local/non-prod environments easier. Bug fixes and other minor improvements are at the bottom.
You can now opt-in to receive a Lambda context object if your function has a parameter named lambda_context.
This unlocks long-running data pipelines, where you need to know how much time you have left before your function times out - thanks to @mew1033 for the feature request.
from typing import Optional
from aws_lambda_powertools.utilities.batch import (BatchProcessor, EventType,
batch_processor)
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.typing import LambdaContext
processor = BatchProcessor(event_type=EventType.SQS) # or DynamoDB, Kinesis
# lambda_context will be populated at runtime
def record_handler(record: SQSRecord, lambda_context: Optional[LambdaContext] = None): ...
@batch_processor(record_handler=record_handler, processor=processor)
def lambda_handler(event, context: LambdaContext):
return processor.response()
You can now inject contextual information before or during event routing. This is the precursor to Middleware support in Event Handler. This unlocks two common use cases:
Router() feature, and you need to pass data from App() without running into potential circular import issuesThanks to @MaartenUreel for the feature request, and @benbridts, @gwlester, and @walmsles for the discussion to get to an optimal UX.
UX
Entrypoint: app.py
import todos
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.utilities.typing import LambdaContext
app = APIGatewayRestResolver()
app.include_router(todos.router)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
app.append_context(is_admin=True) # arbitrary number of key=value data
return app.resolve(event, context)
todos.py
import requests
from requests import Response
from aws_lambda_powertools.event_handler.api_gateway import Router
ENDPOINT = "https://jsonplaceholder.typicode.com/todos"
router = Router()
@router.get("/todos")
def get_todos():
is_admin: bool = router.context.get("is_admin", False)
todos = {}
if is_admin:
todos: Response = requests.get(ENDPOINT)
todos.raise_for_status()
todos = todos.json()[:10]
return {"todos": todos}
Thanks to @pmarko1711, Logger now uses pretty-print when you set POWERTOOLS_DEV=1 environment variable. This also helps us consolidate features where we can make it easier to prototype locally or against a non-production environment.
When raising bug reports, you can now get Lambda Powertools debug logs with POWERTOOLS_DEBUG=1. Previously, you had to change your source code to explicitly enable debugging in Powertools.
@Tankanow, @dependabot, @dependabot[bot], @heitorlessa, @kbakk, @pmarko1711, @rubenfonseca
fix(deps): bump dev dep mako version to address CVE-2022-40023 (#1524) by @rubenfonseca
This patch release fixes a bug in the Serverless Application Repository (SAR) Extras Layer for customers using Parser/Pydantic, where the module wasn't available.
@dependabot, @dependabot[bot], @rubenfonseca and Release bot
Due to a problem with the release pipeline, we had to yank the v1.29.0 release from PyPI. To fix the problem we are releasing v1.29.1 with no addition
Due to a problem with the release pipeline, we had to yank the v1.29.0 release from PyPI. To fix the problem we are releasing v1.29.1 with no additional changes.
This release adds a new major integration with Apache Kafka (both Amazon Managed Streaming for Apache Kafka (MSK) and self-managed clusters). We also extended the Event Source Data Classes to support CloudWatch dashboard custom widgets.
Powertools can now seamlessly consume events sourced from Apache Kafka (MSK, self-managed). Apache Kafka as an event source operates similarly to using Amazon Simple Queue Service (Amazon SQS) or Amazon Kinesis.
You can easily consume a Kafka event by using the KafkaEvent Data Class:
If you want extra flexibility and validation, you can also use the Pydantic models KafkaMskEventModel (for Apache MSK) and KafkaSelfManagedEventModel (for self-managed clusters):
If your Kafka event payload has a known structure, you could also leverage the KafkaEnvelope model.
Thank you @lyoung-confluent and @ran-isenberg for your contribution!
🌟 Would you like to see more integrations between Apache Kafka and Lambda Powertools? Drop us a line in the python channel of our Discord.
We also added support for handling events generated by CloudWatch dashboard custom widgets:
Big thanks to @sthuber90 for your contribution!
@dependabot, @dependabot[bot], @heitorlessa, @leandrodamascena, @lyoung-confluent, @ran-isenberg, @rubenfonseca, @sthuber90 and Release bot
Nothing published for this version
We’ve got a new RFC for our first major version v2.0.0 - aiming to end of September. We’re striving to make the bare minimum breaking changes given th…
This release brings major updates: upcoming v2 to drop Python 3.6 support, Lambda Function URL support in Parser(Pydantic), and.... 🥁 🥁 🥁 our new public Discord Server - join us
Oh hey, please welcome Leandro as our new full-time maintainer - @leandrodamascena🎉
This release adds support for Lambda Function URL for Parser (Pydantic Model) - big thanks to @ran-isenberg. It extends the work previously done in v1.27.0 for Event Source Data Classes.
We’ve got a new RFC for our first major version v2.0.0 - aiming to end of September. We’re striving to make the bare minimum breaking changes given the timeframe we have. Please get acquainted with the breaking changes (e.g., legacy sqs_batch_processor, Event Handler to support multi-value headers) and do share your feedback.
We know that a good documentation is critical for adopting any open-source project. We continue to make progress in rewriting code snippets to be more realistic, and apply our code quality standards like we do for any other change.
In this release, we've refactored the JMESPath Functions,, Validation, Typing, and Middleware factory documentation to include real examples of using these features. You can follow progress on upcoming refactoring in our board of activities.
:eyes: Take a look at this gigantic yet realistic all-in-one middleware combining Powertools features.
Here’s a sneak peek:
We’ve published our internal framework to create and run E2E testing via GitHub Actions. This allow us to start working on further complement our functional testing, and soon begin wider coverage on integration tests. We’ll be documenting how customers can contribute E2E soon - you can find the internal details here.
@am29d, @dependabot, @dependabot[bot], @heitorlessa, @leandrodamascena, @peterschutt, @ran-isenberg, @rubenfonseca, @sthuber90 and Release bot
This is our feature release after our extended pause, we couldn't be more excited to bring you: Lambda Function URL, Metrics now supports up to 29 dim
This is our feature release after our extended pause, we couldn't be more excited to bring you: Lambda Function URL, Metrics now supports up to 29 dimensions, and Pre-configured GitPod.io dev environment for contributors.
Huge thanks to a new contributor: @leandrodamascena
You can now use LambdaFunctionUrlResolver to seamless develop with Lambda Function URL. Developer experience remains exactly the same for other REST Event Handlers, making it easier to switch between Application Load Balancer, and API Gateway (REST and HTTP) APIs.
Correlation ID support and documentation have been documented accordingly - here's how it looks like!
For new contributors and those new to Python, @leandrodamascena contributed a pre-configured Cloud-based development environment based on Gitpod.io product. When visiting our contribution guide, you will now see a link to instantly spin up a dev environment pre-configured with all dependencies, linters, and git pre-commit hooks to fast track reviews.
Amazon CloudWatch Metrics made tons of improvements 2 days ago. This release takes advantage of the new metric dimension to increase our validation limit to 29 (MAX-1). By default, we add a service dimension to more easily identify metrics for a given service, hence the limit being 29 and not 30.
We plan on closing the gap on releases with Lambda Layer versioning by automatically generating documentation for all regions. This means publishing 5 additional regions customers asked for support and making it easier to find out which Lambda Layer has which PyPi version. Beyond that, docs and Event Handler improvements, and maybe a surprise if time allows.
@dependabot, @dependabot[bot], @heitorlessa, @leandrodamascena, @rubenfonseca, Docs and Release bot
This release adds a change to the idempotency utility, expiring in-progress executions if the Lambda function times out halfway through.
This release adds a change to the idempotency utility, expiring in-progress executions if the Lambda function times out halfway through.
The new feature requires no code changes. However, if you're using just the idempotent_function decorator, you will want to register the Lambda context on your handler to take advantage of this feature:
To prevent against extended failed retries when a Lambda function times out, Powertools now calculates and includes the remaining invocation available time as part of the idempotency record. If a second invocation happens after this timestamp, and the record is marked as INPROGRESS, we will execute the invocation again as if it was in the EXPIRED state (e.g, expire_seconds field elapsed).
We also enabled static analysis (mypy) on the new statically typed documentation snippets - big thanks to @VictorCovalski for spotting one issue in Event Handler that later led to 50 other fixes.
Oh! Did I also tell you we’ve got a new full time maintainer? Big welcome to Ruben :wink: https://twitter.com/rubenfonseca
@VictorCovalski, @dependabot, @dependabot[bot], @heitorlessa, @rubenfonseca and Release bot
> Third-time is the luck of the Irish (new security release mechanisms)
Third-time is the luck of the Irish (new security release mechanisms)
This patch release addresses an internal issue for Event Handler REST API compression feature when headers value is None -- Huge thanks for spotting and fixing it as a first-time contributor @tbuatois!!
We now have public Lambda Layers deployed across all AWS commercial regions (22) - stellar multi-month work by @am29d. We have one last work to complete before we share the additional ARNs with everyone -- automate documentation generation for every Layer Version::Region mapping.
Lastly on Operational Excellence, we've implemented further security layers for GitHub Actions following GitHub's Hardening Guide, and created a Maintenance Issue template to make tech debt work more visible to all customers and future maintainers.
@am29d, @dependabot, @dependabot[bot], @heitorlessa, @tbuatois and Release bot
This patch release addresses a packaging regression that introduced mypy boto3 typing packages as prod dependencies instead of dev dependencies -- hug
This patch release addresses a packaging regression that introduced mypy boto3 typing packages as prod dependencies instead of dev dependencies -- huge thanks to @tb102122 for spotting this 30m ago.
Everything else in this release is related to operational excellence on improving GitHub Actions workflow performance, and last steps to make our new Lambda Layer release pipeline ready.
@am29d, @dependabot[bot], @heitorlessa, @rubenfonseca and Release bot
This release continues our focus on Operational Excellence with several GitHub Actions to improve our automation on governance tasks (e.g., PR labelli
This release continues our focus on Operational Excellence with several GitHub Actions to improve our automation on governance tasks (e.g., PR labelling, security, release automation, dependencies, etc.).
We also moved the Lambda Layer release pipeline to Github Actions (#1278). This will expand the official Lambda Layer to all commercial AWS regions, once operational. Huge thanks to @am29d!
In addition, we laid the groundwork for end-to-end (E2E) testing, starting with the core features (logger, metrics, tracer). Thank you @mploski!
@am29d, @dependabot, @dependabot[bot], @heitorlessa, @mploski and @rubenfonseca
This patch release addresses an issue with Lambda handlers with custom signatures (*args, kwargs) when using [`logger.inject_lambda_context` decorator
This patch release addresses an issue with Lambda handlers with custom signatures (*args, **kwargs) when using logger.inject_lambda_context decorator - thanks to @RonRandemaker
Thanks to @kbakk, we now have officially documented how to inject standard logging record attributes when using Logger.
This release continues our focus on Operational Excellence with several GitHub Actions to improve our automation on governance tasks (e.g., PR labeling, Comments on issues closed, Markdown linting rules etc.).
Last but not least, we've started revamping all documentation examples, including making some of them more realistic to how customers would code.
Ah! Before I forget...huge thanks to new contributors @RonRademaker, @kbakk.
@RonRademaker, @dependabot, @dependabot[bot], @heitorlessa and @kbakk
This release addresses CORS Pre-flight issue when using Event Handler with ALB. Prior to this release, ALB would return HTTP 502 if Lambda would retur
This release addresses CORS Pre-flight issue when using Event Handler with ALB. Prior to this release, ALB would return HTTP 502 if Lambda would return a null body for a Pre-flight (HTTP OPTIONS) request. This release sets body to an empty string for compliance, and it doesn't affect API Gateway/AppSync features.
Shoutout to @francispeabody for spotting this behaviour, as CORS Specification doesn't dictate whether there should be a response or not leaving to each service to make their own judgement.
@am29d, @dependabot, @dependabot[bot] and @heitorlessa
This is an emergency release to ensure Metrics utility raises an exception when customers add 9 user-defined metric dimensions.
This is an emergency release to ensure Metrics utility raises an exception when customers add 9 user-defined metric dimensions.
Huge thanks to @dnlopes for spotting this bug thus warranting this emergency release.
Background
Lambda Powertools adds a service CloudWatch Metric Dimension on top of user-defined dimensions (max 8). Prior to this release, customers were able to add 9 user-defined dimensions leading to a total of 10 metric dimensions. This makes CloudWatch EMF fail silently and not convert them as metrics.
The confusion stems from CloudWatch Metrics and CloudWatch Logs EMF having different limits. CloudWatch Metrics support 10 dimensions while CloudWatch EMF (async) support 9. For EMF, all validation must happen client side as EMF fails silently should any limit is hit and no feedback is given.
We provided this feedback to the CloudWatch EMF team, and they're looking at ways to improve customers feedback on the server-side so it's less reliant on client-side validation.
@dependabot, @dependabot[bot] and @heitorlessa
This release unblocks customers using Parameters utility when trying to use VPC private endpoints, or looking to bring their own boto3 clients to ease
This release unblocks customers using Parameters utility when trying to use VPC private endpoints, or looking to bring their own boto3 clients to ease testing, custom settings, etc.
User Experience
As part of this release, we've made progress on a few governance topics such as a new dedicated board of activities and our first iteration of the new roadmap process - you can read more details on our current pause message update.
@dependabot, @dependabot[bot], @heitorlessa, @mew1033, @peterschutt, @ran-isenberg and @sthulb
chore(ci): disable mergify configuration after breaking changes (#1188) by @heitorlessa
This patch release addresses the following main issues:
ServiceError like NotFound as part of an exception handler ended up propagating a ServiceError instead of converting into a non-successful response (e.g., 4xx)date field type annotation in SESEvent to string instead of list, and missing fields like cc, bcc, sender, and reply_to, and dkim related fieldsdate field typing and add missing fields (#1045) by @michaelbrewer@dependabot, @dependabot[bot], @heitorlessa, @huonw, @michaelbrewer, and @sthulb
Quick patch release to fix jmespath pip marker to exact Python 3.6 version to address conflict in later versions.
Quick patch release to fix jmespath pip marker to exact Python 3.6 version to address conflict in later versions.
@dependabot[bot], @heitorlessa and @mergify[bot]
This patch release updates jmespath marker to address a dependency conflict for customers bundling the latest version of boto3, as part of their Lambd
This patch release updates jmespath marker to address a dependency conflict for customers bundling the latest version of boto3, as part of their Lambda deployment package. Boto's latest version upgraded jmespath to 1.0 which drops support for Python 3.6.
@Dunedan, @dependabot, @dependabot[bot], @heitorlessa, @corey-cole, @sthulb, and @michaelbrewer
This patch release addresses bugs on Logger, Event Handler REST API, Idempotency, and a static type annotation on middleware factory utility.
This patch release addresses bugs on Logger, Event Handler REST API, Idempotency, and a static type annotation on middleware factory utility.
We also updated our issues to use the new GitHub Issue Form, including a new Ask a Question, and Static typing mismatch report. The latter addresses an increase of bug reports that are better categorized as static typing mismatch - this will help us understand whether we might be able to support both Mypy and Pyright in the future.
Thanks to @eliasbrange, customers using clear_state=True will no longer be affected by custom keys being accidentally removed in subsequent invocations - a regression introduced two releases ago. We improved tests on clear_state functionality.
Thanks to @ojongerius, customers changing the incoming event within the Lambda handler will no longer impact idempotency hash calculation. This was caused by Python's default pass by reference behaviour.
Thanks to @sthuber90, customers receiving API calls that contain whitespace in path parameters - /customer/power tools - will no longer return 404s. Despite API Gateway correctly decoding whitespace, our regex for safe and unsafe URI chars missed an explicit whitespace.
Thanks to @huonw, Mypy customers using --strict mode will no longer complain about the return type of custom decorators using lambda_handler_decorator. It's a hard problem to solve considering our support for Python 3.6+. Later in the year, we'll look into the value-effort ratio of supporting strict mode, and how to gradually bring typing_extensions package across the board.
Huge thanks to new contributors: @sthuber90, @eliasbrange, and @ojongerius
@eliasbrange, @heitorlessa, @huonw, @mergify[bot], @ojongerius and @sthuber90
This patch release fixes a regression in Logger introduced in 1.25.3 when using clear_state=True. A fix for customer formatters introduced in 1.25.3 e
This patch release fixes a regression in Logger introduced in 1.25.3 when using clear_state=True. A fix for customer formatters introduced in 1.25.3 ended up introducing a regression, where standard keys like level, location and timestamp no longer being present when using clear_state=True for the default Logger formatter.
Thanks to @alexanderluiscampino for a high quality bug report, and @HAK-CODE for confirming the issue two hours ago, we managed to reproduce it quickly, improved our tests, and thus making this emergency release.
@heitorlessa and @mergify[bot]
Thanks to @DandyDev, this emergency release fixes a regression on logger utils to clone formatting configuration from Powertools Logger to external Lo
Thanks to @DandyDev, this emergency release fixes a regression on logger utils to clone formatting configuration from Powertools Logger to external Loggers.
@heitorlessa, @mergify[bot] and @mploski
This is a patch release for those using Logger utils to clone Lambda Powertools Logger configuration to all existing Python Loggers.
This is a patch release for those using Logger utils to clone Lambda Powertools Logger configuration to all existing Python Loggers.
Thanks to @DandyDev for spotting a log duplication for external loggers, and for @mploski for helping fix it.
@heitorlessa, @mploski and Michal Ploski
This patch release is strictly about customers bringing their own Logging Formatter.
This patch release is strictly about customers bringing their own Logging Formatter.
This release ensures that when using Clear State with a custom formatter, all previously added keys will be cleared. It also guarantees that Lambda Context information will no longer be cleared abruptly before logs are printed (also for custom formatters).
There is no code change needed on customers end. We also improved the documentation to clarify the most common use cases on bringing your own formatter, and exceptional cases where you want to replace the entire Powertools Logger Formatter logic.
Huge thanks to @jacobdarrossi for spotting, and @ilias-at-adarma for confirming this bug.
@dreamorosi and @heitorlessa
This patch release addresses two main bugs: 1/ API Gateway Lambda Authorizer data class regex to validate for proxy resources (proxy+), and 2/ metrics
This patch release addresses two main bugs: 1/ API Gateway Lambda Authorizer data class regex to validate for proxy resources (proxy+), and 2/ metrics to be automatically flushed when a single metric has 100 values.
Next release will focus on two newly identified bugs on Logger (custom formatter, child logger). Subsequently, our bandwidth remains focused on addressing the subpar experiences and operational excellence.
@darnley, @dependabot, @dependabot[bot], @heitorlessa, @knightjoel and @michaelbrewer
Emergency release to fix a critical bug in the new BatchProcessor feature spotted by Huon Wilson@huonw, and thanks to @michaelbrewer for the quick fix
Emergency release to fix a critical bug in the new BatchProcessor feature spotted by Huon Wilson@huonw, and thanks to @michaelbrewer for the quick fix. The issue was exceptions not being consistently cleared between invocations (cold vs warm state).
@heitorlessa, @mergify[bot] and @michaelbrewer
We will take everyone's input as much as possible to help shape what breaking changes are necessary, how we might facilitate new utilities like Testin…
This release continues to focus on addressing suboptimal experiences (a.k.a papercuts).
But first... big thanks to our first time contributors @am1ru1, @houbie - thank you for helping us improve everyone's experience!
ApiGatewayResolver to access event properties, customers didn't have full IntelliSense support from their IDEs. This release introduces APIGatewayRestResolver, APIGatewayHttpResolver, and ALBResolver to accurately provide self-documented access to all properties available - big thanks to @michaelbrewer.We're going to fully turn our attention to our list of papercuts. Due to new features and other bugs, we let it slip a couple of times, one being as old as ~7 months. We also learned that a central roadmap didn't work well for us. It made planning harder due to the split view (2x repos to look at) situation despite its best intentions.
With GitHub's new Beta projects, we're confident we can migrate the roadmap back to each repository and give the visibility customers have been asking us. That is work we're currently doing, themes that need further research, areas we'd love contributions, and ideas we'd love to execute but lack bandwidth.
Once this is complete, we will start a draft RFC of what a modularized Powertools v2 could look like. We will take everyone's input as much as possible to help shape what breaking changes are necessary, how we might facilitate new utilities like Testing, and what an early beta as well as migration guide should look like.
@am1ru1, @cakepietoast, @dependabot, @dependabot[bot], @heitorlessa, @houbie, @mergify[bot], @michaelbrewer, @mploski and @whardier
Quick patch release to fix an issue with API Gateway Authorizer in Event Source Data Classes not supporting resource names using _. It's an old bug in
Quick patch release to fix an issue with API Gateway Authorizer in Event Source Data Classes not supporting resource names using _. It's an old bug in the original blueprint this code was based on. However, it prevents anyone from having a _ in the resource name get_something which is significant enough to warrant a patch release for that alone.
Thanks to Chris Elkin for raising and @michaelbrewer for the quick fix (as always).
@heitorlessa, @mergify[bot] and @michaelbrewer
This is an emergency release to fix a critical Batch bug spotted by @kimberlyamandalu, where multiple failed records weren't reported due to dictionar
This is an emergency release to fix a critical Batch bug spotted by @kimberlyamandalu, where multiple failed records weren't reported due to dictionary key uniqueness - If you are using BatchProcessor, please update to 1.24.1 as soon as possible.
Thanks to the gigantic effort by @mploski and @am29d on reviewing it, we now finally have an official tutorial covering core Powertools features. The tutorial demonstrates how new customers can add one feature at a time. It also opens the door to create other tutorials covering other complex features in more depth.
Please do let us know your thoughts and what other tutorials we should focus on next ;-)
A common question we receive is: How can I enable powertools logging for imported libraries?
Thanks to @mploski, we now have a tiny standalone function you can use to copy your current Logger config to any or a select list of loggers you want.
Why would people want that? It's a fair question. Reason is sometimes you want external libraries to have the exact same structured logging that your application has. This function allows you to specify which explicit loggers you wanna copy config to (or all), and whether you want to set a different log level.
import logging
from aws_lambda_powertools import Logger
from aws_lambda_powertools.logging import utils
logger = Logger()
external_logger = logging.logger()
utils.copy_config_to_registered_loggers(source_logger=logger)
external_logger.info("test message")
Big thanks to new contributors @thehananbhat @j2clerck on helping us make documentation better (critical to us!).
@dependabot, @dependabot[bot], @heitorlessa, @j2clerck, @michaelbrewer, @mozz100, @mploski, @ran-isenberg, @thehananbhat and Michal Ploski
@nayaverdier for a future proof change on how we internally convert string to bool (distutils being deprecated in Python 3.12)
For the last release of the year (happy 2022!), we bring a major enhancements to Idempotency and Feature Flags.
We also addresses important papercuts like caching parsed JSON data in Event Sources, support for datetime format codes in Logger and the ability to ignore certain endpoints from being traced.
Did I say that 90% of this release was contributed by the community? thank you everyone!!!
HUGE shoutout to @DanyC97 on helping us make all of our documentation banners (warning, tip) consistent.
Big thanks to new contributors too (you rock!)
When using idempotent_function to make any Python synchronous function idempotent, you might have data available as Dataclasses or Pydantic models, not just dictionaries.
This release gives you more flexibility on what data can be used as your idempotency token - it could be an entire Dataclass, Pydantic model, or fields within these models.
You can now use the new boolean_feature: false parameter in your schema to signal Feature Flags that you will return any JSON valid value.
Example scenario: you might have a list of features to unlock for premium customers, or a set of beta features for select customers
{
"premium_features": {
"boolean_type": false,
"default": [],
"rules": {
"customer tier equals premium": {
"when_match": ["no_ads", "no_limits", "chat"],
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
}
}
Translating to the following API:
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
def lambda_handler(event, context):
# Get customer's tier from incoming request
ctx = { "tier": event.get("tier", "standard") }
# Evaluate `has_premium_features` base don customer's tier
premium_features: list[str] = feature_flags.evaluate(name="premium_features",
context=ctx, default=False)
for feature in premium_features:
# enable premium features
...
AWS X-Ray has a limit of 64K tracing data. This could be a problem if you're making hundreds of HTTP requests to the same endpoint.
Alternatively, there are sensitive endpoints you might want them to not be included in your tracing data.
You can now use ignore_endpoint for this purpose - globs (*) are allowed!
from aws_lambda_powertools import Tracer
tracer = Tracer()
# ignore all calls to `ec2.amazon.com`
tracer.ignore_endpoint(hostname="ec2.amazon.com")
# ignore calls to `*.sensitive.com/password` and `*.sensitive.com/credit-card`
tracer.ignore_endpoint(hostname="*.sensitive.com", urls=["/password", "/credit-card"])
def ec2_api_calls():
return "suppress_api_responses"
@tracer.capture_lambda_handler
def handler(event, context):
for x in long_list:
ec2_api_calls()
@DanyC97, @dependabot, @dependabot[bot], @heitorlessa, @huonw, @michaelbrewer, @nayaverdier, @ran-isenberg and @trey-rosius
Full Changelog: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.23.0...v1.23.1
This Christmas release was heavily focused on papercuts and two must needed improvements - Batch now supports SQS, DynamoDB, and Kinesis using the new
This Christmas release was heavily focused on papercuts and two must needed improvements - Batch now supports SQS, DynamoDB, and Kinesis using the new native partial response (decreased cost), and Event Handler API Gateway now supports exception handling and overriding HTTP 404 errors.
For the next release, we're going to further invest in Mypy compliance, support native serialization of Python Dataclasses and Pydantic Models in Idempotency utility, and more!
Join us on Slack #lambda-powertools to help us make the next release even better!
Docs: Infra required, SQS, Kinesis, and DynamoDB
A few weeks ago Lambda added native support for partial response. Since GA, Batch utility provided that capability to SQS by handling partial failures and deleting successful messages on your behalf.
With the new capability, we've added a new BatchProcessor that can process both SQS, Kinesis Data Streams, and DynamoDB Streams.
The new capability requires an infrastructure change in the Lambda Event Source Mapping hence why a new Batch Processor - We've made sure to add complete SAM examples for SQS, Kinesis, and DynamoDB Streams on everything you need to get going including minimal IAM permissions.
We also took this opportunity to a) provide tight integration with Event Source Data Classes for self-documented batch records, b) Parser (Pydantic) integration so you can bring your own models for data schema and validation, and c) mypy is fully compliant regardles of the event type or Pydantic model you bring.
Did I also say we refreshed the entire documentation with sample events, responses, how to unit test, and a Migration Guide?
If you were using SQSBatchProcessor before as a decorator or as a context manager, we've written a guide to help you transition to the new more performant and cost efficient way: https://awslabs.github.io/aws-lambda-powertools-python/develop/utilities/batch/#migration-guide
A warm start with tracing, logging, data transformation, and exception handling can run as fast as 8ms end-to-end now.
You can now override how we handle unmatched routes (404) by using a decorator not_found. The experience is similar to how you'd define routes, except your function will receive an exception - NotFoundError in this case.
You can customize the response by simply returning Response like you would in a more advanced route -- This should give you more flexibility to generate metrics, log errors, or anything you might want for unmatched routes.
Besides 404 errors, you might want to handle custom validation errors, or any exception you might raise as part of your route handling. You can now use exception_handler decorator and pass any valid Python Exception.
Tracer now automatically adds a Service annotation if either service parameter or POWERTOOLS_SERVICE_NAME is set - This makes it easier to slice and dice all of your traces by the exact service you have deployed.
Moreover, we now include ColdStart=false for warm start invocations so you can more easily sample and analyze performance differences between cold and warm starts.
As our Java Powertools colleagues added a new utility for easily writing CloudFormation Custom Resources, we added a link in our docs pointing to the official Python library for crafting custom resources.... just in case it's not widely known: https://github.com/aws-cloudformation/custom-resource-helper
@cakepietoast, @dependabot, @dependabot[bot], @heitorlessa, @michaelbrewer, @ran-isenberg and @whardier
Full Changelog: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.22.0...v1.22.1
This release adds two major changes: 1/ New Router feature in Event Handler utility including GraphQL Resolvers composition in AppSync, and 2/ Idiomat
This release adds two major changes: 1/ New Router feature in Event Handler utility including GraphQL Resolvers composition in AppSync, and 2/ Idiomatic tenet has been updated to Progressive.
Additionally, we now support ActiveMQ and RabbitMQ in the Event Source Data Classes, and primary composite key for Idempotency when using DynamoDB Storage. There's been lots of improvements to documentation around Lambda Layers install, and a bug fix for Parser (Pydantic) to address API Gateway v1/v2 supporting a null body.
This release note will primarily cover the new Router feature in Event Handler given how significant this is. Also, we created a new section named Considerations in the docs to share an opinionated set of trade-offs when going with a monolithic vs micro function approach, when using API Gateway, ALB, or AppSync.
You can now use separate files to compose routes and GraphQL resolvers. Before this feature, you'd need all your routes or GraphQL resolvers in the same file where your Lambda handler is.
This is how it would look like before this feature in either API Gateway, ALB, and AppSync:
app.py
import itertools
from typing import Dict
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
logger = Logger(child=True)
app = ApiGatewayResolver()
USERS = {"user1": "details_here", "user2": "details_here", "user3": "details_here"}
@app.get("/users")
def get_users() -> Dict:
# /users?limit=1
pagination_limit = app.current_event.get_query_string_value(name="limit", default_value=10)
logger.info(f"Fetching the first {pagination_limit} users...")
ret = dict(itertools.islice(USERS.items(), int(pagination_limit)))
return {"items": [ret]}
@app.get("/users/<username>")
def get_user(username: str) -> Dict:
logger.info(f"Fetching username {username}")
return {"details": USERS.get(username, {})}
With Router, you can now split the /users routes in a separate file and change ApiGatewayResolver with Router, for example:
users.py
import itertools
from typing import Dict
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.api_gateway import Router
logger = Logger(child=True)
router = Router()
USERS = {"user1": "details_here", "user2": "details_here", "user3": "details_here"}
@router.get("/users")
def get_users() -> Dict:
# /users?limit=1
pagination_limit = router.current_event.get_query_string_value(name="limit", default_value=10)
logger.info(f"Fetching the first {pagination_limit} users...")
ret = dict(itertools.islice(USERS.items(), int(pagination_limit)))
return {"items": [ret]}
@router.get("/users/<username>")
def get_user(username: str) -> Dict:
logger.info(f"Fetching username {username}")
return {"details": USERS.get(username, {})}
Note that the user experience is exactly the same on accessing request details and defining routes, except we use Router instead of ApiGatewayResolver.
Next, within your Lambda entry point, you have to use the new include_router method to inject routes from /users at runtime:
app.py
from typing import Dict
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import ApiGatewayResolver
from aws_lambda_powertools.utilities.typing import LambdaContext
import users
logger = Logger()
app = ApiGatewayResolver()
app.include_router(users.router)
@logger.inject_lambda_context
def lambda_handler(event: Dict, context: LambdaContext):
return app.resolve(event, context)
Similarly to API Gateway and ALB, you can now use Router to split GraphQL resolvers allowing for further composition:
resolvers/location.py
from typing import Any, Dict, List
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.appsync import Router
logger = Logger(child=True)
router = Router()
@router.resolver(type_name="Query", field_name="listLocations")
def list_locations(merchant_id: str) -> List[Dict[str, Any]]:
return [{"name": "Location name", "merchant_id": merchant_id}]
@router.resolver(type_name="Location", field_name="status")
def resolve_status(merchant_id: str) -> str:
logger.debug(f"Resolve status for merchant_id: {merchant_id}")
return "FOO"
app.py
from typing import Dict
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import AppSyncResolver
from aws_lambda_powertools.logging.correlation_paths import APPSYNC_RESOLVER
from aws_lambda_powertools.utilities.typing import LambdaContext
from resolvers import location
tracer = Tracer()
logger = Logger()
app = AppSyncResolver()
app.include_router(location.router)
@tracer.capture_lambda_handler
@logger.inject_lambda_context(correlation_id_path=APPSYNC_RESOLVER)
def lambda_handler(event: Dict, context: LambdaContext):
app.resolve(event, context)
We've updated Idiomatic tenet to Progressive to reflect the new Router feature in Event Handler, and more importantly the new wave of customers coming from SRE, Data Analysis, and Data Science background.
@AlessandroVol23, @BVMiko, @Tankanow, @arthurf1969, @cakepietoast, @dependabot, @dependabot[bot], @eldritchideen, @heitorlessa, @jonemo and @michaelbrewer
Patch release to address regression in Metrics with mypy not recognizing a Callable when using log_metrics().
Patch release to address regression in Metrics with mypy not recognizing a Callable when using log_metrics().
Oh! It's finally here!!!
This release adds our first batch of public Lambda Layers for every AWS region supported by AWS Lambda - huge thanks to @am29d.
This means you no longer need to deploy a SAR App in order to use Lambda Powertools as a Lambda Layer.
That being said, we will keep SAR App in order to give you the flexibility to choose which semantic version you want to use as a Lambda Layer, until it is officially supported by Lambda Layers.
@am29d, @dependabot, @dependabot[bot] and @heitorlessa
After some vacation period, we're back with a new minor release with major features:
After some vacation period, we're back with a new minor release with major features:
I'd like to personally thank our new contributors to the project :)
You can now pass in your own boto3 session when using Parameters, Batch, and Idempotency.
This is helpful in two typical scenarios: 1/ You want to run an operation in another account like fetching secrets/parameters somewhere else, 2/ Use snapshot testing tools like Placebo that will replay session data that happened earlier when doing unit testing.
from aws_lambda_powertools.utilities import parameters
import boto3
boto3_session = boto3.session.Session()
ssm_provider = parameters.SSMProvider(boto3_session=boto3_session)
def handler(event, context):
# Retrieve a single parameter
value = ssm_provider.get("/my/parameter")
...
There's been three main improvements in Feature flags utility as part of this release: New rule conditions, Bring your own Logger for debugging, and Getting a copy of fetched configuration from the store
You can now use the following new rule conditions to evaluate your feature flags for inequality, comparison, and more explicit contains logic, where a is the key and b is the value passed as a context input for evaluation:
| Action | Equivalent expression |
|---|---|
| KEY_GREATER_THAN_VALUE | lambda a, b: a > b |
| KEY_GREATER_THAN_OR_EQUAL_VALUE | lambda a, b: a >= b |
| KEY_LESS_THAN_VALUE | lambda a, b: a < b |
| KEY_LESS_THAN_OR_EQUAL_VALUE | lambda a, b: a <= b |
| KEY_IN_VALUE | lambda a, b: a in b |
| KEY_NOT_IN_VALUE | lambda a, b: a not in b |
| VALUE_IN_KEY | lambda a, b: b in a |
| VALUE_NOT_IN_KEY | lambda a, b: b not in a |
Example
Feature flag schema
{
"premium_features": {
"default": false,
"rules": {
"customer tier equals premium": {
"when_match": true,
"conditions": [
{
"action": "VALUE_IN_KEY",
"key": "groups",
"value": "PAID_PREMIUM",
}
]
}
}
},
"ten_percent_off_campaign": {
"default": false
}
}
App
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
def lambda_handler(event, context):
# groups: ["FREE_TIER", "PAID_BASIC", "PAID_PREMIUM"]
ctx={"tenant_id": "6", "username": "a", "groups": event.get("groups", [])}
# Evaluate whether customer's tier has access to premium features
# based on `has_premium_features` rules
has_premium_features: bool = feature_flags.evaluate(name="premium_features",
context=ctx, default=False)
if has_premium_features:
# enable premium features
...
Previously, if you were using a single application configuration and a feature schema in a single AppConfig key, we would only use the feature flags schema and discard the rest.
You can now access the raw configuration with a new property get_raw_configuration within AppConfig Store:
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="configuration",
envelope = "feature_flags"
)
feature_flags = FeatureFlags(store=app_config)
config = app_config.get_raw_configuration
We have improved how you can unit test your code when using @idempotent and @idempotent_function decorators.
You can now disable all interactions with the idempotence store using POWERTOOLS_IDEMPOTENCY_DISABLED environment variable, and monkeypatch the DynamoDB resource client Idempotency utility uses if you wish to either use DynamoDB Local or mock all I/O operations.
import boto3
import app
def test_idempotent_lambda():
# Create our own Table resource using the endpoint for our DynamoDB Local instance
resource = boto3.resource("dynamodb", endpoint_url='http://localhost:8000')
table = resource.Table(app.persistence_layer.table_name)
app.persistence_layer.table = table
result = app.handler({'testkey': 'testvalue'}, {})
assert result['payment_id'] == 12345
When validating input/output with the Validator, you can now access new properties in SchemaValidationError to more easily construct your custom errors based on what went wrong.
| Property | Type | Description |
|---|---|---|
message |
str | Powertools formatted error message |
validation_message |
str, optional | Containing human-readable information what is wrong, e.g. data.property[index] must be smaller than or equal to 42 |
name |
str, optional | name of a path in the data structure, e.g. data.property[index] |
path |
List, optional | path as an array in the data structure, e.g. ['data', 'property', 'index'] |
value |
Any, optional | The invalid value, e.g. {"message": "hello world"} |
definition |
Any, optional | JSON Schema definition |
rule |
str, optional | rule which the data is breaking (e.g. maximum, required) |
rule_definition |
Any, optional | The specific rule definition (e.g. 42, ['message', 'username']) |
Sample
from aws_lambda_powertools.utilities.validation import validate
from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError
import schemas
def handler(event, context):
try:
validate(event=event, schema=schemas.INPUT)
except SchemaValidationError as e:
message = "data must contain ['message', 'username'] properties"
assert str(e.value) == e.value.message
assert e.value.validation_message == message
assert e.value.name == "data"
assert e.value.path is not None
assert e.value.value == data
assert e.value.definition == schema
assert e.value.rule == "required"
assert e.value.rule_definition == schema.get("required")
raise
return event
Last but not least, as part of a documentation revamp in Idempotency by @walmsles, we're now exposing an internal feature used by many Lambda Powertools utilities which is the ability to extract and decode JSON objects.
You can now use JMESPath (JSON Query language) Lambda Powertools functions to easily decode and deserialize JSON often found as compressed (Kinesis, CloudWatch Logs, etc), as strings (SNS, SQS, EventBridge, API Gateway, etc), or as base64 (Kinesis).
We're exposing three custom JMESPath functions you can use such as powertools_json, powertools_base64, powertools_base64_gzip, and a new standalone function that will use JMESPath to search and extract the data you want called extract_data_from_envelope.
Sample
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):
payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)")
customer = payload.get("customerId") # now deserialized
...
SNS sample using built-in envelope
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope, envelopes
from aws_lambda_powertools.utilities.typing import LambdaContext
def handler(event: dict, context: LambdaContext):
payload = extract_data_from_envelope(data=event, envelope=envelopes.SNS)
customer = payload.get("customerId") # now deserialized
...
Sample event
{
"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"
}
]
}
@DanyC97, @Tankanow, @cakepietoast, @dependabot, @dependabot[bot], @gwlester, @heitorlessa, @michaelbrewer, @risenberg-cyberark and @walmsles
Full Changelog: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.20.2...v1.21.0
Bugfix release to address a bug with event handler which caused issues for api gateway events when using strip_prefix together with a root level handl
Bugfix release to address a bug with event handler which caused issues for api gateway events when using strip_prefix together with a root level handler. Thanks @BVMiko for the fix!
@BVMiko, @BastianZim, @cakepietoast
Hotfix release to address a bug in idempotency hashing logic to ensure data is sorted as part of the process - Huge thanks to @walmsles!
Hotfix release to address a bug in idempotency hashing logic to ensure data is sorted as part of the process - Huge thanks to @walmsles!
We also now support CodeSpaces to ease contribution, also thanks to @michaelbrewer
@BastianZim, @heitorlessa and @michaelbrewer
This release highlights 1/ support for Python 3.9, 2/ support for [API Gateway][apigateway-http-authorizer] and [AppSync Lambda Authorizers][appsync-a
This release highlights 1/ support for Python 3.9, 2/ support for API Gateway and AppSync Lambda Authorizers, 3/ support for API Gateway Custom Domain Mappings, 4/ support to make any Python synchronous function idempotent, and a number of documentation improvements & bugfixes.
This release adds Data Class support for AppSyncAuthorizerEvent, AppSyncAuthorizerResponse, and correlation ID in Logger.
You can use AppSyncAuthorizerEvent to easily access all self-documented properties, and AppSyncAuthorizerResponse to serialize the response in the expected format.
You can read more in the announcement blog post for more details
from typing import Dict
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.logging.logger import Logger
from aws_lambda_powertools.utilities.data_classes.appsync_authorizer_event import (
AppSyncAuthorizerEvent,
AppSyncAuthorizerResponse,
)
from aws_lambda_powertools.utilities.data_classes.event_source import event_source
logger = Logger()
def get_user_by_token(token: str):
"""Look a user by token"""
@logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_AUTHORIZER)
@event_source(data_class=AppSyncAuthorizerEvent)
def lambda_handler(event: AppSyncAuthorizerEvent, context) -> Dict:
user = get_user_by_token(event.authorization_token)
if not user:
# No user found, return not authorized
return AppSyncAuthorizerResponse().to_dict()
return AppSyncAuthorizerResponse(
authorize=True,
resolver_context={"id": user.id},
# Only allow admins to delete events
deny_fields=None if user.is_admin else ["Mutation.deleteEvent"],
).asdict()
This release adds support for both Lambda Authorizer for payload v1 - APIGatewayAuthorizerRequestEvent, APIGatewayAuthorizerResponse - and v2 formats APIGatewayAuthorizerEventV2, APIGatewayAuthorizerResponseV2.
Similar to AppSync, you can use APIGatewayAuthorizerRequestEvent and APIGatewayAuthorizerEventV2 to easily access all self-documented properties available, and its corresponding APIGatewayAuthorizerResponse and APIGatewayAuthorizerResponseV2 to serialize the response in the expected format.
You can read more in the announcement blog post for more details
from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
APIGatewayAuthorizerEventV2,
APIGatewayAuthorizerResponseV2,
)
from secrets import compare_digest
def get_user_by_token(token):
if compare_digest(token, "Foo"):
return {"name": "Foo"}
return None
@event_source(data_class=APIGatewayAuthorizerEventV2)
def handler(event: APIGatewayAuthorizerEventV2, context):
user = get_user_by_token(event.get_header_value("x-token"))
if user is None:
# No user was found, so we return not authorized
return APIGatewayAuthorizerResponseV2().asdict()
# Found the user and setting the details in the context
return APIGatewayAuthorizerResponseV2(authorize=True, context=user).asdict()
from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
APIGatewayAuthorizerRequestEvent,
APIGatewayAuthorizerResponse,
HttpVerb,
)
from secrets import compare_digest
def get_user_by_token(token):
if compare_digest(token, "admin-foo"):
return {"isAdmin": True, "name": "Admin"}
elif compare_digest(token, "regular-foo"):
return {"name": "Joe"}
else:
return None
@event_source(data_class=APIGatewayAuthorizerRequestEvent)
def handler(event: APIGatewayAuthorizerRequestEvent, context):
user = get_user_by_token(event.get_header_value("Authorization"))
# parse the `methodArn` as an `APIGatewayRouteArn`
arn = event.parsed_arn
# Create the response builder from parts of the `methodArn`
policy = APIGatewayAuthorizerResponse(
principal_id="user",
region=arn.region,
aws_account_id=arn.aws_account_id,
api_id=arn.api_id,
stage=arn.stage
)
if user is None:
# No user was found, so we return not authorized
policy.deny_all_routes()
return policy.asdict()
# Found the user and setting the details in the context
policy.context = user
# Conditional IAM Policy
if user.get("isAdmin", False):
policy.allow_all_routes()
else:
policy.allow_route(HttpVerb.GET, "/user-profile")
return policy.asdict()
When using Custom Domain API Mappings feature, you must use the new strip_prefixes param in the ApiGatewayResolver constructor.
Scenario: You have a custom domain api.mydomain.dev and set an API Mapping payment to forward requests to your Payments API, the path argument will be /payment/<your_actual_path>.
This will lead to a HTTP 404 despite having your Lambda configured correctly. See the example below on how to account for this change.
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
tracer = Tracer()
logger = Logger()
app = ApiGatewayResolver(strip_prefixes=["/payment"])
@app.get("/subscriptions/<subscription>")
@tracer.capture_method
def get_subscription(subscription):
return {"subscription_id": subscription}
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
return app.resolve(event, context)
Previously, you could only make the entire Lambda function handler idempotent. You can now make any Python function idempotent with the new idempotent_function.
This also enables easy integration with any other utility in Powertools. Take example the Batch utility, where you wouldn't want to make the entire Lambda handler idempotent as the batch will vary, instead you'd want to make sure you can process a given message only once.
As a trade-off to allow any Python function with an arbitrary number of parameters, you must call your function with a keyword argument, and you tell us upfront which one that might be using data_keyword_argument, so we can apply all operations like hashing the idempotency token, payload extraction, parameter validation, etc.
import uuid
from aws_lambda_powertools.utilities.batch import sqs_batch_processor
from aws_lambda_powertools.utilities.idempotency import idempotent_function, DynamoDBPersistenceLayer, IdempotencyConfig
dynamodb = DynamoDBPersistenceLayer(table_name="idem")
config = IdempotencyConfig(
event_key_jmespath="messageId", # see "Choosing a payload subset for idempotency" docs section
use_local_cache=True,
)
@idempotent_function(data_keyword_argument="data", config=config, persistence_store=dynamodb)
def dummy(arg_one, arg_two, data: dict, **kwargs):
return {"data": data}
@idempotent_function(data_keyword_argument="record", config=config, persistence_store=dynamodb)
def record_handler(record):
return {"message": record["body"]}
@sqs_batch_processor(record_handler=record_handler)
def lambda_handler(event, context):
# `data` parameter must be called as a keyword argument to work
dummy("hello", "universe", data="test")
return {"statusCode": 200}
@dependabot, @dependabot[bot], @heitorlessa, @hjurong and @michaelbrewer
This release highlights 1/ a brand new Feature Flags utility, 2/ auto-disable Tracer in non-Lambda environments to ease unit testing, 3/ API Gateway e
This release highlights 1/ a brand new Feature Flags utility, 2/ auto-disable Tracer in non-Lambda environments to ease unit testing, 3/ API Gateway event handler now supports a custom JSON serializer, and a number of documentation improvements & bugfixes.
We hope you enjoy this new utility as much as we did working on it!!
Special thanks to: @risenberg-cyberark, @michaelbrewer, @pcolazurdo and @am29d
You might have heard of feature flags when:
This new utility makes this entire process so much easier by fetching feature flags configuration from AWS AppConfig, and evaluating contextual values against rules you defined to decide whether a feature should be enabled.
Let's dive into the code to better understand what this all means.
Evaluating whether a customer should have access to premium features
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
def lambda_handler(event, context):
# Get customer's tier from incoming request
ctx = { "tier": event.get("tier", "standard") }
has_premium_features: bool = feature_flags.evaluate(name="premium_features",
context=ctx, default=False)
if has_premium_features:
# enable premium features
...
Sample feature flag configuration in AWS AppConfig
{
"premium_features": {
"default": false,
"rules": {
"customer tier equals premium": {
"when_match": true,
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
},
"ten_percent_off_campaign": {
"default": false
}
}
Notice we have premium_features flag that will conditionally be available for premium customers, and a static feature flag named ten_percent_off_campaign that is disabled by default.
Sample invocation event for this function
{
"username": "lessa",
"tier": "premium",
"basked_id": "random_id"
}
There's a LOT going on here. Allow me to break it down:
AppConfigStore using AWS AppConfig values created via Infrastructure as code (available on docs)FeatureFlags and use our previously instantiated AppConfigStoreevaluate method and pass the name of the premium feature, along with our contextual information our rules should run against, and a sentinel value to be used in case service errors happenpremium_features and evaluate whether tier key has the value premiumTrue which is then stored as has_premium_features variableBut what if you have multiple feature flags and only want all enabled features?
We've got you covered! You can use get_enabled_features to make a single call to AWS AppConfig and return a list of all enabled features at once.
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app = ApiGatewayResolver()
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
@app.get("/products")
def list_products():
ctx = {
**app.current_event.headers,
**app.current_event.json_body
}
# all_features is evaluated to ["geo_customer_campaign", "ten_percent_off_campaign"]
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)
if "geo_customer_campaign" in all_features:
# apply discounts based on geo
...
if "ten_percent_off_campaign" in all_features:
# apply additional 10% for all customers
...
def lambda_handler(event, context):
return app.resolve(event, context)
But hang on, why Beta?
We want to hear from you on the UX and evaluate how we can make it easier for you to bring your own feature flag store such as Redis, HashiCorp Consul, etc.
Environment variables. For when you need simple configuration that will rarely if ever change, because changing it requires a Lambda function deployment.
Parameters utility. For when you need access to secrets, or fetch parameters in different formats from AWS System Manager Parameter Store or Amazon DynamoDB.
Feature flags utility. For when you need static or feature flags that will be enable conditionally based on the input and on a set of rules per feature whether that applies for all customers or on a per customer basis.
In both Parameters and Feature Flags utility you can change their config without having to change your application code.
@am29d, @dependabot, @dependabot[bot], @dreamorosi, @heitorlessa, @michaelbrewer, @risenberg-cyberark and @pcolazurdo
Fix a pesky regression introduced when fixing API Gateway/ALB Event Handler routing regex in 1.18.0. This bug made numeric and safe URI chars to retur
Fix a pesky regression introduced when fixing API Gateway/ALB Event Handler routing regex in 1.18.0. This bug made numeric and safe URI chars to return 404 -- Big thanks to @moretension for raising it
@heitorlessa
This release mainly focused on bug fixes and a few minor features, so we can spend time documenting a new utility (Feature Toggles) that will be fully
This release mainly focused on bug fixes and a few minor features, so we can spend time documenting a new utility (Feature Toggles) that will be fully available in 1.19.0.
Bug fixes were largely on MyPy errors (~600 to 38) across the entire code base. We also fixed a a) faulty greedy regex in the API Gateway event handler when dealing with long and dynamic routes (more in Changes section), b) Parser authorization and version fields for API Gateway that should've been optional, and c) Event Source Data Classes to conform with AppSync Scalar by including milliseconds in the time resolution.
We also had two new first-time contributors: :clap: @walmsles and @whardier, we appreciate your help with this release!
You can now retrieve the latest correlation ID previously set in the Logger instance at any part of the code base. This is useful when propagating correlation ID for external calls whether these are AWS service integrations or 3rd party endpoints.
from aws_lambda_powertools import Logger
logger = Logger(service="payment")
@logger.inject_lambda_context(correlation_id_path="headers.my_request_id_header")
def handler(event, context):
logger.debug(f"NEW Correlation ID => {logger.get_correlation_id()}")
You can now easily enable debug mode for API Gateway or ALB event handlers. Additionally, we've made it easier to raise quick HTTP service errors in response to malformed requests, resources not found, etc.
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError,
InternalServerError,
NotFoundError,
ServiceError,
UnauthorizedError,
)
app = ApiGatewayResolver(debug=True)
@app.get(rule="/bad-request-error")
def bad_request_error():
# HTTP 400
raise BadRequestError("Missing required parameter")
@app.get(rule="/unauthorized-error")
def unauthorized_error():
# HTTP 401
raise UnauthorizedError("Unauthorized")
@app.get(rule="/not-found-error")
def not_found_error():
# HTTP 404
raise NotFoundError
@app.get(rule="/internal-server-error")
def internal_server_error():
# HTTP 500
raise InternalServerError("Internal server error")
@app.get(rule="/service-error", cors=True)
def service_error():
raise ServiceError(502, "Something went wrong!")
# alternatively
# from http import HTTPStatus
# raise ServiceError(HTTPStatus.BAD_GATEWAY.value, "Something went wrong)
def handler(event, context):
return app.resolve(event, context)
When building data-driven APIs using GraphQL and AppSync, you might have a set of reusable methods you want closer to the data model. Event Handler for AppSync supports a new parameter data_model to facilitate that.
You can now subclass AppSyncResolverEvent from Event Source Data Classes while handling incoming requests with Event Handler for AppSync.
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.event_handler import AppSyncResolver
tracer = Tracer(service="sample_resolver")
logger = Logger(service="sample_resolver")
app = AppSyncResolver()
class MyCustomModel(AppSyncResolverEvent):
@property
def country_viewer(self) -> str:
return self.request_headers.get("cloudfront-viewer-country")
@app.resolver(field_name="listLocations")
@app.resolver(field_name="locations")
def get_locations(name: str, description: str = ""):
if app.current_event.country_viewer == "US":
...
return name + description
@logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
return app.resolve(event, context, data_model=MyCustomModel)
This fixes a regex bug that used a greedy pattern ending with incorrect path resolution, as any path after a pattern would be included in the argument.
Excerpt:
@app.get("/accounts/<account_id>")
def get_account(account_id: str):
print(f"Account ID ({account_id}) would be 123")
# Greedy regex would inject the incorrect function parameter
@app.get("/accounts/<account_id>/source_networks")
def get_account_networks(account_id: str):
print(f"Account ID ({account_id}) would be 123/source_networks")
In this example, say we have a GET request as /accounts/123 and another as /accounts/123/source_networks, we'd have the following effect prior to this fix:
| Function | Regex | Effective account_id value |
|---|---|---|
| get_account | r'^/accounts/(?P<account_id>.+)$' |
123 |
| get_account_networks | r'^/accounts/(?P<account_id>.+)/source_networks$' |
123/source_networks |
With this fix, account_id parameter would be 123 in both occasions due to word boundary not being non-greedy. This also allows an arbitrary number of dynamic route paths and static route paths.
| Function | Regex | Effective account_id value |
|---|---|---|
| get_account | r'^/accounts/(?P<account_id>\\w+\\b)$' |
123 |
| get_account_networks | r'^/accounts/(?P<account_id>\\w+\\b)/source_networks$' |
123 |
@dependabot, @dependabot[bot], @heitorlessa, @michaelbrewer, @risenberg-cyberark, @walmsles and @whardier
This patch release fixes JSON Schema Validation utility when a built-in format date-time is used but previously unrecognized.
This patch release fixes JSON Schema Validation utility when a built-in format date-time is used but previously unrecognized.
Additionally, this includes Dark Mode (🕶️) to the Documentation, Serverless Framework and CDK examples for installing Lambda Layers from SAR, and clarifications on logger and auto-capture tracer's feature.
@dependabot, @dependabot[bot], @heitorlessa and @michaelbrewer
This release brings a 1/ new handy decorator event_source for data classes, 2/ API Gateway HTTP API support for Parser, 3/ a new parameter to reset lo
This release brings a 1/ new handy decorator event_source for data classes, 2/ API Gateway HTTP API support for Parser, 3/ a new parameter to reset logging state upon every Lambda invocation in Logger, and 4/ high level methods to fetch attribute values and their types when using DynamoDBStreamEvent data class.
Similarly to Parser's parse decorator, you can now use event_source decorator to instantiate a data class as part of the invocation more easily.
from typing import Dict, Any
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEventV2, event_source
from aws_lambda_powertools.utilities.typing import LambdaContext
@event_source(data_class=APIGatewayProxyEventV2)
def lambda_handler(event: APIGatewayProxyEventV2, context: LambdaContext) -> Dict[str, Any]:
assert event.get_header_value("x-foo") == "Foo"
This also gives you the ability to combine other utilities like Idempotency which expects a dictionary to serialize/deserialize without losing the benefits of self-documented schema and high level data methods.
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEventV2, event_source
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer, idempotent
)
persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
@event_source(data_class=APIGatewayProxyEventV2)
@idempotent(persistence_store=persistence_layer)
def lambda_handler(event: APIGatewayProxyEventV2, context):
assert isinstance(event, APIGatewayProxyEventV2)
Parser now supports API Gateway HTTP API as a native model in addition to REST API.
from typing import Dict, Any
from aws_lambda_powertools.utilities.parser import envelopes, event_parser
from aws_lambda_powertools.utilities.parser.models import APIGatewayProxyEventV2Model
@event_parser(model=APIGatewayProxyEventV2Model)
def lambda_handler(event: APIGatewayProxyEventV2Model, context: LambdaContext) -> Dict[str, Any]:
...
Like REST API, Parser also supports an envelope for HTTP API to help you extract your model from the payload, providing deep data validation, type enforcement at runtime, and more.
from typing import Dict, Any
from aws_lambda_powertools.utilities.parser import envelopes, event_parser
from aws_lambda_powertools.utilities.parser.models import APIGatewayProxyEventV2Model
from aws_lambda_powertools.utilities.parser.pydantic import EmailStr, UUID4, Json
class UserModel(BaseModel):
id: UUID4
username: str
preferences: Json
email: EmailStr
@event_parser(model=APIGatewayProxyEventV2Model, envelope=envelopes.ApiGatewayV2Envelope)
def lambda_handler(event: UserModel, context: LambdaContext) -> Dict[str, Any]:
assert event.username == "ran_isenberg"
As Logger is initialized in the global scope, logging state can persist across invocations. It is a good practice to always append new keys and use default values (None if unavailable), so Logger can update and remove keys on a per invocation basis.
However, there are times where you might add keys conditionally depending on the incoming event. For these type of use cases, you can now use clear_state parameter when using inject_lambda_context decorator.
from aws_lambda_powertools import Logger
logger = Logger(service="payment")
@logger.inject_lambda_context(clear_state=True)
def handler(event, context):
if event.get("special_key"):
# Should only be available in the first request log
# as the second request doesn't contain `special_key`
logger.append_keys(debugging_key="value")
logger.info("Collecting payment")
You can now introspect what DynamoDB Record type and value is more easily with AttributeValueType and AttributeValue. This is useful when you might want to deserialize or deal with certain records and their attributes differently based on their type, e.g. a map vs string.
Each DynamoDB key available in the stream record now has high level methods such as get_value and get_type.
from aws_lambda_powertools.utilities.data_classes import event_source, DynamoDBStreamEvent
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import AttributeValueType, AttributeValue
from aws_lambda_powertools.utilities.typing import LambdaContext
@event_source(data_class=DynamoDBStreamEvent)
def lambda_handler(event: DynamoDBStreamEvent, context: LambdaContext):
for record in event.records:
key: AttributeValue = record.dynamodb.keys["id"]
if key == AttributeValueType.Number:
# {"N": "123.45"} => "123.45"
assert key.get_value == key.n_value
print(key.get_value)
elif key == AttributeValueType.Map:
assert key.get_value == key.map_value
print(key.get_value)
You can find their respective implementations and all contributors below.
@dependabot, @dependabot[bot], @heitorlessa, @michaelbrewer and @risenberg-cyberark
Emergency release to patch Pydantic against CVE-2021-29510used by Parser utility - This causes resource starvation when parsing infinity/-inf values.
Emergency release to patch Pydantic against CVE-2021-29510used by Parser utility - This causes resource starvation when parsing infinity/-inf values.
Thanks to @risenberg-cyberark for the quick turnaround.
@risenberg-cyberark
This release adds support for CodePipeline Job event, and a new property to save you some keystrokes when decoding base64 encoded data in API Gateway
This release adds support for CodePipeline Job event, and a new property to save you some keystrokes when decoding base64 encoded data in API Gateway and ALB when using Data Classes utility.
If you're new to the CodePipeline Job event, customers typically use it for out of band integrations, such as orchestrate heavy deployments with a State Machine or validate an external system/KPI before continuing.
This release adds support for CodePipeline Job including methods to easily retrieve user parameters, download artifacts from S3 using temporary credentials sent as part of event, etc.
Based on this thread on Twitter with Luc from SentiaCloud, decoding base64 data in the first try without looking at a snippet is not for everyone :D
When using Data classes for API Gateway or ALB, you can now easily decode data with the new decoded_body property:
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent
def lambda_handler(event: dict, context):
event = APIGatewayProxyEvent(event)
# Dynamically base64 decode body as a str
body: str = event.decoded_body
return body
@heitorlessa and @michaelbrewer
Quick release to fix a regression in Logger for the %s operator.
Quick release to fix a regression in Logger for the %s operator.
@heitorlessa, @huonw and @michaelbrewer
Your coding agent can read these notes before it upgrades. Set up the MCP server →