Skip to content

Commit 1730848

Browse files
Improve workflow
1 parent 933000f commit 1730848

2 files changed

Lines changed: 92 additions & 13 deletions

File tree

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import logging
77
import re
88
import traceback
9-
import typing
109
import warnings
1110
import zlib
1211
from abc import ABC, abstractmethod
@@ -468,10 +467,10 @@ def __init__(
468467

469468
self.custom_response_validation_http_code = custom_response_validation_http_code
470469

471-
# _request_param_name caches the name of any Request-typed parameter in the handler (None = "not found").
472-
# _request_param_checked avoids re-scanning the signature on every invocation.
473-
self._request_param_name: str | None = None
474-
self._request_param_name_checked: bool = False
470+
# Caches the name of any Request-typed parameter in the handler.
471+
# Avoids re-scanning the signature on every invocation.
472+
self.request_param_name: str | None = None
473+
self.request_param_name_checked: bool = False
475474

476475
def __call__(
477476
self,
@@ -1638,16 +1637,23 @@ def my_middleware(app, next_middleware):
16381637
return next_middleware(app)
16391638
```
16401639
"""
1640+
cached: Request | None = self.context.get("_request")
1641+
if cached is not None:
1642+
return cached
1643+
16411644
route: Route | None = self.context.get("_route")
16421645
if route is None:
16431646
raise RuntimeError(
16441647
"app.request is only available after route resolution. Use it inside middleware or a route handler.",
16451648
)
1646-
return Request(
1649+
1650+
request = Request(
16471651
route_path=route.openapi_path,
16481652
path_parameters=self.context.get("_route_args", {}),
16491653
current_event=self.current_event,
16501654
)
1655+
self.context["_request"] = request
1656+
return request
16511657

16521658

16531659
class MiddlewareFrame:
@@ -1723,10 +1729,12 @@ def __call__(self, app: ApiGatewayResolver) -> dict | tuple | Response:
17231729

17241730
def _find_request_param_name(func: Callable) -> str | None:
17251731
"""Return the name of the first parameter annotated as ``Request``, or ``None``."""
1732+
from typing import get_type_hints
1733+
17261734
try:
17271735
# get_type_hints resolves string annotations from ``from __future__ import annotations``
1728-
# using the function's own module globals — no pydantic dependency required.
1729-
hints = typing.get_type_hints(func)
1736+
# using the function's own module globals.
1737+
hints = get_type_hints(func)
17301738
except Exception:
17311739
hints = {}
17321740

@@ -1770,11 +1778,11 @@ def _registered_api_adapter(
17701778
# Lookup is cached on the Route object to avoid repeated signature inspection.
17711779
route: Route | None = app.context.get("_route")
17721780
if route is not None:
1773-
if not route._request_param_name_checked:
1774-
route._request_param_name = _find_request_param_name(next_middleware)
1775-
route._request_param_name_checked = True
1776-
if route._request_param_name:
1777-
route_args = {**route_args, route._request_param_name: app.request}
1781+
if not route.request_param_name_checked:
1782+
route.request_param_name = _find_request_param_name(next_middleware)
1783+
route.request_param_name_checked = True
1784+
if route.request_param_name:
1785+
route_args = {**route_args, route.request_param_name: app.request}
17781786

17791787
return app._to_response(next_middleware(**route_args))
17801788

tests/functional/event_handler/required_dependencies/test_request.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,74 @@ def put_application(application_id: str):
515515
assert req.route == "/applications/{application_id}"
516516
assert req.path_parameters == {"application_id": "4da715ee-79d4-4e52-81cb-1ecc464708fb"}
517517
assert req.method == "PUT"
518+
519+
520+
# ---------------------------------------------------------------------------
521+
# Missing coverage: json_body, query_parameters=None, request caching
522+
# ---------------------------------------------------------------------------
523+
524+
525+
def test_request_json_body_in_middleware():
526+
app = APIGatewayRestResolver()
527+
bodies_seen: list = []
528+
529+
def mw(app: APIGatewayRestResolver, next_middleware):
530+
bodies_seen.append(app.request.json_body)
531+
return next_middleware(app)
532+
533+
app.use(middlewares=[mw])
534+
535+
@app.post("/items")
536+
def handler():
537+
return {}
538+
539+
event = _make_rest_event("/items", method="POST", body='{"name": "widget"}')
540+
app(event, {})
541+
542+
assert bodies_seen == [{"name": "widget"}]
543+
544+
545+
def test_request_query_parameters_empty():
546+
"""When no query string parameters are present, query_parameters returns empty or None."""
547+
app = APIGatewayRestResolver()
548+
captured: list = []
549+
550+
def mw(app: APIGatewayRestResolver, next_middleware):
551+
captured.append(app.request.query_parameters)
552+
return next_middleware(app)
553+
554+
app.use(middlewares=[mw])
555+
556+
@app.get("/my/path")
557+
def handler():
558+
return {}
559+
560+
event = _make_rest_event("/my/path")
561+
app(event, {})
562+
563+
# No query params present — should be falsy (empty dict or None depending on event source)
564+
assert not captured[0]
565+
566+
567+
def test_request_is_cached_across_multiple_accesses():
568+
"""Accessing app.request multiple times in the same invocation returns the same object."""
569+
app = APIGatewayRestResolver()
570+
ids_seen: list[int] = []
571+
572+
def mw(app: APIGatewayRestResolver, next_middleware):
573+
ids_seen.append(id(app.request))
574+
ids_seen.append(id(app.request))
575+
return next_middleware(app)
576+
577+
app.use(middlewares=[mw])
578+
579+
@app.get("/my/path")
580+
def handler(request: Request):
581+
ids_seen.append(id(request))
582+
return {}
583+
584+
app(API_REST_EVENT, {})
585+
586+
# All accesses should return the same cached instance
587+
assert len(ids_seen) == 3
588+
assert ids_seen[0] == ids_seen[1] == ids_seen[2]

0 commit comments

Comments
 (0)