Skip to content
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

Event Handler: API Gateway current_event doesn't show all properties #761

Closed
Tracked by #1009
heitorlessa opened this issue Oct 12, 2021 · 6 comments · Fixed by #978
Closed
Tracked by #1009

Event Handler: API Gateway current_event doesn't show all properties #761

heitorlessa opened this issue Oct 12, 2021 · 6 comments · Fixed by #978
Labels

Comments

@heitorlessa
Copy link
Contributor

When trying to access request_context when using either API Gateway v1 or v2 we can't have type hinting due to ALB being different - both inherit from BaseProxyEvent.

Overloading the internal method _to_proxy_event with all potential returns, even with Literal[ProxyEventType.APIGatewayProxyEvent] didn't seem to yield any result, as MyPy and Intelisense continues to think it's a BaseProxyEvent.

Need a deeper understanding of MyPy and type invariants to potentially solve this.

from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver

app = ApiGatewayResolver()


@app.get("/test")
def test_it():
	# this should autocomplete when not using ALB as the ProxyType argument in ApiGatewayResolver
	app.current_event.request_context.identity.account_id 
    ...


def handler(event, context):
    app.resolve(event, context)


app({}, None)
@heitorlessa heitorlessa added the help wanted Could use a second pair of eyes/hands label Oct 12, 2021
@heitorlessa
Copy link
Contributor Author

Forgot the instance variable had an explicit type of BaseProxyEvent hence why overloading didn't potentially work... shall try again at some point -- current_event: BaseProxyEvent

@michaelbrewer
Copy link
Contributor

michaelbrewer commented Dec 10, 2021

@heitorlessa any suggestions on how to implement this on Python? I am not sure how generics would work on Python.

Another option would be to add properties that cast to the preferred type app.current_alb_event, app.current_rest_event and app.current_http_event?

@heitorlessa
Copy link
Contributor Author

Because we have a parameter that indicates which Proxy Type to use, we can use function overloading from typing to indicate the return type will be different based on the params received.

I'm considering to have an entire release just focused on MyPy stuff as more and more customers are raising issues on typing that could've been prevented, then enable MyPy at CI level -- it's a good thing, we just need a focused release or else other higher priorities will take place.

After the next release I'll write a plan for this on all areas we need. MyPy and Integ test for runtime botocore are my top concerns now

@heitorlessa heitorlessa added this to the 1.24.0 milestone Dec 21, 2021
@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@heitorlessa
Copy link
Contributor Author

heitorlessa commented Jan 24, 2022

Dumping it here for posterity.

None of the techniques I tried worked due to being late-bound: Dynamically inject properties, Create BaseProxyEvent at runtime, Use generic to propagate, etc.

The closest I've got working is using an Union of all Proxy Event types -- auto-completion works as expected, but it shows attributes that might not be there leading to an AttributeError.

Late bound issue

As the class variable defines the type upfront, static analysis cannot evaluate what is the correct event as it's only resolved when a request is received:

class BaseRouter(ABC):
    current_event: ProxyEventTypes  # Union[APIGatewayProxyEvent, APIGatewayProxyEventV2, ALBEvent]
    lambda_context: LambdaContext
    ...
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.utilities.data_classes.api_gateway_proxy_event import APIGatewayProxyEvent

app = ApiGatewayResolver()

@app.get
def hello():
    request: APIGatewayProxyEvent = app.current_event  # explicit type annotation works
    request = app.current_event.route_key  # leads to an AttributeError despite auto-completion as it's a API GW V1 event
    return request.get_header_value(name="X-Id", default_value="Blah")

Next steps

Three potential solutions in my head now:

  1. Create a separate class for each type of event, update the docs (ApiGatewayV1Resolver, etc.)
  2. Refactor the class instance that was created as part of Router's implementation, use a proper attribute or property w/ setter, so we can propagate the type correctly. Reason: Doesn't solve the late-bound issue.
  3. Keep Union type, explicitly call out in the docs that it's best to use type annotation to have precise types -- Cons: Potential AttributeError and possibly methods that might not exist (need to check) Reason: Too easy to get it wrong, catching every AttributeError can decrease performance too.

cc @cakepietoast @mploski as I need some brainstorming.

@heitorlessa heitorlessa changed the title Maintenance: Event Handler API Gateway BaseProxyEvent type Event Handler: API Gateway current_event doesn't show all properties Jan 24, 2022
michaelbrewer added a commit to gyft/aws-lambda-powertools-python that referenced this issue Jan 26, 2022
…ties

Add new specialized resolvers APIGatewayProxyEventResolver, APIGatewayProxyEventV2Resolver and ALBEventResolver

closes aws-powertools#761
@heitorlessa heitorlessa removed this from the 1.24.0 milestone Jan 26, 2022
@heitorlessa heitorlessa added p1 and removed help wanted Could use a second pair of eyes/hands labels Jan 30, 2022
@michaelbrewer
Copy link
Contributor

DX from #978


Example of an Application Load Balancer handler (ALBResolver) where current_event can only be an ALBEvent

from aws_lambda_powertools.event_handler import ALBResolver

app = ALBResolver()

@app.get("/lambda")
def foo():
    # Now you can access ALBEvent specific attributes
    assert app.current_event.request_context.elb_target_group_arn is not None
    return Response(200, content_types.TEXT_HTML, "foo")

Example of an REST API handler (APIGatewayRestResolver) where current_event can only be an APIGatewayProxyEvent

from aws_lambda_powertools.event_handler import APIGatewayRestResolver

app = APIGatewayRestResolver()

@app.get("/my/path")
def get_lambda() -> Response:
    # Now you can access APIGatewayProxyEvent specific attributes
    assert app.current_event.request_context.domain_name == "id.execute-api.us-east-1.amazonaws.com"
    return Response(200, content_types.APPLICATION_JSON, json.dumps({"foo": "value"}))

Example of an Http API handler (APIGatewayHttpResolver) where current_event can only be an APIGatewayProxyEventV2

from aws_lambda_powertools.event_handler import APIGatewayHttpResolver

app = APIGatewayHttpResolver()

@app.post("/my/path")
def my_path() -> Response:
    # Now you can access APIGatewayProxyEventV2 specific attributes
    assert app.current_event.cookies[0] == "cookie1"
    return Response(200, content_types.TEXT_PLAIN, "Foo")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants