github aws-powertools/powertools-lambda-python v1.18.0

latest releases: v3.0.0, v2.43.1, v2.43.0...
3 years ago

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

πŸ› Bug and hot fixes

πŸ”§ Maintenance

This release was made possible by the following contributors:

@dependabot, @dependabot[bot], @heitorlessa, @michaelbrewer, @risenberg-cyberark, @walmsles and @whardier

Don't miss a new powertools-lambda-python release

NewReleases is sending notifications on new releases.