You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Feature request: Async support for ApiGatewayResolver #3934
I would love to be able to use asynchronous Python with Powertools. I understand there is not as much need for it in a Lambda runtime as a Lambda (process) will handle only one event but I would still be able to use async-only libraries like encode/databases (with the likes of Neon) or simply reduce my execution time by utilizing asyncio.gather when doing concurrent waits for I/O operations.
Solution/User Experience
I imagine being able to use it similarly to what the GraphQL resolver does:
@app.get("/todos")@tracer.capture_methodasyncdefget_todos():
todos: httpx.Response=httpx.get("https://jsonplaceholder.typicode.com/todos")
todos.raise_for_status()
return {"todos": todos.json()[:10]}
# You can continue to use other utilities just as before@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)@tracer.capture_lambda_handlerdeflambda_handler(event: dict, context: LambdaContext) ->dict:
result=app.resolve(event, context)
returnasyncio.run(result)
Should this be considered in other Powertools for AWS Lambda languages? i.e. Java, TypeScript, and .NET
Reacted by Wojciech Gajda, Piotr Grundas, DamianCzajkowski, Karol Jagodziński, Adam Janik, facu-01, Heitor Lessa, FrancisRalph, keremeyuboglu, Fabrício Maruta and 18 more
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link
Hey @pkucmus! Wowww, we're diving into some pretty cool Feature Request here: making our EventHandler and all its resolvers work async and add more flexibility to enhance I/O operations. But it comes with some significant changes ahead.
One big switch-up is how we handle object serialization. It basically means we'll need to rethink how we call Python functions and make all (or most) of them async. Another challenge is that our resolvers are a kind of Middleware between Lambda execution and the map between specific routes (GET /todos, POST /hello) to execute functions. That adds a bit of extra spice to the mix of how to execute these functions with coroutines.
And when it comes to our OpenAPI validation, currently, it's all about sync calls. Shifting this to asynchronous operations is quite a significant work.
To be completely transparent, I'm unable to quantify the extent of work needed to implement this capability right now. Perhaps we should consider drafting an RFC to outline the process in detail. It might even be worth exploring the creation of entirely new resolvers that operate entirely async.
It's encouraging to see "thumbs up" from many people regarding this matter, and I believe we should seriously consider it. To ensure we stay on track, I'll add some labels such as "revisit in 3 months" and "need help" to keep this issue on our radar and facilitate brainstorming for a solution.
Thank you and please let us know if you have in mind any kind of implementation or solution for this.
Thank you @leandrodamascena, what a nice approach for a random idea like that. I would be happy to put in some work into this. Maybe this would not be as replacement for the existing resolvers but something additional? If you would like I could draft something but I would need to understand how it's currently working, i.e. the use of the global state is confusing me a lot - I'm sure there's a reason for it but I don't know why we're storing and accessing the event in a global manner. Is there chance for some guidance or rather I should stay put not to generate more work for you than needed?
Thank you @leandrodamascena, what a nice approach for a random idea like that. I would be happy to put in some work into this. Maybe this would not be as replacement for the existing resolvers but something additional? If you would like I could draft something but I would need to understand how it's currently working
We're truly appreciate the possibility of you working on a draft for how we can incorporate support for Async in our resolvers and make it work.
i.e. the use of the global state is confusing me a lot - I'm sure there's a reason for it but I don't know why we're storing and accessing the event in a global manner.
This code is a little older, probably from when we created the Event Handler utility, but it is still in use. Payloads are different when working with Event Handler, and we store the event and context in the BaseRouter class because we need to access particular fields depending on the type of Resolver, i.e:
All of our resolvers inherit from ApiGatewayResolver and ApiGatewayResolver inherits from BaseRouter (the Router class too), so we store this to be easily accessible globally across all Resolvers and dependencies.
Does this explanation cover what you need, or would you like more information? Feel free to ask if you need further clarification!
Is there chance for some guidance or rather I should stay put not to generate more work for you than needed?
Of course, there is! Please feel free to share any questions or blocks you have, and we can collaborate to find the best way to move forward.
Thank you for taking the time to collaborate on this matter! 🌟
I tried your decorator and it indeed works by leveraging asyncio.run to manage the event loop for each synchronous call to our endpoints. However, this approach seems to counteract the benefits of asynchronous programming due to the significant performance overhead introduced, which I'm yet to find out why (im using aioboto3).
I also wanted to discuss why incorporating async programming is important, especially in the context of API Gateway. In many projects, I've implemented the API Composition pattern, where the API Gateway is responsible for querying multiple services and aggregating their responses. It's very common API Gateways are responsible for doing this. This model inherently benefits from asynchronous code, as it allows for concurrent calls to multiple services, enhancing efficiency and reducing total response time from the server.
For instance, if my Lambda function needs to query 5 different AWS services, firing all these requests simultaneously (assuming independence among them) is far more efficient than making sequential calls. Given that Lambda functions already have latency issues by nature, adding synchronous operations only worsens the response time.
Moreover, I encountered compatibility issues with the X-Ray SDK when using aioboto3 for making concurrent AWS service calls via asyncio.gather. This resulted in an exception: Already ended segment and subsegment cannot be modified. (https://docs.powertools.aws.dev/lambda/python/latest/core/tracer/#concurrent-asynchronous-functions))
Consequently, I was unable to receive a successful response or track the trace for this case.
By not using asyncio.gather and just making a single asynchronous AWS call didn’t improve the situation either. Surprisingly, endpoint response times increased significantly, from approximately 800 ms to 1700 ms, when querying DynamoDB asynchronously. This suggests that the use of asyncio.run could be introducing an overhead, but I can't confirm by how much. I'll do more tests. A difference of 800ms is too much. I'll update as soon as I have more results.
Hey everyone! I did a deep investigation into what it takes to make this work properly. I want to be very transparent about the complexity and share the plan I have on top of my mind.
Current situation
The entire resolve() chain today is synchronous - from route matching through the middleware stack to response serialization. A simple asyncio.run() wrapper around the handler (like the workarounds shared by @drecdroid and @sevetseh28) works for basic cases, but it bypasses our middleware chain, doesn't work with OpenAPI validation, and has the X-Ray issues you've already experienced.
To do this right, we need to build a parallel async middleware chain alongside the existing sync one.
I don't think everything needs to be async because some operations like JSON parsing, Pydantic validation, response serialization, and compression are all CPU-bound and not I/O bound, so, there is nothing to do here with async. Async only helps with I/O (DynamoDB, S3, HTTP calls - the stuff you actually want to parallelize). So the existing sync operations stay sync. We only need async for the call structure so we can await the user's handler at the right point.
Existing sync middlewares will continue working without any changes. Async middlewares will also be supported for those who need them.
I appreciate everyone's patience and the workarounds you've shared - they helped me understand the real-world usage patterns. As always, contributions and feedback are welcome.
Activity
boring-cyborg commentedon Mar 11, 2024
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link
leandrodamascena commentedon Mar 12, 2024
Hey @pkucmus! Wowww, we're diving into some pretty cool Feature Request here: making our EventHandler and all its resolvers work async and add more flexibility to enhance I/O operations. But it comes with some significant changes ahead.
One big switch-up is how we handle object serialization. It basically means we'll need to rethink how we call Python functions and make all (or most) of them async. Another challenge is that our resolvers are a kind of Middleware between Lambda execution and the map between specific routes (GET /todos, POST /hello) to execute functions. That adds a bit of extra spice to the mix of how to execute these functions with coroutines.
And when it comes to our OpenAPI validation, currently, it's all about sync calls. Shifting this to asynchronous operations is quite a significant work.
To be completely transparent, I'm unable to quantify the extent of work needed to implement this capability right now. Perhaps we should consider drafting an RFC to outline the process in detail. It might even be worth exploring the creation of entirely new resolvers that operate entirely async.
It's encouraging to see "thumbs up" from many people regarding this matter, and I believe we should seriously consider it. To ensure we stay on track, I'll add some labels such as "revisit in 3 months" and "need help" to keep this issue on our radar and facilitate brainstorming for a solution.
Thank you and please let us know if you have in mind any kind of implementation or solution for this.
pkucmus commentedon Mar 13, 2024
Thank you @leandrodamascena, what a nice approach for a random idea like that. I would be happy to put in some work into this. Maybe this would not be as replacement for the existing resolvers but something additional? If you would like I could draft something but I would need to understand how it's currently working, i.e. the use of the global state is confusing me a lot - I'm sure there's a reason for it but I don't know why we're storing and accessing the event in a
globalmanner. Is there chance for some guidance or rather I should stay put not to generate more work for you than needed?powertools-lambda-python/aws_lambda_powertools/event_handler/api_gateway.py
Lines 1814 to 1815 in c3e36de
leandrodamascena commentedon Mar 18, 2024
We're truly appreciate the possibility of you working on a draft for how we can incorporate support for Async in our resolvers and make it work.
This code is a little older, probably from when we created the Event Handler utility, but it is still in use. Payloads are different when working with Event Handler, and we store the event and context in the
BaseRouterclass because we need to access particular fields depending on the type of Resolver, i.e:ALB - https://github.com/aws-powertools/powertools-lambda-python/blob/develop/tests/events/albEvent.json
VPC Lattice - https://github.com/aws-powertools/powertools-lambda-python/blob/develop/tests/events/vpcLatticeEvent.json
API Gateway REST - https://github.com/aws-powertools/powertools-lambda-python/blob/develop/tests/events/apiGatewayProxyEvent.json
All of our resolvers inherit from
ApiGatewayResolverandApiGatewayResolverinherits fromBaseRouter(theRouterclass too), so we store this to be easily accessible globally across all Resolvers and dependencies.Does this explanation cover what you need, or would you like more information? Feel free to ask if you need further clarification!
Of course, there is! Please feel free to share any questions or blocks you have, and we can collaborate to find the best way to move forward.
Thank you for taking the time to collaborate on this matter! 🌟
drecdroid commentedon Jul 9, 2024
I don't know if this is entirely correct but I'm solving it this way:
sevetseh28 commentedon Jul 21, 2024
Hey @sykire,
I tried your decorator and it indeed works by leveraging asyncio.run to manage the event loop for each synchronous call to our endpoints. However, this approach seems to counteract the benefits of asynchronous programming due to the significant performance overhead introduced, which I'm yet to find out why (im using
aioboto3).I also wanted to discuss why incorporating async programming is important, especially in the context of API Gateway. In many projects, I've implemented the API Composition pattern, where the API Gateway is responsible for querying multiple services and aggregating their responses. It's very common API Gateways are responsible for doing this. This model inherently benefits from asynchronous code, as it allows for concurrent calls to multiple services, enhancing efficiency and reducing total response time from the server.
For instance, if my Lambda function needs to query 5 different AWS services, firing all these requests simultaneously (assuming independence among them) is far more efficient than making sequential calls. Given that Lambda functions already have latency issues by nature, adding synchronous operations only worsens the response time.
Moreover, I encountered compatibility issues with the X-Ray SDK when using aioboto3 for making concurrent AWS service calls via
asyncio.gather. This resulted in an exception:Already ended segment and subsegment cannot be modified.(https://docs.powertools.aws.dev/lambda/python/latest/core/tracer/#concurrent-asynchronous-functions))Consequently, I was unable to receive a successful response or track the trace for this case.
By not using
asyncio.gatherand just making a single asynchronous AWS call didn’t improve the situation either. Surprisingly, endpoint response times increased significantly, from approximately 800 ms to 1700 ms, when querying DynamoDB asynchronously. This suggests that the use ofasyncio.runcould be introducing an overhead, but I can't confirm by how much. I'll do more tests. A difference of 800ms is too much. I'll update as soon as I have more results.8 remaining items
leandrodamascena commentedon Apr 6, 2026
Hey everyone! I did a deep investigation into what it takes to make this work properly. I want to be very transparent about the complexity and share the plan I have on top of my mind.
Current situation
The entire
resolve()chain today is synchronous - from route matching through the middleware stack to response serialization. A simpleasyncio.run()wrapper around the handler (like the workarounds shared by @drecdroid and @sevetseh28) works for basic cases, but it bypasses our middleware chain, doesn't work with OpenAPI validation, and has the X-Ray issues you've already experienced.To do this right, we need to build a parallel async middleware chain alongside the existing sync one.
I don't think everything needs to be async because some operations like JSON parsing, Pydantic validation, response serialization, and compression are all CPU-bound and not I/O bound, so, there is nothing to do here with async. Async only helps with I/O (DynamoDB, S3, HTTP calls - the stuff you actually want to parallelize). So the existing sync operations stay sync. We only need async for the call structure so we can
awaitthe user's handler at the right point.Here's how the async chain looks internally:
The new implementation
I'm not promising an exactly date here, but I want to make a dedicated release in Q3 2026 with two phases.
Phase 1 - Core async support:
resolve_async()method - existingresolve()stays untouchedAsyncMiddlewareFramethat auto-detects sync vs async middlewares_registered_api_adapterthat awaits coroutine resultsPhase 2 - Tracer/observability:
asyncio.gathercompatibility (theAlready ended segmentissue @sevetseh28 hit)@tracer.capture_methodfor async functions already existsAn example
Existing sync middlewares will continue working without any changes. Async middlewares will also be supported for those who need them.
I appreciate everyone's patience and the workarounds you've shared - they helped me understand the real-world usage patterns. As always, contributions and feedback are welcome.
_resolve_async()and_call_route_async()for async resolver support #8136resolve_async()public method for async handler support #8137feat(event-handler): add _registered_api_adapter_async() internal bui…
feat(event-handler): add _registered_api_adapter_async() internal bui…
github-actions commentedon May 4, 2026
This is now released under 3.29.0 version!