aws-lambda-powertools 3.0.1a9__py3-none-any.whl → 3.0.1.dev1__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 +54 -227
- aws_lambda_powertools/event_handler/appsync.py +97 -343
- aws_lambda_powertools/event_handler/bedrock_agent.py +0 -121
- aws_lambda_powertools/event_handler/exceptions.py +1 -1
- aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +1 -1
- aws_lambda_powertools/event_handler/openapi/encoders.py +70 -94
- aws_lambda_powertools/event_handler/openapi/exceptions.py +0 -12
- aws_lambda_powertools/event_handler/openapi/models.py +6 -39
- aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +4 -1
- aws_lambda_powertools/event_handler/util.py +2 -55
- aws_lambda_powertools/logging/utils.py +5 -5
- aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +0 -1
- aws_lambda_powertools/shared/cookies.py +2 -2
- aws_lambda_powertools/shared/dynamodb_deserializer.py +0 -13
- aws_lambda_powertools/shared/version.py +1 -1
- aws_lambda_powertools/tracing/tracer.py +2 -4
- aws_lambda_powertools/utilities/batch/base.py +4 -13
- aws_lambda_powertools/utilities/data_classes/__init__.py +0 -4
- aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +1 -260
- aws_lambda_powertools/utilities/data_classes/kafka_event.py +2 -2
- aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +0 -7
- aws_lambda_powertools/utilities/data_masking/base.py +4 -4
- aws_lambda_powertools/utilities/feature_flags/appconfig.py +2 -21
- aws_lambda_powertools/utilities/idempotency/base.py +11 -8
- aws_lambda_powertools/utilities/idempotency/idempotency.py +2 -22
- aws_lambda_powertools/utilities/idempotency/persistence/base.py +1 -4
- aws_lambda_powertools/utilities/parameters/secrets.py +1 -1
- aws_lambda_powertools/utilities/parser/envelopes/base.py +6 -5
- aws_lambda_powertools/utilities/parser/functions.py +1 -40
- aws_lambda_powertools/utilities/parser/models/__init__.py +0 -6
- aws_lambda_powertools/utilities/parser/models/apigw.py +1 -12
- aws_lambda_powertools/utilities/parser/models/apigwv2.py +1 -7
- aws_lambda_powertools/utilities/parser/models/s3.py +2 -4
- aws_lambda_powertools/utilities/parser/parser.py +8 -8
- aws_lambda_powertools/utilities/validation/base.py +2 -27
- aws_lambda_powertools/utilities/validation/validator.py +4 -49
- {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/METADATA +2 -13
- {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/RECORD +40 -46
- aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
- aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +0 -78
- aws_lambda_powertools/event_handler/graphql_appsync/base.py +0 -160
- aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +0 -10
- aws_lambda_powertools/event_handler/graphql_appsync/router.py +0 -62
- aws_lambda_powertools/utilities/data_classes/code_deploy_lifecycle_hook_event.py +0 -13
- {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/WHEEL +0 -0
|
@@ -14,12 +14,10 @@ from http import HTTPStatus
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Mapping, Match, Pattern, Sequence, TypeVar, cast
|
|
16
16
|
|
|
17
|
-
from typing_extensions import override
|
|
18
|
-
|
|
19
17
|
from aws_lambda_powertools.event_handler import content_types
|
|
20
18
|
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
|
|
21
19
|
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
|
|
22
|
-
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
|
|
20
|
+
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
|
|
23
21
|
from aws_lambda_powertools.event_handler.openapi.types import (
|
|
24
22
|
COMPONENT_REF_PREFIX,
|
|
25
23
|
METHODS_WITH_BODY,
|
|
@@ -29,13 +27,7 @@ from aws_lambda_powertools.event_handler.openapi.types import (
|
|
|
29
27
|
validation_error_definition,
|
|
30
28
|
validation_error_response_definition,
|
|
31
29
|
)
|
|
32
|
-
from aws_lambda_powertools.event_handler.util import
|
|
33
|
-
_FrozenDict,
|
|
34
|
-
_FrozenListDict,
|
|
35
|
-
_validate_openapi_security_parameters,
|
|
36
|
-
extract_origin_header,
|
|
37
|
-
)
|
|
38
|
-
from aws_lambda_powertools.shared.cookies import Cookie
|
|
30
|
+
from aws_lambda_powertools.event_handler.util import _FrozenDict, extract_origin_header
|
|
39
31
|
from aws_lambda_powertools.shared.functions import powertools_dev_is_set
|
|
40
32
|
from aws_lambda_powertools.shared.json_encoder import Encoder
|
|
41
33
|
from aws_lambda_powertools.utilities.data_classes import (
|
|
@@ -104,22 +96,20 @@ class CORSConfig:
|
|
|
104
96
|
Examples
|
|
105
97
|
--------
|
|
106
98
|
|
|
107
|
-
Simple
|
|
99
|
+
Simple cors example using the default permissive cors, not this should only be used during early prototyping
|
|
108
100
|
|
|
109
101
|
```python
|
|
110
|
-
from aws_lambda_powertools.event_handler
|
|
111
|
-
APIGatewayRestResolver, CORSConfig
|
|
112
|
-
)
|
|
102
|
+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
|
|
113
103
|
|
|
114
|
-
app = APIGatewayRestResolver(
|
|
104
|
+
app = APIGatewayRestResolver()
|
|
115
105
|
|
|
116
|
-
@app.get("/my/path")
|
|
106
|
+
@app.get("/my/path", cors=True)
|
|
117
107
|
def with_cors():
|
|
118
108
|
return {"message": "Foo"}
|
|
119
109
|
```
|
|
120
110
|
|
|
121
111
|
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
|
|
122
|
-
do not include any
|
|
112
|
+
do not include any cors headers.
|
|
123
113
|
|
|
124
114
|
```python
|
|
125
115
|
from aws_lambda_powertools.event_handler.api_gateway import (
|
|
@@ -176,12 +166,9 @@ class CORSConfig:
|
|
|
176
166
|
allow_credentials: bool
|
|
177
167
|
A boolean value that sets the value of `Access-Control-Allow-Credentials`
|
|
178
168
|
"""
|
|
179
|
-
|
|
180
169
|
self._allowed_origins = [allow_origin]
|
|
181
|
-
|
|
182
170
|
if extra_origins:
|
|
183
171
|
self._allowed_origins.extend(extra_origins)
|
|
184
|
-
|
|
185
172
|
self.allow_headers = set(self._REQUIRED_HEADERS + (allow_headers or []))
|
|
186
173
|
self.expose_headers = expose_headers or []
|
|
187
174
|
self.max_age = max_age
|
|
@@ -202,42 +189,17 @@ class CORSConfig:
|
|
|
202
189
|
# The origin matched an allowed origin, so return the CORS headers
|
|
203
190
|
headers = {
|
|
204
191
|
"Access-Control-Allow-Origin": origin,
|
|
205
|
-
"Access-Control-Allow-Headers":
|
|
192
|
+
"Access-Control-Allow-Headers": ",".join(sorted(self.allow_headers)),
|
|
206
193
|
}
|
|
207
194
|
|
|
208
195
|
if self.expose_headers:
|
|
209
196
|
headers["Access-Control-Expose-Headers"] = ",".join(self.expose_headers)
|
|
210
197
|
if self.max_age is not None:
|
|
211
198
|
headers["Access-Control-Max-Age"] = str(self.max_age)
|
|
212
|
-
if
|
|
199
|
+
if self.allow_credentials is True:
|
|
213
200
|
headers["Access-Control-Allow-Credentials"] = "true"
|
|
214
201
|
return headers
|
|
215
202
|
|
|
216
|
-
def allowed_origin(self, extracted_origin: str) -> str | None:
|
|
217
|
-
if extracted_origin in self._allowed_origins:
|
|
218
|
-
return extracted_origin
|
|
219
|
-
if extracted_origin is not None and "*" in self._allowed_origins:
|
|
220
|
-
return "*"
|
|
221
|
-
|
|
222
|
-
return None
|
|
223
|
-
|
|
224
|
-
@staticmethod
|
|
225
|
-
def build_allow_methods(methods: set[str]) -> str:
|
|
226
|
-
"""Build sorted comma delimited methods for Access-Control-Allow-Methods header
|
|
227
|
-
|
|
228
|
-
Parameters
|
|
229
|
-
----------
|
|
230
|
-
methods : set[str]
|
|
231
|
-
Set of HTTP Methods
|
|
232
|
-
|
|
233
|
-
Returns
|
|
234
|
-
-------
|
|
235
|
-
set[str]
|
|
236
|
-
Formatted string with all HTTP Methods allowed for CORS e.g., `GET, OPTIONS`
|
|
237
|
-
|
|
238
|
-
"""
|
|
239
|
-
return ",".join(sorted(methods))
|
|
240
|
-
|
|
241
203
|
|
|
242
204
|
class Response(Generic[ResponseT]):
|
|
243
205
|
"""Response data class that provides greater control over what is returned from the proxy event"""
|
|
@@ -298,17 +260,16 @@ class Route:
|
|
|
298
260
|
func: Callable,
|
|
299
261
|
cors: bool,
|
|
300
262
|
compress: bool,
|
|
301
|
-
cache_control: str | None
|
|
302
|
-
summary: str | None
|
|
303
|
-
description: str | None
|
|
304
|
-
responses: dict[int, OpenAPIResponse] | None
|
|
305
|
-
response_description: str | None
|
|
306
|
-
tags: list[str] | None
|
|
307
|
-
operation_id: str | None
|
|
308
|
-
include_in_schema: bool
|
|
309
|
-
security: list[dict[str, list[str]]] | None
|
|
310
|
-
|
|
311
|
-
middlewares: list[Callable[..., Response]] | None = None,
|
|
263
|
+
cache_control: str | None,
|
|
264
|
+
summary: str | None,
|
|
265
|
+
description: str | None,
|
|
266
|
+
responses: dict[int, OpenAPIResponse] | None,
|
|
267
|
+
response_description: str | None,
|
|
268
|
+
tags: list[str] | None,
|
|
269
|
+
operation_id: str | None,
|
|
270
|
+
include_in_schema: bool,
|
|
271
|
+
security: list[dict[str, list[str]]] | None,
|
|
272
|
+
middlewares: list[Callable[..., Response]] | None,
|
|
312
273
|
):
|
|
313
274
|
"""
|
|
314
275
|
|
|
@@ -345,8 +306,6 @@ class Route:
|
|
|
345
306
|
Whether or not to include this route in the OpenAPI schema
|
|
346
307
|
security: list[dict[str, list[str]]], optional
|
|
347
308
|
The OpenAPI security for this route
|
|
348
|
-
openapi_extensions: dict[str, Any], optional
|
|
349
|
-
Additional OpenAPI extensions as a dictionary.
|
|
350
309
|
middlewares: list[Callable[..., Response]] | None
|
|
351
310
|
The list of route middlewares to be called in order.
|
|
352
311
|
"""
|
|
@@ -370,7 +329,6 @@ class Route:
|
|
|
370
329
|
self.tags = tags or []
|
|
371
330
|
self.include_in_schema = include_in_schema
|
|
372
331
|
self.security = security
|
|
373
|
-
self.openapi_extensions = openapi_extensions
|
|
374
332
|
self.middlewares = middlewares or []
|
|
375
333
|
self.operation_id = operation_id or self._generate_operation_id()
|
|
376
334
|
|
|
@@ -522,10 +480,6 @@ class Route:
|
|
|
522
480
|
if self.security:
|
|
523
481
|
operation["security"] = self.security
|
|
524
482
|
|
|
525
|
-
# Add OpenAPI extensions if present
|
|
526
|
-
if self.openapi_extensions:
|
|
527
|
-
operation.update(self.openapi_extensions)
|
|
528
|
-
|
|
529
483
|
# Add the parameters to the OpenAPI operation
|
|
530
484
|
if parameters:
|
|
531
485
|
all_parameters = {(param["in"], param["name"]): param for param in parameters}
|
|
@@ -808,10 +762,7 @@ class ResponseBuilder(Generic[ResponseEventT]):
|
|
|
808
762
|
def _add_cors(self, event: ResponseEventT, cors: CORSConfig):
|
|
809
763
|
"""Update headers to include the configured Access-Control headers"""
|
|
810
764
|
extracted_origin_header = extract_origin_header(event.resolved_headers_field)
|
|
811
|
-
|
|
812
|
-
origin = cors.allowed_origin(extracted_origin_header)
|
|
813
|
-
if origin is not None:
|
|
814
|
-
self.response.headers.update(cors.to_dict(origin))
|
|
765
|
+
self.response.headers.update(cors.to_dict(extracted_origin_header))
|
|
815
766
|
|
|
816
767
|
def _add_cache_control(self, cache_control: str):
|
|
817
768
|
"""Set the specified cache control headers for 200 http responses. For non-200 `no-cache` is used."""
|
|
@@ -922,7 +873,6 @@ class BaseRouter(ABC):
|
|
|
922
873
|
operation_id: str | None = None,
|
|
923
874
|
include_in_schema: bool = True,
|
|
924
875
|
security: list[dict[str, list[str]]] | None = None,
|
|
925
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
926
876
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
927
877
|
):
|
|
928
878
|
raise NotImplementedError()
|
|
@@ -982,7 +932,6 @@ class BaseRouter(ABC):
|
|
|
982
932
|
operation_id: str | None = None,
|
|
983
933
|
include_in_schema: bool = True,
|
|
984
934
|
security: list[dict[str, list[str]]] | None = None,
|
|
985
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
986
935
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
987
936
|
):
|
|
988
937
|
"""Get route decorator with GET `method`
|
|
@@ -1021,7 +970,6 @@ class BaseRouter(ABC):
|
|
|
1021
970
|
operation_id,
|
|
1022
971
|
include_in_schema,
|
|
1023
972
|
security,
|
|
1024
|
-
openapi_extensions,
|
|
1025
973
|
middlewares,
|
|
1026
974
|
)
|
|
1027
975
|
|
|
@@ -1039,7 +987,6 @@ class BaseRouter(ABC):
|
|
|
1039
987
|
operation_id: str | None = None,
|
|
1040
988
|
include_in_schema: bool = True,
|
|
1041
989
|
security: list[dict[str, list[str]]] | None = None,
|
|
1042
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1043
990
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
1044
991
|
):
|
|
1045
992
|
"""Post route decorator with POST `method`
|
|
@@ -1079,7 +1026,6 @@ class BaseRouter(ABC):
|
|
|
1079
1026
|
operation_id,
|
|
1080
1027
|
include_in_schema,
|
|
1081
1028
|
security,
|
|
1082
|
-
openapi_extensions,
|
|
1083
1029
|
middlewares,
|
|
1084
1030
|
)
|
|
1085
1031
|
|
|
@@ -1097,7 +1043,6 @@ class BaseRouter(ABC):
|
|
|
1097
1043
|
operation_id: str | None = None,
|
|
1098
1044
|
include_in_schema: bool = True,
|
|
1099
1045
|
security: list[dict[str, list[str]]] | None = None,
|
|
1100
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1101
1046
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
1102
1047
|
):
|
|
1103
1048
|
"""Put route decorator with PUT `method`
|
|
@@ -1137,7 +1082,6 @@ class BaseRouter(ABC):
|
|
|
1137
1082
|
operation_id,
|
|
1138
1083
|
include_in_schema,
|
|
1139
1084
|
security,
|
|
1140
|
-
openapi_extensions,
|
|
1141
1085
|
middlewares,
|
|
1142
1086
|
)
|
|
1143
1087
|
|
|
@@ -1155,7 +1099,6 @@ class BaseRouter(ABC):
|
|
|
1155
1099
|
operation_id: str | None = None,
|
|
1156
1100
|
include_in_schema: bool = True,
|
|
1157
1101
|
security: list[dict[str, list[str]]] | None = None,
|
|
1158
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1159
1102
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
1160
1103
|
):
|
|
1161
1104
|
"""Delete route decorator with DELETE `method`
|
|
@@ -1194,7 +1137,6 @@ class BaseRouter(ABC):
|
|
|
1194
1137
|
operation_id,
|
|
1195
1138
|
include_in_schema,
|
|
1196
1139
|
security,
|
|
1197
|
-
openapi_extensions,
|
|
1198
1140
|
middlewares,
|
|
1199
1141
|
)
|
|
1200
1142
|
|
|
@@ -1212,7 +1154,6 @@ class BaseRouter(ABC):
|
|
|
1212
1154
|
operation_id: str | None = None,
|
|
1213
1155
|
include_in_schema: bool = True,
|
|
1214
1156
|
security: list[dict[str, list[str]]] | None = None,
|
|
1215
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1216
1157
|
middlewares: list[Callable] | None = None,
|
|
1217
1158
|
):
|
|
1218
1159
|
"""Patch route decorator with PATCH `method`
|
|
@@ -1254,7 +1195,6 @@ class BaseRouter(ABC):
|
|
|
1254
1195
|
operation_id,
|
|
1255
1196
|
include_in_schema,
|
|
1256
1197
|
security,
|
|
1257
|
-
openapi_extensions,
|
|
1258
1198
|
middlewares,
|
|
1259
1199
|
)
|
|
1260
1200
|
|
|
@@ -1272,7 +1212,6 @@ class BaseRouter(ABC):
|
|
|
1272
1212
|
operation_id: str | None = None,
|
|
1273
1213
|
include_in_schema: bool = True,
|
|
1274
1214
|
security: list[dict[str, list[str]]] | None = None,
|
|
1275
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1276
1215
|
middlewares: list[Callable] | None = None,
|
|
1277
1216
|
):
|
|
1278
1217
|
"""Head route decorator with HEAD `method`
|
|
@@ -1313,7 +1252,6 @@ class BaseRouter(ABC):
|
|
|
1313
1252
|
operation_id,
|
|
1314
1253
|
include_in_schema,
|
|
1315
1254
|
security,
|
|
1316
|
-
openapi_extensions,
|
|
1317
1255
|
middlewares,
|
|
1318
1256
|
)
|
|
1319
1257
|
|
|
@@ -1434,6 +1372,7 @@ def _registered_api_adapter(app: ApiGatewayResolver, next_middleware: Callable[.
|
|
|
1434
1372
|
"""
|
|
1435
1373
|
route_args: dict = app.context.get("_route_args", {})
|
|
1436
1374
|
logger.debug(f"Calling API Route Handler: {route_args}")
|
|
1375
|
+
|
|
1437
1376
|
return app._to_response(next_middleware(**route_args))
|
|
1438
1377
|
|
|
1439
1378
|
|
|
@@ -1534,7 +1473,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1534
1473
|
license_info: License | None = None,
|
|
1535
1474
|
security_schemes: dict[str, SecurityScheme] | None = None,
|
|
1536
1475
|
security: list[dict[str, list[str]]] | None = None,
|
|
1537
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1538
1476
|
) -> OpenAPI:
|
|
1539
1477
|
"""
|
|
1540
1478
|
Returns the OpenAPI schema as a pydantic model.
|
|
@@ -1565,8 +1503,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1565
1503
|
A declaration of the security schemes available to be used in the specification.
|
|
1566
1504
|
security: list[dict[str, list[str]]], optional
|
|
1567
1505
|
A declaration of which security mechanisms are applied globally across the API.
|
|
1568
|
-
openapi_extensions: Dict[str, Any], optional
|
|
1569
|
-
Additional OpenAPI extensions as a dictionary.
|
|
1570
1506
|
|
|
1571
1507
|
Returns
|
|
1572
1508
|
-------
|
|
@@ -1599,15 +1535,11 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1599
1535
|
|
|
1600
1536
|
info.update({field: value for field, value in optional_fields.items() if value})
|
|
1601
1537
|
|
|
1602
|
-
if not isinstance(openapi_extensions, dict):
|
|
1603
|
-
openapi_extensions = {}
|
|
1604
|
-
|
|
1605
1538
|
output: dict[str, Any] = {
|
|
1606
1539
|
"openapi": openapi_version,
|
|
1607
1540
|
"info": info,
|
|
1608
1541
|
"servers": self._get_openapi_servers(servers),
|
|
1609
1542
|
"security": self._get_openapi_security(security, security_schemes),
|
|
1610
|
-
**openapi_extensions,
|
|
1611
1543
|
}
|
|
1612
1544
|
|
|
1613
1545
|
components: dict[str, dict[str, Any]] = {}
|
|
@@ -1628,16 +1560,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1628
1560
|
|
|
1629
1561
|
# Add routes to the OpenAPI schema
|
|
1630
1562
|
for route in all_routes:
|
|
1631
|
-
|
|
1632
|
-
if route.security and not _validate_openapi_security_parameters(
|
|
1633
|
-
security=route.security,
|
|
1634
|
-
security_schemes=security_schemes,
|
|
1635
|
-
):
|
|
1636
|
-
raise SchemaValidationError(
|
|
1637
|
-
"Security configuration was not found in security_schemas or security_schema was not defined. "
|
|
1638
|
-
"See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes",
|
|
1639
|
-
)
|
|
1640
|
-
|
|
1641
1563
|
if not route.include_in_schema:
|
|
1642
1564
|
continue
|
|
1643
1565
|
|
|
@@ -1683,11 +1605,12 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1683
1605
|
if not security:
|
|
1684
1606
|
return None
|
|
1685
1607
|
|
|
1686
|
-
if not
|
|
1687
|
-
raise
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1608
|
+
if not security_schemes:
|
|
1609
|
+
raise ValueError("security_schemes must be provided if security is provided")
|
|
1610
|
+
|
|
1611
|
+
# Check if all keys in security are present in the security_schemes
|
|
1612
|
+
if any(key not in security_schemes for sec in security for key in sec):
|
|
1613
|
+
raise ValueError("Some security schemes not found in security_schemes")
|
|
1691
1614
|
|
|
1692
1615
|
return security
|
|
1693
1616
|
|
|
@@ -1718,7 +1641,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1718
1641
|
license_info: License | None = None,
|
|
1719
1642
|
security_schemes: dict[str, SecurityScheme] | None = None,
|
|
1720
1643
|
security: list[dict[str, list[str]]] | None = None,
|
|
1721
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1722
1644
|
) -> str:
|
|
1723
1645
|
"""
|
|
1724
1646
|
Returns the OpenAPI schema as a JSON serializable dict
|
|
@@ -1749,8 +1671,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1749
1671
|
A declaration of the security schemes available to be used in the specification.
|
|
1750
1672
|
security: list[dict[str, list[str]]], optional
|
|
1751
1673
|
A declaration of which security mechanisms are applied globally across the API.
|
|
1752
|
-
openapi_extensions: Dict[str, Any], optional
|
|
1753
|
-
Additional OpenAPI extensions as a dictionary.
|
|
1754
1674
|
|
|
1755
1675
|
Returns
|
|
1756
1676
|
-------
|
|
@@ -1773,7 +1693,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1773
1693
|
license_info=license_info,
|
|
1774
1694
|
security_schemes=security_schemes,
|
|
1775
1695
|
security=security,
|
|
1776
|
-
openapi_extensions=openapi_extensions,
|
|
1777
1696
|
),
|
|
1778
1697
|
by_alias=True,
|
|
1779
1698
|
exclude_none=True,
|
|
@@ -1801,7 +1720,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1801
1720
|
security: list[dict[str, list[str]]] | None = None,
|
|
1802
1721
|
oauth2_config: OAuth2Config | None = None,
|
|
1803
1722
|
persist_authorization: bool = False,
|
|
1804
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1805
1723
|
):
|
|
1806
1724
|
"""
|
|
1807
1725
|
Returns the OpenAPI schema as a JSON serializable dict
|
|
@@ -1844,8 +1762,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1844
1762
|
The OAuth2 configuration for the Swagger UI.
|
|
1845
1763
|
persist_authorization: bool, optional
|
|
1846
1764
|
Whether to persist authorization data on browser close/refresh.
|
|
1847
|
-
openapi_extensions: dict[str, Any], optional
|
|
1848
|
-
Additional OpenAPI extensions as a dictionary.
|
|
1849
1765
|
"""
|
|
1850
1766
|
from aws_lambda_powertools.event_handler.openapi.compat import model_json
|
|
1851
1767
|
from aws_lambda_powertools.event_handler.openapi.models import Server
|
|
@@ -1895,7 +1811,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1895
1811
|
license_info=license_info,
|
|
1896
1812
|
security_schemes=security_schemes,
|
|
1897
1813
|
security=security,
|
|
1898
|
-
openapi_extensions=openapi_extensions,
|
|
1899
1814
|
)
|
|
1900
1815
|
|
|
1901
1816
|
# The .replace('</', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
|
|
@@ -1920,6 +1835,7 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1920
1835
|
|
|
1921
1836
|
body = generate_swagger_html(
|
|
1922
1837
|
escaped_spec,
|
|
1838
|
+
f"{base_path}{path}",
|
|
1923
1839
|
swagger_js,
|
|
1924
1840
|
swagger_css,
|
|
1925
1841
|
swagger_base_url,
|
|
@@ -1948,7 +1864,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1948
1864
|
operation_id: str | None = None,
|
|
1949
1865
|
include_in_schema: bool = True,
|
|
1950
1866
|
security: list[dict[str, list[str]]] | None = None,
|
|
1951
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
1952
1867
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
1953
1868
|
):
|
|
1954
1869
|
"""Route decorator includes parameter `method`"""
|
|
@@ -1976,7 +1891,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
1976
1891
|
operation_id,
|
|
1977
1892
|
include_in_schema,
|
|
1978
1893
|
security,
|
|
1979
|
-
openapi_extensions,
|
|
1980
1894
|
middlewares,
|
|
1981
1895
|
)
|
|
1982
1896
|
|
|
@@ -2002,36 +1916,6 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
2002
1916
|
def resolve(self, event, context) -> dict[str, Any]:
|
|
2003
1917
|
"""Resolves the response based on the provide event and decorator routes
|
|
2004
1918
|
|
|
2005
|
-
## Internals
|
|
2006
|
-
|
|
2007
|
-
Request processing chain is triggered by a Route object being called _(`_call_route` -> `__call__`)_:
|
|
2008
|
-
|
|
2009
|
-
1. **When a route is matched**
|
|
2010
|
-
1.1. Exception handlers _(if any exception bubbled up and caught)_
|
|
2011
|
-
1.2. Global middlewares _(before, and after on the way back)_
|
|
2012
|
-
1.3. Path level middleware _(before, and after on the way back)_
|
|
2013
|
-
1.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
|
|
2014
|
-
1.5. Run actual route
|
|
2015
|
-
2. **When a route is NOT matched**
|
|
2016
|
-
2.1. Exception handlers _(if any exception bubbled up and caught)_
|
|
2017
|
-
2.2. Global middlewares _(before, and after on the way back)_
|
|
2018
|
-
2.3. Path level middleware _(before, and after on the way back)_
|
|
2019
|
-
2.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
|
|
2020
|
-
2.5. Run 404 route handler
|
|
2021
|
-
3. **When a route is a pre-flight CORS (often not matched)**
|
|
2022
|
-
3.1. Exception handlers _(if any exception bubbled up and caught)_
|
|
2023
|
-
3.2. Global middlewares _(before, and after on the way back)_
|
|
2024
|
-
3.3. Path level middleware _(before, and after on the way back)_
|
|
2025
|
-
3.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
|
|
2026
|
-
3.5. Return 204 with appropriate CORS headers
|
|
2027
|
-
4. **When a route is matched with Data Validation enabled**
|
|
2028
|
-
4.1. Exception handlers _(if any exception bubbled up and caught)_
|
|
2029
|
-
4.2. Data Validation middleware _(before, and after on the way back)_
|
|
2030
|
-
4.3. Global middlewares _(before, and after on the way back)_
|
|
2031
|
-
4.4. Path level middleware _(before, and after on the way back)_
|
|
2032
|
-
4.5. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
|
|
2033
|
-
4.6. Run actual route
|
|
2034
|
-
|
|
2035
1919
|
Parameters
|
|
2036
1920
|
----------
|
|
2037
1921
|
event: dict[str, Any]
|
|
@@ -2155,9 +2039,7 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
2155
2039
|
method = self.current_event.http_method.upper()
|
|
2156
2040
|
path = self._remove_prefix(self.current_event.path)
|
|
2157
2041
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
for route in registered_routes:
|
|
2042
|
+
for route in self._static_routes + self._dynamic_routes:
|
|
2161
2043
|
if method != route.method:
|
|
2162
2044
|
continue
|
|
2163
2045
|
match_results: Match | None = route.rule.match(path)
|
|
@@ -2169,7 +2051,8 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
2169
2051
|
route_keys = self._convert_matches_into_route_keys(match_results)
|
|
2170
2052
|
return self._call_route(route, route_keys) # pass fn args
|
|
2171
2053
|
|
|
2172
|
-
|
|
2054
|
+
logger.debug(f"No match found for path {path} and method {method}")
|
|
2055
|
+
return self._not_found(method)
|
|
2173
2056
|
|
|
2174
2057
|
def _remove_prefix(self, path: str) -> str:
|
|
2175
2058
|
"""Remove the configured prefix from the path"""
|
|
@@ -2207,65 +2090,36 @@ class ApiGatewayResolver(BaseRouter):
|
|
|
2207
2090
|
|
|
2208
2091
|
return path.startswith(prefix + "/")
|
|
2209
2092
|
|
|
2210
|
-
def
|
|
2093
|
+
def _not_found(self, method: str) -> ResponseBuilder:
|
|
2211
2094
|
"""Called when no matching route was found and includes support for the cors preflight response"""
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
Response
|
|
2226
|
-
HTTP 404 response
|
|
2227
|
-
"""
|
|
2228
|
-
_headers: dict[str, Any] = {}
|
|
2229
|
-
|
|
2230
|
-
# Pre-flight request? Return immediately to avoid browser error
|
|
2231
|
-
if self._cors and method == "OPTIONS":
|
|
2232
|
-
logger.debug("Pre-flight request detected. Returning CORS with empty response")
|
|
2233
|
-
_headers["Access-Control-Allow-Methods"] = CORSConfig.build_allow_methods(self._cors_methods)
|
|
2234
|
-
|
|
2235
|
-
return Response(status_code=204, content_type=None, headers=_headers, body="")
|
|
2095
|
+
headers = {}
|
|
2096
|
+
if self._cors:
|
|
2097
|
+
logger.debug("CORS is enabled, updating headers.")
|
|
2098
|
+
extracted_origin_header = extract_origin_header(self.current_event.resolved_headers_field)
|
|
2099
|
+
headers.update(self._cors.to_dict(extracted_origin_header))
|
|
2100
|
+
|
|
2101
|
+
if method == "OPTIONS":
|
|
2102
|
+
logger.debug("Pre-flight request detected. Returning CORS with null response")
|
|
2103
|
+
headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods))
|
|
2104
|
+
return ResponseBuilder(
|
|
2105
|
+
response=Response(status_code=204, content_type=None, headers=headers, body=""),
|
|
2106
|
+
serializer=self._serializer,
|
|
2107
|
+
)
|
|
2236
2108
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
return custom_not_found_handler(NotFoundError())
|
|
2109
|
+
handler = self._lookup_exception_handler(NotFoundError)
|
|
2110
|
+
if handler:
|
|
2111
|
+
return self._response_builder_class(response=handler(NotFoundError()), serializer=self._serializer)
|
|
2241
2112
|
|
|
2242
|
-
|
|
2243
|
-
|
|
2113
|
+
return self._response_builder_class(
|
|
2114
|
+
response=Response(
|
|
2244
2115
|
status_code=HTTPStatus.NOT_FOUND.value,
|
|
2245
2116
|
content_type=content_types.APPLICATION_JSON,
|
|
2246
|
-
headers=
|
|
2117
|
+
headers=headers,
|
|
2247
2118
|
body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"},
|
|
2248
|
-
)
|
|
2249
|
-
|
|
2250
|
-
# We create a route to trigger entire request chain (middleware+exception handlers)
|
|
2251
|
-
route = Route(
|
|
2252
|
-
rule=self._compile_regex(r".*"),
|
|
2253
|
-
method=method,
|
|
2254
|
-
path=path,
|
|
2255
|
-
func=not_found_handler,
|
|
2256
|
-
cors=self._cors_enabled,
|
|
2257
|
-
compress=False,
|
|
2119
|
+
),
|
|
2120
|
+
serializer=self._serializer,
|
|
2258
2121
|
)
|
|
2259
2122
|
|
|
2260
|
-
# Add matched Route reference into the Resolver context
|
|
2261
|
-
self.append_context(_route=route, _path=path)
|
|
2262
|
-
|
|
2263
|
-
# Kick-off request chain:
|
|
2264
|
-
# -> exception_handlers()
|
|
2265
|
-
# --> middlewares()
|
|
2266
|
-
# ---> not_found_route()
|
|
2267
|
-
return self._call_route(route=route, route_arguments={})
|
|
2268
|
-
|
|
2269
2123
|
def _call_route(self, route: Route, route_arguments: dict[str, str]) -> ResponseBuilder:
|
|
2270
2124
|
"""Actually call the matching route with any provided keyword arguments."""
|
|
2271
2125
|
try:
|
|
@@ -2490,7 +2344,6 @@ class Router(BaseRouter):
|
|
|
2490
2344
|
operation_id: str | None = None,
|
|
2491
2345
|
include_in_schema: bool = True,
|
|
2492
2346
|
security: list[dict[str, list[str]]] | None = None,
|
|
2493
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
2494
2347
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
2495
2348
|
):
|
|
2496
2349
|
def register_route(func: Callable):
|
|
@@ -2498,8 +2351,6 @@ class Router(BaseRouter):
|
|
|
2498
2351
|
methods = (method,) if isinstance(method, str) else tuple(method)
|
|
2499
2352
|
frozen_responses = _FrozenDict(responses) if responses else None
|
|
2500
2353
|
frozen_tags = frozenset(tags) if tags else None
|
|
2501
|
-
frozen_security = _FrozenListDict(security) if security else None
|
|
2502
|
-
fronzen_openapi_extensions = _FrozenDict(openapi_extensions) if openapi_extensions else None
|
|
2503
2354
|
|
|
2504
2355
|
route_key = (
|
|
2505
2356
|
rule,
|
|
@@ -2514,8 +2365,7 @@ class Router(BaseRouter):
|
|
|
2514
2365
|
frozen_tags,
|
|
2515
2366
|
operation_id,
|
|
2516
2367
|
include_in_schema,
|
|
2517
|
-
|
|
2518
|
-
fronzen_openapi_extensions,
|
|
2368
|
+
security,
|
|
2519
2369
|
)
|
|
2520
2370
|
|
|
2521
2371
|
# Collate Middleware for routes
|
|
@@ -2596,7 +2446,6 @@ class APIGatewayRestResolver(ApiGatewayResolver):
|
|
|
2596
2446
|
operation_id: str | None = None,
|
|
2597
2447
|
include_in_schema: bool = True,
|
|
2598
2448
|
security: list[dict[str, list[str]]] | None = None,
|
|
2599
|
-
openapi_extensions: dict[str, Any] | None = None,
|
|
2600
2449
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
2601
2450
|
):
|
|
2602
2451
|
# NOTE: see #1552 for more context.
|
|
@@ -2614,7 +2463,6 @@ class APIGatewayRestResolver(ApiGatewayResolver):
|
|
|
2614
2463
|
operation_id,
|
|
2615
2464
|
include_in_schema,
|
|
2616
2465
|
security,
|
|
2617
|
-
openapi_extensions,
|
|
2618
2466
|
middlewares,
|
|
2619
2467
|
)
|
|
2620
2468
|
|
|
@@ -2676,24 +2524,3 @@ class ALBResolver(ApiGatewayResolver):
|
|
|
2676
2524
|
def _get_base_path(self) -> str:
|
|
2677
2525
|
# ALB doesn't have a stage variable, so we just return an empty string
|
|
2678
2526
|
return ""
|
|
2679
|
-
|
|
2680
|
-
@override
|
|
2681
|
-
def _to_response(self, result: dict | tuple | Response) -> Response:
|
|
2682
|
-
"""Convert the route's result to a Response
|
|
2683
|
-
|
|
2684
|
-
ALB requires a non-null body otherwise it converts as HTTP 5xx
|
|
2685
|
-
|
|
2686
|
-
3 main result types are supported:
|
|
2687
|
-
|
|
2688
|
-
- Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to
|
|
2689
|
-
application/json
|
|
2690
|
-
- Tuple[dict, int]: Same dict handling as above but with the option of including a status code
|
|
2691
|
-
- Response: returned as is, and allows for more flexibility
|
|
2692
|
-
"""
|
|
2693
|
-
|
|
2694
|
-
# NOTE: Minor override for early return on Response with null body for ALB
|
|
2695
|
-
if isinstance(result, Response) and result.body is None:
|
|
2696
|
-
logger.debug("ALB doesn't allow None responses; converting to empty string")
|
|
2697
|
-
result.body = ""
|
|
2698
|
-
|
|
2699
|
-
return super()._to_response(result)
|