Skip to content

Feature request: Allow the ApiGatewayResolver to support an existing subclass of BaseProxyEvent #3267

Closed
@BVMiko

Description

@BVMiko

Use case

I often want to retrieve values from a ProxyEvent using the helper functions prior to the app.resolve() call; and find myself creating a second instance for it, for example:

@event_source(data_class=APIGatewayProxyEvent)
def lambda_handler(event: APIGatewayProxyEvent, context: LambdaContext):
    logger.append_keys(user_agent=event.get_header_value("user-agent", "[No User-Agent]"))
    return app.resolve(event.raw_event, context)

Where I feel I should just be able to pass in the instance which was already created:

    return app.resolve(event, context)

I've now found myself wanting to use the ApiGatewayResolver class with a Lambda triggered by EventBridge Rules, and found that it can be done trivially with a custom ProxyEvent subclass -- but only if the ApiGatewayResolver accepts the custom ProxyEvent.

The changes required to support a custom ProxyEvent are very minimal and shouldn't be backwards-incompatible.

I see there was an adjustment about 18 months ago in #1152 & #1159 where a warning message and fallback were added; my proposed change involves replacing the warning with fully supporting the existing ProxyEvent class.

Solution/User Experience

@event_source(data_class=APIGatewayProxyEvent)
def lambda_handler(event: APIGatewayProxyEvent, context: LambdaContext):
    logger.append_keys(user_agent=event.get_header_value("user-agent", "[No User-Agent]"))
    return app.resolve(event.raw_event, context)

becomes

@event_source(data_class=APIGatewayProxyEvent)
def lambda_handler(event: APIGatewayProxyEvent, context: LambdaContext):
    logger.append_keys(user_agent=event.get_header_value("user-agent", "[No User-Agent]"))
    return app.resolve(event, context)

Also it allows a custom ProxyEvent to be used such as:

class CustomProxyEvent(BaseProxyEvent):
    def __init__(self, data: dict[str, Any], json_deserializer: Optional[Callable]=None):
        data_wrapper = {"path": "", "httpMethod": data["action"], "body": json.dumps(data)}
        super().__init__(data_wrapper, json_deserializer)
        self._json_data = data

    @property
    def action(self) -> str:
        return cast(str, cast(dict, self._json_data)["action"])

    @property
    def content(self) -> str:
        return cast(str, cast(dict, self._json_data)["content"])

    # Unfortunately this must still be provided unless we mark it as optional.
    # I have a separate commit prepared, if you are willing to consider I can include it
    def header_serializer(self) -> BaseHeadersSerializer:
        return MultiValueHeadersSerializer()

app = ApiGatewayResolver(debug=True)
logger = Logger()

@app.route("", "foo")
def group_stats_get() -> Response:
    return Response(200, body=app.current_event.content)

@event_source(data_class=CustomProxyEvent)
def handler(event: CustomProxyEvent, context: LambdaContext):
    logger.append_keys(action=event.action)
    return app.resolve(event, context)

with the data

{"action": "foo", "content": "bar"}

Alternative solutions

No response

Acknowledgment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Closed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions