Skip to content

Commit 368cbbc

Browse files
Merge branch 'develop' into fix/batch-processor-logger-injection
2 parents 54251b8 + f7239b8 commit 368cbbc

11 files changed

Lines changed: 421 additions & 250 deletions

File tree

.github/workflows/bootstrap_region.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
with:
5353
node-version: "22"
5454
- name: Setup dependencies
55-
uses: aws-powertools/actions/.github/actions/cached-node-modules@828e78a26eee3554dc2e1d96048004548fbb169f
55+
uses: aws-powertools/actions/.github/actions/cached-node-modules@4bf64a4072489399fbf39b78f558eeeed6767189
5656
- id: credentials
5757
name: AWS Credentials
5858
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b

aws_lambda_powertools/event_handler/bedrock_agent.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,17 @@ def lambda_handler(event, context):
9999

100100
current_event: BedrockAgentEvent
101101

102-
def __init__(self, debug: bool = False, enable_validation: bool = True):
102+
def __init__(
103+
self,
104+
debug: bool = False,
105+
enable_validation: bool = True,
106+
serializer: Callable[[dict], str] | None = None,
107+
):
103108
super().__init__(
104109
proxy_type=ProxyEventType.BedrockAgentEvent,
105110
cors=None,
106111
debug=debug,
107-
serializer=None,
112+
serializer=serializer,
108113
strip_prefixes=None,
109114
enable_validation=enable_validation,
110115
json_body_deserializer=None,

aws_lambda_powertools/event_handler/http_resolver.py

Lines changed: 6 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import base64
4-
import inspect
54
import warnings
65
from typing import TYPE_CHECKING, Any, Callable
76
from urllib.parse import parse_qs
@@ -10,10 +9,7 @@
109
ApiGatewayResolver,
1110
BaseRouter,
1211
ProxyEventType,
13-
Response,
14-
Route,
1512
)
16-
from aws_lambda_powertools.event_handler.middlewares.async_utils import wrap_middleware_async
1713
from aws_lambda_powertools.shared.headers_serializer import BaseHeadersSerializer
1814
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
1915

@@ -240,113 +236,13 @@ def _get_base_path(self) -> str:
240236
return ""
241237

242238
async def _resolve_async(self) -> dict: # type: ignore[override]
243-
"""Async version of resolve that supports async handlers."""
244-
method = self.current_event.http_method.upper()
245-
path = self._remove_prefix(self.current_event.path)
246-
247-
registered_routes = self._static_routes + self._dynamic_routes
248-
249-
for route in registered_routes:
250-
if method != route.method:
251-
continue
252-
match_results = route.rule.match(path)
253-
if match_results:
254-
self.append_context(_route=route, _path=path)
255-
route_keys = self._convert_matches_into_route_keys(match_results)
256-
return await self._call_route_async(route, route_keys)
257-
258-
# Handle not found
259-
return await self._handle_not_found_async()
260-
261-
async def _call_route_async(self, route: Route, route_arguments: dict[str, str]) -> dict: # type: ignore[override]
262-
"""Call route handler, supporting both sync and async handlers."""
263-
from aws_lambda_powertools.event_handler.api_gateway import ResponseBuilder
264-
265-
try:
266-
self._reset_processed_stack()
267-
268-
# Get the route args (may be modified by validation middleware)
269-
self.append_context(_route_args=route_arguments)
270-
271-
# Run middleware chain (sync for now, handlers can be async)
272-
response = await self._run_middleware_chain_async(route)
273-
274-
response_builder: ResponseBuilder = ResponseBuilder(
275-
response=response,
276-
serializer=self._serializer,
277-
route=route,
278-
)
279-
280-
return response_builder.build(self.current_event, self._cors)
281-
282-
except Exception as exc:
283-
exc_response_builder = self._call_exception_handler(exc, route)
284-
if exc_response_builder:
285-
return exc_response_builder.build(self.current_event, self._cors)
286-
raise
287-
288-
async def _run_middleware_chain_async(self, route: Route) -> Response:
289-
"""Run the middleware chain, awaiting async handlers."""
290-
# Build middleware list
291-
all_middlewares: list[Callable[..., Any]] = []
292-
293-
# Determine if validation should be enabled for this route
294-
# If route has explicit enable_validation setting, use it; otherwise, use resolver's global setting
295-
route_validation_enabled = (
296-
route.enable_validation if route.enable_validation is not None else self._enable_validation
297-
)
298-
299-
if route_validation_enabled and hasattr(self, "_request_validation_middleware"):
300-
all_middlewares.append(self._request_validation_middleware)
301-
302-
all_middlewares.extend(self._router_middlewares + route.middlewares)
303-
304-
if route_validation_enabled and hasattr(self, "_response_validation_middleware"):
305-
all_middlewares.append(self._response_validation_middleware)
306-
307-
# Create the final handler that calls the route function
308-
async def final_handler(app):
309-
route_args = app.context.get("_route_args", {})
310-
result = route.func(**route_args)
311-
312-
# Await if coroutine
313-
if inspect.iscoroutine(result):
314-
result = await result
315-
316-
return self._to_response(result)
317-
318-
# Build middleware chain from end to start
319-
next_handler = final_handler
320-
321-
for middleware in reversed(all_middlewares):
322-
next_handler = wrap_middleware_async(middleware, next_handler)
323-
324-
return await next_handler(self)
325-
326-
async def _handle_not_found_async(self, method: str = "", path: str = "") -> dict: # type: ignore[override]
327-
"""Handle 404 responses, using custom not_found handler if registered."""
328-
from http import HTTPStatus
329-
330-
from aws_lambda_powertools.event_handler.api_gateway import ResponseBuilder
331-
from aws_lambda_powertools.event_handler.exceptions import NotFoundError
332-
333-
# Check for custom not_found handler
334-
custom_not_found_handler = self.exception_handler_manager.lookup_exception_handler(NotFoundError)
335-
if custom_not_found_handler:
336-
response = custom_not_found_handler(NotFoundError())
337-
else:
338-
response = Response(
339-
status_code=HTTPStatus.NOT_FOUND.value,
340-
content_type="application/json",
341-
body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"},
342-
)
343-
344-
response_builder: ResponseBuilder = ResponseBuilder(
345-
response=response,
346-
serializer=self._serializer,
347-
route=None,
348-
)
239+
"""Thin async resolver: delegates entirely to the parent and serializes to dict.
349240
241+
The parent's _resolve_async handles route matching, CORS preflight, not-found
242+
logic, and exception handling. The only adaptation needed here is converting
243+
the returned ResponseBuilder into the dict format that asgi_handler expects.
244+
"""
245+
response_builder = await super()._resolve_async()
350246
return response_builder.build(self.current_event, self._cors)
351247

352248
async def asgi_handler(self, scope: dict, receive: Callable, send: Callable) -> None:

aws_lambda_powertools/utilities/data_classes/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def _str_helper(self) -> dict[str, Any]:
163163
def _properties(self) -> list[str]:
164164
return [p for p in dir(self.__class__) if isinstance(getattr(self.__class__, p), property)]
165165

166-
def get(self, key: str, default: Any | None = None) -> Any | None:
167-
return self._data.get(key, default)
166+
def get(self, key: object, default: Any | None = None) -> Any | None: # type: ignore[override]
167+
return self._data.get(str(key), default)
168168

169169
@property
170170
def raw_event(self) -> dict[str, Any]:

docs/core/event_handler/api_gateway.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,33 @@ Use `dependency_overrides` to replace any dependency with a mock or stub during
14591459
--8<-- "examples/event_handler_rest/src/dependency_injection_testing.py"
14601460
```
14611461

1462+
???+ warning "Import path must match exactly when using dependency_overrides"
1463+
When using `dependency_overrides` in tests, the imported dependency reference must use the **exact same import path** as the handler file. Python treats differently-imported modules as different objects in `sys.modules`, so the override will be silently ignored otherwise.
1464+
1465+
=== "✅ Correct"
1466+
1467+
```python
1468+
# handler.py
1469+
from depends import get_config
1470+
1471+
# test_handler.py - matches handler's import path exactly
1472+
from depends import get_config
1473+
1474+
app.dependency_overrides[get_config] = lambda: "test-value"
1475+
```
1476+
1477+
=== "❌ Wrong"
1478+
1479+
```python
1480+
# handler.py
1481+
from depends import get_config
1482+
1483+
# test_handler.py - different import path, override won't apply
1484+
from my_app.api_handler.depends import get_config
1485+
1486+
app.dependency_overrides[get_config] = lambda: "test-value"
1487+
```
1488+
14621489
???+ tip "Caching behavior"
14631490
By default, dependencies are cached within the same invocation (`use_cache=True`). If the same dependency is used by multiple handlers or sub-dependencies, it is resolved once and the result is reused. Use `Depends(fn, use_cache=False)` to resolve every time.
14641491

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "aws-lambda-powertools-python-e2e",
33
"version": "1.0.0",
44
"devDependencies": {
5-
"aws-cdk": "^2.1126.0"
5+
"aws-cdk": "^2.1127.0"
66
}
77
}

0 commit comments

Comments
 (0)