v1.18.0
Summary
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: 👏 @walmsles and @whardier, we appreciate your help with this release!
Changes
New get_correlation_id method in Logger
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()}")
Debug mode and HTTP service errors in Event Handlers
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)
Data model sub-classing in AppSync event handler
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)
Bugfix API Gateway routing params
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 |
🌟New features and non-breaking changes
- feat(appsync): Support AppSyncResolverEvent subclassing (#526) by @whardier
- feat(logger): add get_correlation_id method (#516) by @michaelbrewer
- feat(feat-toggle): new simple feature toggles rule engine (WIP) (#494) by @risenberg-cyberark
- feat(api-gateway): add debug mode (#507) by @michaelbrewer
- feat(api-gateway): add common HTTP service errors (#506) by @michaelbrewer
📜 Documentation updates
- docs(api-gateway): new HTTP service error exceptions (#546) by @heitorlessa
- docs(logger): new get_correlation_id method (#545) by @heitorlessa
- feat(appsync): Support AppSyncResolverEvent subclassing (#526) by @whardier
🐛 Bug and hot fixes
- fix(api-gateway): non-greedy route pattern regex (#533) by @heitorlessa
- fix(tracer): mypy generic to preserve decorated method signature (#529) by @heitorlessa
- fix(data-classes): include milliseconds in scalar types (#504) by @michaelbrewer
- fix(parser): Make ApiGateway version, authorizer fields optional (#532) by @walmsles
- fix(mypy): addresses lack of optional types (#521) by @michaelbrewer
🔧 Maintenance
- chore: bump 1.18.0 (#547) by @heitorlessa
- chore(deps-dev): bump mkdocs-material from 7.1.10 to 7.1.11 (#542) by @dependabot
- chore(deps): bump codecov/codecov-action from 1 to 2.0.1 (#539) by @dependabot
- refactor(feature-toggles): code coverage and housekeeping (#530) by @michaelbrewer
- chore(deps): bump boto3 from 1.18.0 to 1.18.1 (#528) by @dependabot
- chore(deps): bump boto3 from 1.17.110 to 1.18.0 (#527) by @dependabot
- chore(deps-dev): bump mkdocs-material from 7.1.9 to 7.1.10 (#522) by @dependabot
- chore(deps): bump boto3 from 1.17.102 to 1.17.110 (#523) by @dependabot
- chore(deps-dev): bump isort from 5.9.1 to 5.9.2 (#514) by @dependabot
- chore(mypy): add mypy support to makefile (#508) by @michaelbrewer
This release was made possible by the following contributors:
@dependabot, @dependabot[bot], @heitorlessa, @michaelbrewer, @risenberg-cyberark, @walmsles and @whardier