aws-lambda-powertools 3.10.1a1__py3-none-any.whl → 3.10.1a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_lambda_powertools/event_handler/api_gateway.py +98 -6
- aws_lambda_powertools/event_handler/bedrock_agent.py +11 -0
- aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +11 -1
- aws_lambda_powertools/event_handler/openapi/exceptions.py +3 -2
- aws_lambda_powertools/event_handler/openapi/types.py +12 -0
- aws_lambda_powertools/shared/version.py +1 -1
- {aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/METADATA +1 -1
- {aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/RECORD +10 -10
- {aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/WHEEL +0 -0
@@ -35,6 +35,7 @@ from aws_lambda_powertools.event_handler.openapi.types import (
|
|
35
35
|
OpenAPIResponse,
|
36
36
|
OpenAPIResponseContentModel,
|
37
37
|
OpenAPIResponseContentSchema,
|
38
|
+
response_validation_error_response_definition,
|
38
39
|
validation_error_definition,
|
39
40
|
validation_error_response_definition,
|
40
41
|
)
|
@@ -319,6 +320,7 @@ class Route:
|
|
319
320
|
security: list[dict[str, list[str]]] | None = None,
|
320
321
|
openapi_extensions: dict[str, Any] | None = None,
|
321
322
|
deprecated: bool = False,
|
323
|
+
custom_response_validation_http_code: HTTPStatus | None = None,
|
322
324
|
middlewares: list[Callable[..., Response]] | None = None,
|
323
325
|
):
|
324
326
|
"""
|
@@ -360,11 +362,13 @@ class Route:
|
|
360
362
|
Additional OpenAPI extensions as a dictionary.
|
361
363
|
deprecated: bool
|
362
364
|
Whether or not to mark this route as deprecated in the OpenAPI schema
|
365
|
+
custom_response_validation_http_code: int | HTTPStatus | None, optional
|
366
|
+
Whether to have custom http status code for this route if response validation fails
|
363
367
|
middlewares: list[Callable[..., Response]] | None
|
364
368
|
The list of route middlewares to be called in order.
|
365
369
|
"""
|
366
370
|
self.method = method.upper()
|
367
|
-
self.path =
|
371
|
+
self.path = path if path.strip() else "/"
|
368
372
|
|
369
373
|
# OpenAPI spec only understands paths with { }. So we'll have to convert Powertools' < >.
|
370
374
|
# https://swagger.io/specification/#path-templating
|
@@ -397,6 +401,8 @@ class Route:
|
|
397
401
|
# _body_field is used to cache the dependant model for the body field
|
398
402
|
self._body_field: ModelField | None = None
|
399
403
|
|
404
|
+
self.custom_response_validation_http_code = custom_response_validation_http_code
|
405
|
+
|
400
406
|
def __call__(
|
401
407
|
self,
|
402
408
|
router_middlewares: list[Callable],
|
@@ -565,6 +571,16 @@ class Route:
|
|
565
571
|
},
|
566
572
|
}
|
567
573
|
|
574
|
+
# Add custom response validation response, if exists
|
575
|
+
if self.custom_response_validation_http_code:
|
576
|
+
http_code = self.custom_response_validation_http_code.value
|
577
|
+
operation_responses[http_code] = {
|
578
|
+
"description": "Response Validation Error",
|
579
|
+
"content": {"application/json": {"schema": {"$ref": f"{COMPONENT_REF_PREFIX}ResponseValidationError"}}},
|
580
|
+
}
|
581
|
+
# Add model definition
|
582
|
+
definitions["ResponseValidationError"] = response_validation_error_response_definition
|
583
|
+
|
568
584
|
# Add the response to the OpenAPI operation
|
569
585
|
if self.responses:
|
570
586
|
for status_code in list(self.responses):
|
@@ -942,6 +958,7 @@ class BaseRouter(ABC):
|
|
942
958
|
security: list[dict[str, list[str]]] | None = None,
|
943
959
|
openapi_extensions: dict[str, Any] | None = None,
|
944
960
|
deprecated: bool = False,
|
961
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
945
962
|
middlewares: list[Callable[..., Any]] | None = None,
|
946
963
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
947
964
|
raise NotImplementedError()
|
@@ -1003,6 +1020,7 @@ class BaseRouter(ABC):
|
|
1003
1020
|
security: list[dict[str, list[str]]] | None = None,
|
1004
1021
|
openapi_extensions: dict[str, Any] | None = None,
|
1005
1022
|
deprecated: bool = False,
|
1023
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1006
1024
|
middlewares: list[Callable[..., Any]] | None = None,
|
1007
1025
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1008
1026
|
"""Get route decorator with GET `method`
|
@@ -1043,6 +1061,7 @@ class BaseRouter(ABC):
|
|
1043
1061
|
security,
|
1044
1062
|
openapi_extensions,
|
1045
1063
|
deprecated,
|
1064
|
+
custom_response_validation_http_code,
|
1046
1065
|
middlewares,
|
1047
1066
|
)
|
1048
1067
|
|
@@ -1062,6 +1081,7 @@ class BaseRouter(ABC):
|
|
1062
1081
|
security: list[dict[str, list[str]]] | None = None,
|
1063
1082
|
openapi_extensions: dict[str, Any] | None = None,
|
1064
1083
|
deprecated: bool = False,
|
1084
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1065
1085
|
middlewares: list[Callable[..., Any]] | None = None,
|
1066
1086
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1067
1087
|
"""Post route decorator with POST `method`
|
@@ -1103,6 +1123,7 @@ class BaseRouter(ABC):
|
|
1103
1123
|
security,
|
1104
1124
|
openapi_extensions,
|
1105
1125
|
deprecated,
|
1126
|
+
custom_response_validation_http_code,
|
1106
1127
|
middlewares,
|
1107
1128
|
)
|
1108
1129
|
|
@@ -1122,6 +1143,7 @@ class BaseRouter(ABC):
|
|
1122
1143
|
security: list[dict[str, list[str]]] | None = None,
|
1123
1144
|
openapi_extensions: dict[str, Any] | None = None,
|
1124
1145
|
deprecated: bool = False,
|
1146
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1125
1147
|
middlewares: list[Callable[..., Any]] | None = None,
|
1126
1148
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1127
1149
|
"""Put route decorator with PUT `method`
|
@@ -1163,6 +1185,7 @@ class BaseRouter(ABC):
|
|
1163
1185
|
security,
|
1164
1186
|
openapi_extensions,
|
1165
1187
|
deprecated,
|
1188
|
+
custom_response_validation_http_code,
|
1166
1189
|
middlewares,
|
1167
1190
|
)
|
1168
1191
|
|
@@ -1182,6 +1205,7 @@ class BaseRouter(ABC):
|
|
1182
1205
|
security: list[dict[str, list[str]]] | None = None,
|
1183
1206
|
openapi_extensions: dict[str, Any] | None = None,
|
1184
1207
|
deprecated: bool = False,
|
1208
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1185
1209
|
middlewares: list[Callable[..., Any]] | None = None,
|
1186
1210
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1187
1211
|
"""Delete route decorator with DELETE `method`
|
@@ -1222,6 +1246,7 @@ class BaseRouter(ABC):
|
|
1222
1246
|
security,
|
1223
1247
|
openapi_extensions,
|
1224
1248
|
deprecated,
|
1249
|
+
custom_response_validation_http_code,
|
1225
1250
|
middlewares,
|
1226
1251
|
)
|
1227
1252
|
|
@@ -1241,6 +1266,7 @@ class BaseRouter(ABC):
|
|
1241
1266
|
security: list[dict[str, list[str]]] | None = None,
|
1242
1267
|
openapi_extensions: dict[str, Any] | None = None,
|
1243
1268
|
deprecated: bool = False,
|
1269
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1244
1270
|
middlewares: list[Callable] | None = None,
|
1245
1271
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1246
1272
|
"""Patch route decorator with PATCH `method`
|
@@ -1284,6 +1310,7 @@ class BaseRouter(ABC):
|
|
1284
1310
|
security,
|
1285
1311
|
openapi_extensions,
|
1286
1312
|
deprecated,
|
1313
|
+
custom_response_validation_http_code,
|
1287
1314
|
middlewares,
|
1288
1315
|
)
|
1289
1316
|
|
@@ -1303,6 +1330,7 @@ class BaseRouter(ABC):
|
|
1303
1330
|
security: list[dict[str, list[str]]] | None = None,
|
1304
1331
|
openapi_extensions: dict[str, Any] | None = None,
|
1305
1332
|
deprecated: bool = False,
|
1333
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
1306
1334
|
middlewares: list[Callable] | None = None,
|
1307
1335
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
1308
1336
|
"""Head route decorator with HEAD `method`
|
@@ -1345,6 +1373,7 @@ class BaseRouter(ABC):
|
|
1345
1373
|
security,
|
1346
1374
|
openapi_extensions,
|
1347
1375
|
deprecated,
|
1376
|
+
custom_response_validation_http_code,
|
1348
1377
|
middlewares,
|
1349
1378
|
)
|
1350
1379
|
|
@@ -1571,6 +1600,7 @@ class ApiGatewayResolver(BaseRouter):
|
|
1571
1600
|
response_validation_error_http_code: HTTPStatus | int | None,
|
1572
1601
|
enable_validation: bool,
|
1573
1602
|
) -> HTTPStatus:
|
1603
|
+
|
1574
1604
|
if response_validation_error_http_code and not enable_validation:
|
1575
1605
|
msg = "'response_validation_error_http_code' cannot be set when enable_validation is False."
|
1576
1606
|
raise ValueError(msg)
|
@@ -1588,6 +1618,33 @@ class ApiGatewayResolver(BaseRouter):
|
|
1588
1618
|
|
1589
1619
|
return response_validation_error_http_code or HTTPStatus.UNPROCESSABLE_ENTITY
|
1590
1620
|
|
1621
|
+
def _add_resolver_response_validation_error_response_to_route(
|
1622
|
+
self,
|
1623
|
+
route_openapi_path: tuple[dict[str, Any], dict[str, Any]],
|
1624
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
1625
|
+
"""Adds resolver response validation error response to route's operations."""
|
1626
|
+
path, path_definitions = route_openapi_path
|
1627
|
+
if self._has_response_validation_error and "ResponseValidationError" not in path_definitions:
|
1628
|
+
response_validation_error_response = {
|
1629
|
+
"description": "Response Validation Error",
|
1630
|
+
"content": {
|
1631
|
+
"application/json": {
|
1632
|
+
"schema": {"$ref": f"{COMPONENT_REF_PREFIX}ResponseValidationError"},
|
1633
|
+
},
|
1634
|
+
},
|
1635
|
+
}
|
1636
|
+
http_code = self._response_validation_error_http_code.value
|
1637
|
+
for operation in path.values():
|
1638
|
+
operation["responses"][http_code] = response_validation_error_response
|
1639
|
+
return path, path_definitions
|
1640
|
+
|
1641
|
+
def _generate_schemas(self, definitions: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
1642
|
+
schemas = {k: definitions[k] for k in sorted(definitions)}
|
1643
|
+
# add response validation error definition
|
1644
|
+
if self._response_validation_error_http_code:
|
1645
|
+
schemas.setdefault("ResponseValidationError", response_validation_error_response_definition)
|
1646
|
+
return schemas
|
1647
|
+
|
1591
1648
|
def get_openapi_schema(
|
1592
1649
|
self,
|
1593
1650
|
*,
|
@@ -1739,14 +1796,14 @@ class ApiGatewayResolver(BaseRouter):
|
|
1739
1796
|
field_mapping=field_mapping,
|
1740
1797
|
)
|
1741
1798
|
if result:
|
1742
|
-
path, path_definitions = result
|
1799
|
+
path, path_definitions = self._add_resolver_response_validation_error_response_to_route(result)
|
1743
1800
|
if path:
|
1744
1801
|
paths.setdefault(route.openapi_path, {}).update(path)
|
1745
1802
|
if path_definitions:
|
1746
1803
|
definitions.update(path_definitions)
|
1747
1804
|
|
1748
1805
|
if definitions:
|
1749
|
-
components["schemas"] =
|
1806
|
+
components["schemas"] = self._generate_schemas(definitions)
|
1750
1807
|
if security_schemes:
|
1751
1808
|
components["securitySchemes"] = security_schemes
|
1752
1809
|
if components:
|
@@ -2108,6 +2165,29 @@ class ApiGatewayResolver(BaseRouter):
|
|
2108
2165
|
body=body,
|
2109
2166
|
)
|
2110
2167
|
|
2168
|
+
def _validate_route_response_validation_error_http_code(
|
2169
|
+
self,
|
2170
|
+
custom_response_validation_http_code: int | HTTPStatus | None,
|
2171
|
+
) -> HTTPStatus | None:
|
2172
|
+
if custom_response_validation_http_code and not self._enable_validation:
|
2173
|
+
msg = (
|
2174
|
+
"'custom_response_validation_http_code' cannot be set for route when enable_validation is False "
|
2175
|
+
"on resolver."
|
2176
|
+
)
|
2177
|
+
raise ValueError(msg)
|
2178
|
+
|
2179
|
+
if (
|
2180
|
+
not isinstance(custom_response_validation_http_code, HTTPStatus)
|
2181
|
+
and custom_response_validation_http_code is not None
|
2182
|
+
):
|
2183
|
+
try:
|
2184
|
+
custom_response_validation_http_code = HTTPStatus(custom_response_validation_http_code)
|
2185
|
+
except ValueError:
|
2186
|
+
msg = f"'{custom_response_validation_http_code}' must be an integer representing an HTTP status code or an enum of type HTTPStatus." # noqa: E501
|
2187
|
+
raise ValueError(msg) from None
|
2188
|
+
|
2189
|
+
return custom_response_validation_http_code
|
2190
|
+
|
2111
2191
|
def route(
|
2112
2192
|
self,
|
2113
2193
|
rule: str,
|
@@ -2125,10 +2205,15 @@ class ApiGatewayResolver(BaseRouter):
|
|
2125
2205
|
security: list[dict[str, list[str]]] | None = None,
|
2126
2206
|
openapi_extensions: dict[str, Any] | None = None,
|
2127
2207
|
deprecated: bool = False,
|
2208
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
2128
2209
|
middlewares: list[Callable[..., Any]] | None = None,
|
2129
2210
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
2130
2211
|
"""Route decorator includes parameter `method`"""
|
2131
2212
|
|
2213
|
+
custom_response_validation_http_code = self._validate_route_response_validation_error_http_code(
|
2214
|
+
custom_response_validation_http_code,
|
2215
|
+
)
|
2216
|
+
|
2132
2217
|
def register_resolver(func: AnyCallableT) -> AnyCallableT:
|
2133
2218
|
methods = (method,) if isinstance(method, str) else method
|
2134
2219
|
logger.debug(f"Adding route using rule {rule} and methods: {','.join(m.upper() for m in methods)}")
|
@@ -2154,6 +2239,7 @@ class ApiGatewayResolver(BaseRouter):
|
|
2154
2239
|
security,
|
2155
2240
|
openapi_extensions,
|
2156
2241
|
deprecated,
|
2242
|
+
custom_response_validation_http_code,
|
2157
2243
|
middlewares,
|
2158
2244
|
)
|
2159
2245
|
|
@@ -2523,15 +2609,17 @@ class ApiGatewayResolver(BaseRouter):
|
|
2523
2609
|
)
|
2524
2610
|
|
2525
2611
|
# OpenAPIValidationMiddleware will only raise ResponseValidationError when
|
2526
|
-
# 'self._response_validation_error_http_code' is not None
|
2612
|
+
# 'self._response_validation_error_http_code' is not None or
|
2613
|
+
# when route has custom_response_validation_http_code
|
2527
2614
|
if isinstance(exp, ResponseValidationError):
|
2528
|
-
|
2615
|
+
# route validation must take precedence over app validation
|
2616
|
+
http_code = route.custom_response_validation_http_code or self._response_validation_error_http_code
|
2529
2617
|
errors = [{"loc": e["loc"], "type": e["type"]} for e in exp.errors()]
|
2530
2618
|
return self._response_builder_class(
|
2531
2619
|
response=Response(
|
2532
2620
|
status_code=http_code.value,
|
2533
2621
|
content_type=content_types.APPLICATION_JSON,
|
2534
|
-
body={"statusCode":
|
2622
|
+
body={"statusCode": http_code, "detail": errors},
|
2535
2623
|
),
|
2536
2624
|
serializer=self._serializer,
|
2537
2625
|
route=route,
|
@@ -2682,6 +2770,7 @@ class Router(BaseRouter):
|
|
2682
2770
|
security: list[dict[str, list[str]]] | None = None,
|
2683
2771
|
openapi_extensions: dict[str, Any] | None = None,
|
2684
2772
|
deprecated: bool = False,
|
2773
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
2685
2774
|
middlewares: list[Callable[..., Any]] | None = None,
|
2686
2775
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
2687
2776
|
def register_route(func: AnyCallableT) -> AnyCallableT:
|
@@ -2708,6 +2797,7 @@ class Router(BaseRouter):
|
|
2708
2797
|
frozen_security,
|
2709
2798
|
frozen_openapi_extensions,
|
2710
2799
|
deprecated,
|
2800
|
+
custom_response_validation_http_code,
|
2711
2801
|
)
|
2712
2802
|
|
2713
2803
|
# Collate Middleware for routes
|
@@ -2794,6 +2884,7 @@ class APIGatewayRestResolver(ApiGatewayResolver):
|
|
2794
2884
|
security: list[dict[str, list[str]]] | None = None,
|
2795
2885
|
openapi_extensions: dict[str, Any] | None = None,
|
2796
2886
|
deprecated: bool = False,
|
2887
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
2797
2888
|
middlewares: list[Callable[..., Any]] | None = None,
|
2798
2889
|
) -> Callable[[AnyCallableT], AnyCallableT]:
|
2799
2890
|
# NOTE: see #1552 for more context.
|
@@ -2813,6 +2904,7 @@ class APIGatewayRestResolver(ApiGatewayResolver):
|
|
2813
2904
|
security,
|
2814
2905
|
openapi_extensions,
|
2815
2906
|
deprecated,
|
2907
|
+
custom_response_validation_http_code,
|
2816
2908
|
middlewares,
|
2817
2909
|
)
|
2818
2910
|
|
@@ -14,6 +14,7 @@ from aws_lambda_powertools.event_handler.api_gateway import (
|
|
14
14
|
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
|
15
15
|
|
16
16
|
if TYPE_CHECKING:
|
17
|
+
from http import HTTPStatus
|
17
18
|
from re import Match
|
18
19
|
|
19
20
|
from aws_lambda_powertools.event_handler.openapi.models import Contact, License, SecurityScheme, Server, Tag
|
@@ -109,6 +110,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
109
110
|
operation_id: str | None = None,
|
110
111
|
include_in_schema: bool = True,
|
111
112
|
deprecated: bool = False,
|
113
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
112
114
|
middlewares: list[Callable[..., Any]] | None = None,
|
113
115
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
114
116
|
openapi_extensions = None
|
@@ -129,6 +131,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
129
131
|
security,
|
130
132
|
openapi_extensions,
|
131
133
|
deprecated,
|
134
|
+
custom_response_validation_http_code,
|
132
135
|
middlewares,
|
133
136
|
)
|
134
137
|
|
@@ -148,6 +151,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
148
151
|
operation_id: str | None = None,
|
149
152
|
include_in_schema: bool = True,
|
150
153
|
deprecated: bool = False,
|
154
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
151
155
|
middlewares: list[Callable[..., Any]] | None = None,
|
152
156
|
):
|
153
157
|
openapi_extensions = None
|
@@ -168,6 +172,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
168
172
|
security,
|
169
173
|
openapi_extensions,
|
170
174
|
deprecated,
|
175
|
+
custom_response_validation_http_code,
|
171
176
|
middlewares,
|
172
177
|
)
|
173
178
|
|
@@ -187,6 +192,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
187
192
|
operation_id: str | None = None,
|
188
193
|
include_in_schema: bool = True,
|
189
194
|
deprecated: bool = False,
|
195
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
190
196
|
middlewares: list[Callable[..., Any]] | None = None,
|
191
197
|
):
|
192
198
|
openapi_extensions = None
|
@@ -207,6 +213,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
207
213
|
security,
|
208
214
|
openapi_extensions,
|
209
215
|
deprecated,
|
216
|
+
custom_response_validation_http_code,
|
210
217
|
middlewares,
|
211
218
|
)
|
212
219
|
|
@@ -226,6 +233,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
226
233
|
operation_id: str | None = None,
|
227
234
|
include_in_schema: bool = True,
|
228
235
|
deprecated: bool = False,
|
236
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
229
237
|
middlewares: list[Callable] | None = None,
|
230
238
|
):
|
231
239
|
openapi_extensions = None
|
@@ -246,6 +254,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
246
254
|
security,
|
247
255
|
openapi_extensions,
|
248
256
|
deprecated,
|
257
|
+
custom_response_validation_http_code,
|
249
258
|
middlewares,
|
250
259
|
)
|
251
260
|
|
@@ -265,6 +274,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
265
274
|
operation_id: str | None = None,
|
266
275
|
include_in_schema: bool = True,
|
267
276
|
deprecated: bool = False,
|
277
|
+
custom_response_validation_http_code: int | HTTPStatus | None = None,
|
268
278
|
middlewares: list[Callable[..., Any]] | None = None,
|
269
279
|
):
|
270
280
|
openapi_extensions = None
|
@@ -285,6 +295,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
285
295
|
security,
|
286
296
|
openapi_extensions,
|
287
297
|
deprecated,
|
298
|
+
custom_response_validation_http_code,
|
288
299
|
middlewares,
|
289
300
|
)
|
290
301
|
|
@@ -150,6 +150,7 @@ class OpenAPIValidationMiddleware(BaseMiddlewareHandler):
|
|
150
150
|
response.body = self._serialize_response(
|
151
151
|
field=route.dependant.return_param,
|
152
152
|
response_content=response.body,
|
153
|
+
has_route_custom_response_validation=route.custom_response_validation_http_code is not None,
|
153
154
|
)
|
154
155
|
|
155
156
|
return response
|
@@ -165,6 +166,7 @@ class OpenAPIValidationMiddleware(BaseMiddlewareHandler):
|
|
165
166
|
exclude_unset: bool = False,
|
166
167
|
exclude_defaults: bool = False,
|
167
168
|
exclude_none: bool = False,
|
169
|
+
has_route_custom_response_validation: bool = False,
|
168
170
|
) -> Any:
|
169
171
|
"""
|
170
172
|
Serialize the response content according to the field type.
|
@@ -173,8 +175,16 @@ class OpenAPIValidationMiddleware(BaseMiddlewareHandler):
|
|
173
175
|
errors: list[dict[str, Any]] = []
|
174
176
|
value = _validate_field(field=field, value=response_content, loc=("response",), existing_errors=errors)
|
175
177
|
if errors:
|
178
|
+
# route-level validation must take precedence over app-level
|
179
|
+
if has_route_custom_response_validation:
|
180
|
+
raise ResponseValidationError(
|
181
|
+
errors=_normalize_errors(errors),
|
182
|
+
body=response_content,
|
183
|
+
source="route",
|
184
|
+
)
|
176
185
|
if self._has_response_validation_error:
|
177
|
-
raise ResponseValidationError(errors=_normalize_errors(errors), body=response_content)
|
186
|
+
raise ResponseValidationError(errors=_normalize_errors(errors), body=response_content, source="app")
|
187
|
+
|
178
188
|
raise RequestValidationError(errors=_normalize_errors(errors), body=response_content)
|
179
189
|
|
180
190
|
if hasattr(field, "serialize"):
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any, Sequence
|
1
|
+
from typing import Any, Literal, Sequence
|
2
2
|
|
3
3
|
|
4
4
|
class ValidationException(Exception):
|
@@ -28,9 +28,10 @@ class ResponseValidationError(ValidationException):
|
|
28
28
|
Raised when the response body does not match the OpenAPI schema
|
29
29
|
"""
|
30
30
|
|
31
|
-
def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
|
31
|
+
def __init__(self, errors: Sequence[Any], *, body: Any = None, source: Literal["route", "app"] = "app") -> None:
|
32
32
|
super().__init__(errors)
|
33
33
|
self.body = body
|
34
|
+
self.source = source
|
34
35
|
|
35
36
|
|
36
37
|
class SerializationError(Exception):
|
@@ -49,6 +49,18 @@ validation_error_response_definition = {
|
|
49
49
|
},
|
50
50
|
}
|
51
51
|
|
52
|
+
response_validation_error_response_definition = {
|
53
|
+
"title": "ResponseValidationError",
|
54
|
+
"type": "object",
|
55
|
+
"properties": {
|
56
|
+
"detail": {
|
57
|
+
"title": "Detail",
|
58
|
+
"type": "array",
|
59
|
+
"items": {"$ref": f"{COMPONENT_REF_PREFIX}ValidationError"},
|
60
|
+
},
|
61
|
+
},
|
62
|
+
}
|
63
|
+
|
52
64
|
|
53
65
|
class OpenAPIResponseContentSchema(TypedDict, total=False):
|
54
66
|
schema: dict
|
{aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: aws_lambda_powertools
|
3
|
-
Version: 3.10.
|
3
|
+
Version: 3.10.1a2
|
4
4
|
Summary: Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity.
|
5
5
|
License: MIT
|
6
6
|
Keywords: aws_lambda_powertools,aws,tracing,logging,lambda,powertools,feature_flags,idempotency,middleware
|
{aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/RECORD
RENAMED
@@ -1,8 +1,8 @@
|
|
1
1
|
aws_lambda_powertools/__init__.py,sha256=o4iEHU0MfWC0_TfVmisxi0VOAUw5uQfqLQWr0t29ZaE,676
|
2
2
|
aws_lambda_powertools/event_handler/__init__.py,sha256=RM4TF62aonr60nVlq4V8ogfjef8RtpUUGuDUfZY34_w,901
|
3
|
-
aws_lambda_powertools/event_handler/api_gateway.py,sha256=
|
3
|
+
aws_lambda_powertools/event_handler/api_gateway.py,sha256=tV61EQ-Jd0Dzgaoy-JSW8mJiKeuDmst2eTAuWzY76sQ,119688
|
4
4
|
aws_lambda_powertools/event_handler/appsync.py,sha256=mnuSkA9NhszX9naIvCveI5ivnJO5vbY7FPBIVWvg3S8,19209
|
5
|
-
aws_lambda_powertools/event_handler/bedrock_agent.py,sha256=
|
5
|
+
aws_lambda_powertools/event_handler/bedrock_agent.py,sha256=oehV8h8cvIMUxbHah-NZNqzFtpvrgUGwH20aisfz9OY,14445
|
6
6
|
aws_lambda_powertools/event_handler/content_types.py,sha256=0MKsKNu-SSrxbULVKnUjwgK-lVXhVD7BBjZ4Js0kEsI,163
|
7
7
|
aws_lambda_powertools/event_handler/exceptions.py,sha256=trhn73GD_9pElEKWts6uQcOE5R7sBKD3uDf64ijHRgY,1150
|
8
8
|
aws_lambda_powertools/event_handler/graphql_appsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -13,7 +13,7 @@ aws_lambda_powertools/event_handler/graphql_appsync/router.py,sha256=f6jFQ3ZbJz9
|
|
13
13
|
aws_lambda_powertools/event_handler/lambda_function_url.py,sha256=tVe95cgThUTlqlfwM6tri2hC-sZxeEeIRoAGKHjFEd4,2312
|
14
14
|
aws_lambda_powertools/event_handler/middlewares/__init__.py,sha256=3R5XptoCT8owm4swcAEG0lsV_zbL4X-gU5nv8eJ0jQs,158
|
15
15
|
aws_lambda_powertools/event_handler/middlewares/base.py,sha256=llr1_sGaAsyMY9Gn4zKUDxePQ5X7ClcqgjqPesg5FJw,3724
|
16
|
-
aws_lambda_powertools/event_handler/middlewares/openapi_validation.py,sha256=
|
16
|
+
aws_lambda_powertools/event_handler/middlewares/openapi_validation.py,sha256=yrwUNVt2d-o7XKU6efyzwHOlkjgjWuqethZCkLLyyWI,16092
|
17
17
|
aws_lambda_powertools/event_handler/middlewares/schema_validation.py,sha256=gEX0lgO8e7sxNQZwFbhK3ExQyb_b_Fw3l6k5SeZyHqk,5204
|
18
18
|
aws_lambda_powertools/event_handler/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
aws_lambda_powertools/event_handler/openapi/compat.py,sha256=elvYmcsKx9TjRnTAief8Xfpd5bgKADoM-9BlQ4Q37iY,10877
|
@@ -21,7 +21,7 @@ aws_lambda_powertools/event_handler/openapi/config.py,sha256=FFz5L42FbwgReUjxgh6
|
|
21
21
|
aws_lambda_powertools/event_handler/openapi/constants.py,sha256=iGEfmNa8c0nQ_RT4b6qyXJqKS-50zXj8QoVLI60Qmew,129
|
22
22
|
aws_lambda_powertools/event_handler/openapi/dependant.py,sha256=KaLyOpDpZUhQPfAXq2K5lcteqxRcDz5uTQNoQVSYIxk,12522
|
23
23
|
aws_lambda_powertools/event_handler/openapi/encoders.py,sha256=ItcAMMdNKDmOPXFvFQGiwxMOD1o-XX815d864mVGrMY,11695
|
24
|
-
aws_lambda_powertools/event_handler/openapi/exceptions.py,sha256=
|
24
|
+
aws_lambda_powertools/event_handler/openapi/exceptions.py,sha256=SIVZqUP9gjIWhvE_woWCKlG8FOiRHx2CX_kTjBqasEQ,1129
|
25
25
|
aws_lambda_powertools/event_handler/openapi/models.py,sha256=pDkWqvbzF3dw1VQkCPbqylp5MFWJKdXj0pOyxI8f4WA,17101
|
26
26
|
aws_lambda_powertools/event_handler/openapi/params.py,sha256=Eq4msH-BOUT8BHpjNyZBnU5llG1shTNS1IBCntbbsS8,42518
|
27
27
|
aws_lambda_powertools/event_handler/openapi/pydantic_loader.py,sha256=Bud_yzI_Cao7UN_9zx6NeP0H2rQOZJbdbDAjgtthB-g,217
|
@@ -30,7 +30,7 @@ aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py,sha256=83f5h3j1E-
|
|
30
30
|
aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py,sha256=YEG3ICbwtArNrUYWZARcD4npULajyID5odB3VZ5zpe8,5561
|
31
31
|
aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui-bundle.min.js,sha256=s_tWqUyVGy4dmU9BppKMNDoKgC9Q9qzvnB3XJEtGEQk,1265807
|
32
32
|
aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui.min.css,sha256=Q0DBjhDAqyjE6KmzTrq1DGYU1UeF7aHQSuTiou_pLS0,151723
|
33
|
-
aws_lambda_powertools/event_handler/openapi/types.py,sha256=
|
33
|
+
aws_lambda_powertools/event_handler/openapi/types.py,sha256=CHl7FvjCPUCHwDcdp8soW8d2Y8QcsNkiCKlLHtL51uE,2077
|
34
34
|
aws_lambda_powertools/event_handler/router.py,sha256=cxL3MtN2Q8_RkPndNtAAcjJSqDJ4WN02dro_O83nxAM,980
|
35
35
|
aws_lambda_powertools/event_handler/types.py,sha256=yppbl_2wdyXSK_oTkSnkwPdAlJibc-uTm1IWmlB1RfU,177
|
36
36
|
aws_lambda_powertools/event_handler/util.py,sha256=j7InZnSXymsWmp2Gj2emnVJjFcKo4IicZNaOLcQEJn8,3164
|
@@ -88,7 +88,7 @@ aws_lambda_powertools/shared/json_encoder.py,sha256=JQeWNu-4M7_xI_hqYExrxsb3OcEH
|
|
88
88
|
aws_lambda_powertools/shared/lazy_import.py,sha256=TbXQm2bcwXdZrYdBaJJXIswyLlumM85RJ_A_0w-h-GU,2019
|
89
89
|
aws_lambda_powertools/shared/types.py,sha256=APkI38HbiTpSF19NSNii8Ydx73vmVUVotgEQ9jHruEI,124
|
90
90
|
aws_lambda_powertools/shared/user_agent.py,sha256=DrCMFQuT4a4iIrpcWpAIjY37EFqR9-QxlxDGD-Nn9Gg,7081
|
91
|
-
aws_lambda_powertools/shared/version.py,sha256=
|
91
|
+
aws_lambda_powertools/shared/version.py,sha256=5hYGxRSShhvON_hMWaLIQa6We3FWztE3q__RgIXVLrg,85
|
92
92
|
aws_lambda_powertools/tracing/__init__.py,sha256=f4bMThOPBPWTPVcYqcAIErAJPerMsf3H_Z4gCXCsK9I,141
|
93
93
|
aws_lambda_powertools/tracing/base.py,sha256=DbLD8OSK05KLdSV36oNA5wDSGv8KbcOD19qMUqoXh58,4513
|
94
94
|
aws_lambda_powertools/tracing/extensions.py,sha256=APOfXOq-hRBKaK5WyfIyrd_6M1_9SWJZ3zxLA9jDZzU,492
|
@@ -258,7 +258,7 @@ aws_lambda_powertools/utilities/validation/envelopes.py,sha256=YD5HOFx6IClQgii0n
|
|
258
258
|
aws_lambda_powertools/utilities/validation/exceptions.py,sha256=PKy_19zQMBJGCMMFl-sMkcm-cc0v3zZBn_bhGE4wKNo,2084
|
259
259
|
aws_lambda_powertools/utilities/validation/validator.py,sha256=x_1qpuKJBuWpgNU-zCD3Di-vXrZfyUu7oA5RmjZjr84,10034
|
260
260
|
aws_lambda_powertools/warnings/__init__.py,sha256=vqDVeZz8wGtD8WGYNSkQE7AHwqtIrPGRxuoJR_BBnSs,1193
|
261
|
-
aws_lambda_powertools-3.10.
|
262
|
-
aws_lambda_powertools-3.10.
|
263
|
-
aws_lambda_powertools-3.10.
|
264
|
-
aws_lambda_powertools-3.10.
|
261
|
+
aws_lambda_powertools-3.10.1a2.dist-info/LICENSE,sha256=vMHS2eBgmwPUIMPb7LQ4p7ib_FPVQXarVjAasflrTwo,951
|
262
|
+
aws_lambda_powertools-3.10.1a2.dist-info/METADATA,sha256=tDj1QqTFhGJ8C4AW3UgeLYe5wz3rFQZ94gPUWJc79N0,11187
|
263
|
+
aws_lambda_powertools-3.10.1a2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
264
|
+
aws_lambda_powertools-3.10.1a2.dist-info/RECORD,,
|
{aws_lambda_powertools-3.10.1a1.dist-info → aws_lambda_powertools-3.10.1a2.dist-info}/LICENSE
RENAMED
File without changes
|
File without changes
|