-
Notifications
You must be signed in to change notification settings - Fork 429
RFC: Pass custom context dict to resolve method #1547
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for opening your first issue here! We'll come back to you as soon as we can. |
Hey @MaartenUreel thanks a lot for this! I like this idea more than an actual Dependency Injection mechanism ala FastAPI, since that would increase the bar to entry and over-complicate things. Question: How are you envisioning If you have the bandwidth, feel free to start a draft implementation and a test. I'd expect |
@walmsles @gwlester @benbridts - I'd be interested in your thoughts here. |
Would getting access to the original event (new feature, I think) also solve this (you could also add middelware instead of doing it in the handler? I don't think this is necessarily better than the original proposal (as it's a bit more of a hack), but might be used for other things too. def lambda_handler(event: dict, context: LambdaContext) -> dict:
event["_extra_context"] = {"some_custom": "data"}
return app.resolve(event, context)
# ...
@router.get("/todos")
def get_todos():
return app.orignal_event["_extra_context"]["some_custom"] My first reaction was "I might want this as a decorator instead of something in the handler - potentionally a middelware function" # not sure if this is possible
@router.get("/todos")
@extra_context # middelware you have te create yourself, fills in extra_context based on the original event
def get_todos(extra_context=None):
pass |
My read is this is a request to add "user data" object to pass around with some application-specific (not event-specific) information. Seems reasonable. The workaround I've seen is to create a singleton class that has the "user data" in it and get the instance every place you need it. Of course, I could be off in left feel and missing what is being asked for. |
What @gwlester said. This opens the door for a newer future we couldn't make progress yet (middleware support), so you could add additional context (user data) on a before/after request basis.. typically used in AuthZ, validation, etc. @benbridts for event access, we provide that feature since launch ;) |
From the router we have access to the resolver. From the resolver don't we already have access to the event and context?? |
Yes, you do. Both resolver and router inherit from BaseRouter, allowing you to access both |
I was aiming at event-specific data, e.g. in my use case a dataclass. |
@MaartenUreel from the initial discussion you had, @gwlester is saying the same thing.. it's just the word "event-specific" that is confusing the intent. One thing is to mutate the incoming event a Lambda receives (available at |
@heitorlessa, have been thinking about this and agree with @benbridts with the middleware/decorator approach rather than polluting the resolver.resolve() method with additional overhead. This feels like a custom use-case and today it's a dict, tomorrow it could be something different - middleware kind of resolves the ambiguity without opening that maintenance doorway. Providing an easy mechanism to create route middleware I feel would be an approach that is more usable and is a utility that is useful for AWS Lambda powertools to provide. I kind of played today at "rolling my own" and came up with the following (excuse the messy code): from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools import Logger
import functools
app = APIGatewayRestResolver()
logger = Logger()
def route_decorator(route_func):
"""Define route decorator middleware function to add my user data"""
@functools.wraps(route_func)
def wrapper(*args, **kwargs):
kwargs["user_data"] = {"param1":"value1"}
logger.info("calling route_decorator")
return route_func(*args, **kwargs)
return wrapper
@app.get("/hello")
@route_decorator
def hello_world(user_data=None):
return {"message": "Hello World", "user_data": user_data}
@logger.inject_lambda_context(log_event=True)
def lambda_handler(event, context):
return app.resolve(event, context) The decorator has to sit after the route definition and at this point, the app.current_event object is populated so can do custom processing across this to parse parameters or other things. This also achieves the DRY principle and enables different processing for different routes which adds more flexibility IMO. |
Thank you as always @walmsles Question: How would you access request information in the route_decorator as it's unaware of the "app" object? I think we have two non-mutually exclusive use cases here:
The former is similar to @walmsles snippet, where we could provide a "before_request", "after_request" decorator factory, and accept a list of callable sat route definition too -- here we can pass request/context info to you. The latter is more closely to Flask G object, but accessed as "app.route_context". This is also a problem outside Event Handler, where you'd like to access Lambda Context anywhere in your code (harder to solve). https://tedboy.github.io/flask/interface_api.app_globals.html For this RFC, did I capture this right? Shall we move towards middleware and let customers handle state isolation themselves? Add your +1 Great discussion! It makes me wonder how to best utilize Discord Audio channels to speed up decisions. |
The actual resolver is instantiated as a global var, so will be in scope for the route_decorator to reference in the same way it is used by the function routes. Middleware for routes is a nice feature - agree needs to be designed well to minimise setup and make it easily digestible and to also expose the inner event data easily. Question: With Share data - How does this help with heavy compute in comparison to middleware solution given the share_data is executed for each lambda invocation? |
Timezones Uh-Oh (I am on the wrong continent still 😁) |
You'd do the computation in the global scope (cold start) because compute resources are allocated without slicing. For Middleware, customers will typically do that at middleware level for completeness, where we sliced compute resources (once Handler is called). As I write this, I could equally make the case that a middleware author could do the same (global scope computation). In practicality, it's more common for middlewares to be self-contained and not rely on global scope vs a loose share_data approach |
Apologies, I usually use external routing ;). I assumed current_event only have the unwrapped fields available. I agree with @walmsles, but we should make sure that this solves @MaartenUreel's problem |
That's great feedback, Ben! We can do a better job in calling out customers can also use external routing (most do!) while still benefiting from the DX, and more importantly that |
Had more thoughts on this, discussed with @rubenfonseca and @leandrodamascena too. I'd like @MaartenUreel (main customer) and @walmsles final weight in here, before we decide Go/No Go. Proposal: Add a new method called Why: As you grow your number of related routes for your service (micro-Lambda or mono-Lambda), it's common to end up with a few hundred lines of code in a single file - not everyone is comfortable with that. As you split, you can end up in a circular import scenario when dealing with global objects, if you're not careful - testing becomes harder too. Why not middleware: They complement each other. Context solves simple data passing while a middleware can use or further enrich that contextual data. For example, a fine-grained AuthZ middleware could append context data (e.g., Lambda function URL, not API GW as it's a problem solved). It also helps isolate incoming event to data related to processing that event, where you don't want to mix the two for logical or incompatibility reasons (event validation). If you agree, I can help guide the implementation details for anyone with the bandwidth. I'd also welcome a RFC to support Middleware in Event Handler. Thanks a lot everyone <3 |
Sounds like a perfect solution to me! |
LGTM too! Love the clarity of thinking in the writeup, always impressed. |
Fantastic. @MaartenUreel, would you like to do the honours and contribute it so we can credit you appropriately? If you don't have the bandwidth, I can do that and tag you in the release notes regardless. Hoping we can incorporate this feature in this week's release (Friday the latest) |
The challenge is that I would have to look into the structure of the project etc, it might be faster if you guys handle it. |
Leave with me then.
I understand. I'd like to emphasize that all maintainers should always give the authors priority in the implementation regardless :) Every contribution is valid. Every little help helps - from docs, feature requests, bug reporting, discussions, etc. We should always credit people from the community ;) |
Doing the implementation now, you can follow the PR if you're interested in the details: #1567 |
Done, just need a review from any of the maintainers (or a community member). I've also made sure to future proof for middlewares, so you can also call NOTE. The same experience will work for ALB, API Gateway REST, API Gateway HTTP, and AppSync resolvers. Docs preview |
|
This is now released under 1.30.0 version! |
Is this related to an existing feature request or issue?
#1546
Which AWS Lambda Powertools utility does this relate to?
Event Handler - REST API
Summary
It would be useful if you could provide an optional dict to the
app.resolve()
method, that would then be available in the handlers themselves.This would contain generic information that is needed for all requests.
Use case
We are implementing callbacks for Twilio. For every event, we need to:
Next, we need the same parameters from step 2 in almost every request.
Proposal
Provide an extra parameter to the
resolve()
method:In the handler itself, it could then be accessible via
route_context
:Out of scope
n/a
Potential challenges
n/a
Dependencies and Integrations
No response
Alternative solutions
Currently I'm passing the data in
app.lambda_context.__dict__
.Acknowledgment
The text was updated successfully, but these errors were encountered: