Skip to content

v1.22.0

Compare
Choose a tag to compare
@release-drafter release-drafter released this 17 Nov 10:22
· 2659 commits to develop since this release
075ac41

Summary

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.

Router feature in Event Handler

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.

API Gateway and ALB

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)

GraphQL Resolvers

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)

Tenet update

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.

  • BEFORE: Idiomatic. Utilities follow programming language idioms and language-specific best practices.
  • AFTER: Progressive. Utilities are designed to be incrementally adoptable for customers at any stage of their Serverless journey. They follow language idioms and their community’s common practices.

Changes

🌟New features and non-breaking changes

  • feat(idempotency): support composite primary key in DynamoDBPersistenceLayer (#740) by @Tankanow
  • feat(data-classes): ActiveMQ and RabbitMQ support (#770) by @michaelbrewer
  • feat(appsync): add Router to allow large resolver composition (#776) by @michaelbrewer
  • feat(apigateway): add Router to allow large routing composition (#645) by @BVMiko

🌟 Minor Changes

📜 Documentation updates

🐛 Bug and hot fixes

  • fix(parser): body/QS can be null or omitted in apigw v1/v2 (#820) by @heitorlessa

🔧 Maintenance

  • chore(deps): bump boto3 from 1.20.3 to 1.20.5 (#817) by @dependabot
  • chore(deps): bump boto3 from 1.19.6 to 1.20.3 (#809) by @dependabot
  • chore(deps-dev): bump mkdocs-material from 7.3.5 to 7.3.6 (#791) by @dependabot
  • fix: change supported python version from 3.6.1 to 3.6.2, bump black (#807) by @cakepietoast
  • chore(deps-dev): bump mkdocs-material from 7.3.3 to 7.3.5 (#781) by @dependabot
  • chore(deps-dev): bump flake8-isort from 4.0.0 to 4.1.1 (#785) by @dependabot
  • chore(deps): bump urllib3 from 1.26.4 to 1.26.5 (#787) by @dependabot
  • chore(deps-dev): bump flake8-eradicate from 1.1.0 to 1.2.0 (#784) by @dependabot
  • chore(deps): bump boto3 from 1.18.61 to 1.19.6 (#783) by @dependabot
  • chore(deps-dev): bump pytest-asyncio from 0.15.1 to 0.16.0 (#782) by @dependabot
  • chore(deps-dev): bump coverage from 6.0.1 to 6.0.2 (#764) by @dependabot
  • chore(deps): bump boto3 from 1.18.59 to 1.18.61 (#766) by @dependabot
  • chore(deps-dev): bump mkdocs-material from 7.3.2 to 7.3.3 (#758) by @dependabot
  • chore(deps-dev): bump flake8-comprehensions from 3.6.1 to 3.7.0 (#759) by @dependabot
  • chore(deps): bump boto3 from 1.18.58 to 1.18.59 (#760) by @dependabot
  • chore(deps-dev): bump coverage from 6.0 to 6.0.1 (#751) by @dependabot
  • chore(deps): bump boto3 from 1.18.56 to 1.18.58 (#755) by @dependabot

This release was made possible by the following contributors:

@AlessandroVol23, @BVMiko, @Tankanow, @arthurf1969, @cakepietoast, @dependabot, @dependabot[bot], @eldritchideen, @heitorlessa, @jonemo and @michaelbrewer