Description
Is your feature request related to a problem? Please describe.
As part of awslabs/aws-lambda-powertools-python#218 RFC, we had a brief discussion about exception handling but didn't implement in the first iteration as we weren't sure.
This is to enable a discussion on this before we make Idempotency G, or to agree on whether we should do this.
Describe the solution you'd like
A mechanism to allow an external function to handle individual or all exceptions raised as part of the Idempotency utility.
At present, customers using API Gateway might want to return a different response to their end customers if an operation is already in progress. This is not currently possible and requires a custom middleware - This brings operational and maintenance complexity.
Haven't put much thought on the UX yet hence why creating this to enable this discussion.
Example for handling a given exception
import os
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer, IdempotencyConfig, idempotent
)
from aws_lambda_powertools.utilities.idempotency.exceptions import IdempotencyAlreadyInProgressError
persistence_layer = DynamoDBPersistenceLayer(table_name=os.getenv("TABLE_NAME"))
config = IdempotencyConfig(
event_key_jmespath="body",
use_local_cache=True,
exception_handler=my_custom_exception_handler,
exceptions_list=[IdempotencyAlreadyInProgressError]
)
def my_custom_exception_handler(event: Dict[str, Any], exception: BaseException):
...
@idempotent(config=config, persistence_store=persistence_layer)
def handler(event, context):
...
Example for handling all exceptions
import os
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer, IdempotencyConfig, idempotent
)
persistence_layer = DynamoDBPersistenceLayer(table_name=os.getenv("TABLE_NAME"))
config = IdempotencyConfig(
event_key_jmespath="body",
use_local_cache=True,
exception_handler=my_custom_exception_handler,
)
def my_custom_exception_handler(event: Dict[str, Any], exception: BaseException):
...
@idempotent(config=config, persistence_store=persistence_layer)
def handler(event, context):
...
Describe alternatives you've considered
Create a custom middleware using middleware factory utility.
Additional context
I don't think we need to change how we remove idempotency keys from the store when exceptions are raised. This is mostly to give customers an option to handle exceptions as they see fit.
FAQ
Does this include exceptions raised in your own handler, or only for the Idempotent related parts?
I'd vote for Idempotent related only, though I also see the point of catching all of them as it can quickly become confusing. I'm on two minds here - Either building a separate scoped utility to handle exceptions as a whole, or handle Idempotency exceptions only to give customers a chance to provide a non-error response when needed.
I like the former because it allows other utilities like the future Circuit Breaker, integration with Error Tracking systems like Sentry, and a clean intent with batteries included (e.g. retry on X exceptions, etc.).
Would the idempotent library have standard error responses defined for API Gateway proxy requests?
No. This could get out of hand quickly, as the next would be Circuit Breaker, or any other utility we might want to provide.
If it is the result of the handler, then if it does not re-raise the error, does this result then get saved in the idempotent persistence layer?
That's where I think it's tricky and I'd like to not touch the current Idempotency mechanics - It's a complex sequence already, and an additional branch flow could make this harder to maintain and reason.