Skip to content

Feature request: fill the request context from the ASGI scope in HttpResolverLocal #8802

Open

Description

@dacevedo12

Use case

Our API runs behind a Lambda Function URL and its handlers read app.current_event.request_context.http.source_ip and .user_agent. Since 3.39 the same HttpResolverLocal app works both in Lambda and under uvicorn, which lets us drop a separate local Lambda emulator.

Under ASGI, though, HttpProxyEvent.from_asgi only copies the method, path, headers, body and query string:

def from_asgi(cls, scope: Mapping[str, Any], body: bytes | None = None) -> HttpProxyEvent:
"""
Create an HttpProxyEvent from an ASGI scope dict.
Parameters
----------
scope : dict
ASGI scope dictionary
body : bytes, optional
Request body
Returns
-------
HttpProxyEvent
Event object compatible with Powertools resolvers
"""
# Extract headers from ASGI format [(b"key", b"value"), ...]
headers: dict[str, str] = {}
for key, value in scope.get("headers", []):
header_name = key.decode("utf-8").lower()
header_value = value.decode("utf-8")
# Handle duplicate headers by joining with comma
if header_name in headers:
headers[header_name] = f"{headers[header_name]}, {header_value}"
else:
headers[header_name] = header_value
return cls(
method=scope["method"],
path=scope["path"],
headers=headers,
body=body,
query_string=scope.get("query_string", b"").decode("utf-8"),
)

The raw request context falls back to a stub with only stage, requestId and http.method/path:

"requestContext": request_context
or {
"stage": "local",
"requestId": "local-request-id",
"http": {"method": method.upper(), "path": path},
},

HttpProxyEvent also has no request_context property, so code that reads the caller's address or user agent from the request context raises AttributeError locally, even though the ASGI scope has client and the headers have user-agent.

Solution/User Experience

Fill the request context from the ASGI scope in from_asgi: the source IP from scope["client"] and the user agent from the user-agent header, and expose it on the local event, which today has no request_context property.

It would also help to let an app choose the local event shape, so an app written for HTTP API or Function URL events reads request_context.http locally the same way it does in Lambda.

Alternative solutions

We subclass `HttpResolverLocal`: `asgi_handler` keeps the client address in a ContextVar, and `_to_proxy_event` rebuilds the local event as a version 2.0 event with `requestContext.http` (method, path, sourceIp, userAgent, stage). It works, but it relies on the private `_to_proxy_event` and on `_state` for a typed `current_event`.

How we checked it, on 3.39.0: under uvicorn, current_event is an HttpProxyEvent with no request_context property, and its raw request context has only stage, requestId and http.method/path. With the subclass, the same app records the client address and user agent, and our existing tests still resolve version 2.0 Function URL events through it (realistic events in tests, not a deployed function).

This issue was drafted with an AI assistant.

Acknowledgment

Activity

  1. boring-cyborg commented on Nov 13, 2026

    @boring-cyborg

    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

  2. powertools-for-aws-oss-automation commented on Nov 13, 2026

    @powertools-for-aws-oss-automation

    Thanks for opening your first issue here! We'll come back to you as soon as we can.
    In the meantime, check out our GitHub Discussions: Join the conversation

  3. leandrodamascena commented on Nov 15, 2026

    @leandrodamascena
    Contributor

    I like this. HttpResolverLocal under uvicorn should give handlers the same request_context fields they read in Lambda, at least http.source_ip, http.user_agent, http.method, http.path and the headers, filled from the ASGI scope. Anything Lambda-specific stays as it is today. Accepting for 3.42.0. Do you want to send the PR? You clearly know the code path.

  4. added this to the 3.42.0 milestone on Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions