aws-lambda-powertools 3.0.1.dev2__py3-none-any.whl → 3.0.2.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 +227 -54
- aws_lambda_powertools/event_handler/appsync.py +343 -97
- aws_lambda_powertools/event_handler/bedrock_agent.py +121 -0
- aws_lambda_powertools/event_handler/exceptions.py +1 -1
- aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
- aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +78 -0
- aws_lambda_powertools/event_handler/graphql_appsync/base.py +160 -0
- aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +10 -0
- aws_lambda_powertools/event_handler/graphql_appsync/router.py +62 -0
- aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +1 -1
- aws_lambda_powertools/event_handler/openapi/encoders.py +94 -70
- aws_lambda_powertools/event_handler/openapi/exceptions.py +12 -0
- aws_lambda_powertools/event_handler/openapi/models.py +38 -5
- aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +1 -4
- aws_lambda_powertools/event_handler/util.py +55 -2
- aws_lambda_powertools/logging/utils.py +5 -5
- aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +1 -0
- aws_lambda_powertools/shared/cookies.py +2 -2
- aws_lambda_powertools/shared/dynamodb_deserializer.py +13 -0
- aws_lambda_powertools/shared/version.py +1 -1
- aws_lambda_powertools/tracing/tracer.py +4 -2
- aws_lambda_powertools/utilities/batch/base.py +13 -4
- aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +260 -1
- aws_lambda_powertools/utilities/data_classes/kafka_event.py +2 -2
- aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +7 -0
- aws_lambda_powertools/utilities/data_masking/base.py +4 -4
- aws_lambda_powertools/utilities/feature_flags/appconfig.py +21 -2
- aws_lambda_powertools/utilities/idempotency/idempotency.py +22 -2
- aws_lambda_powertools/utilities/idempotency/persistence/base.py +4 -1
- aws_lambda_powertools/utilities/parameters/secrets.py +1 -1
- aws_lambda_powertools/utilities/parser/models/__init__.py +6 -0
- aws_lambda_powertools/utilities/parser/models/apigw.py +12 -1
- aws_lambda_powertools/utilities/parser/models/apigwv2.py +7 -1
- aws_lambda_powertools/utilities/validation/base.py +27 -2
- aws_lambda_powertools/utilities/validation/validator.py +49 -4
- {aws_lambda_powertools-3.0.1.dev2.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/METADATA +12 -2
- {aws_lambda_powertools-3.0.1.dev2.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/RECORD +39 -34
- {aws_lambda_powertools-3.0.1.dev2.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.0.1.dev2.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/WHEEL +0 -0
|
@@ -19,6 +19,8 @@ from aws_lambda_powertools.event_handler.openapi.compat import _model_dump
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from aws_lambda_powertools.event_handler.openapi.types import IncEx
|
|
21
21
|
|
|
22
|
+
from aws_lambda_powertools.event_handler.openapi.exceptions import SerializationError
|
|
23
|
+
|
|
22
24
|
"""
|
|
23
25
|
This module contains the encoders used by jsonable_encoder to convert Python objects to JSON serializable data types.
|
|
24
26
|
"""
|
|
@@ -72,88 +74,98 @@ def jsonable_encoder( # noqa: PLR0911
|
|
|
72
74
|
if exclude is not None and not isinstance(exclude, (set, dict)):
|
|
73
75
|
exclude = set(exclude)
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
try:
|
|
78
|
+
# Pydantic models
|
|
79
|
+
if isinstance(obj, BaseModel):
|
|
80
|
+
return _dump_base_model(
|
|
81
|
+
obj=obj,
|
|
82
|
+
include=include,
|
|
83
|
+
exclude=exclude,
|
|
84
|
+
by_alias=by_alias,
|
|
85
|
+
exclude_unset=exclude_unset,
|
|
86
|
+
exclude_none=exclude_none,
|
|
87
|
+
exclude_defaults=exclude_defaults,
|
|
88
|
+
)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
# Dataclasses
|
|
91
|
+
if dataclasses.is_dataclass(obj):
|
|
92
|
+
obj_dict = dataclasses.asdict(obj) # type: ignore[call-overload]
|
|
93
|
+
return jsonable_encoder(
|
|
94
|
+
obj_dict,
|
|
95
|
+
include=include,
|
|
96
|
+
exclude=exclude,
|
|
97
|
+
by_alias=by_alias,
|
|
98
|
+
exclude_unset=exclude_unset,
|
|
99
|
+
exclude_defaults=exclude_defaults,
|
|
100
|
+
exclude_none=exclude_none,
|
|
101
|
+
custom_serializer=custom_serializer,
|
|
102
|
+
)
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
# Enums
|
|
105
|
+
if isinstance(obj, Enum):
|
|
106
|
+
return obj.value
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
# Paths
|
|
109
|
+
if isinstance(obj, PurePath):
|
|
110
|
+
return str(obj)
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
# Scalars
|
|
113
|
+
if isinstance(obj, (str, int, float, type(None))):
|
|
114
|
+
return obj
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
# Dictionaries
|
|
117
|
+
if isinstance(obj, dict):
|
|
118
|
+
return _dump_dict(
|
|
119
|
+
obj=obj,
|
|
120
|
+
include=include,
|
|
121
|
+
exclude=exclude,
|
|
122
|
+
by_alias=by_alias,
|
|
123
|
+
exclude_unset=exclude_unset,
|
|
124
|
+
exclude_none=exclude_none,
|
|
125
|
+
custom_serializer=custom_serializer,
|
|
126
|
+
)
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
# Sequences
|
|
129
|
+
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
|
|
130
|
+
return _dump_sequence(
|
|
131
|
+
obj=obj,
|
|
132
|
+
include=include,
|
|
133
|
+
exclude=exclude,
|
|
134
|
+
by_alias=by_alias,
|
|
135
|
+
exclude_none=exclude_none,
|
|
136
|
+
exclude_defaults=exclude_defaults,
|
|
137
|
+
exclude_unset=exclude_unset,
|
|
138
|
+
custom_serializer=custom_serializer,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Other types
|
|
142
|
+
if type(obj) in ENCODERS_BY_TYPE:
|
|
143
|
+
return ENCODERS_BY_TYPE[type(obj)](obj)
|
|
144
|
+
|
|
145
|
+
for encoder, classes_tuple in encoders_by_class_tuples.items():
|
|
146
|
+
if isinstance(obj, classes_tuple):
|
|
147
|
+
return encoder(obj)
|
|
148
|
+
|
|
149
|
+
# Use custom serializer if present
|
|
150
|
+
if custom_serializer:
|
|
151
|
+
return custom_serializer(obj)
|
|
152
|
+
|
|
153
|
+
# Default
|
|
154
|
+
return _dump_other(
|
|
126
155
|
obj=obj,
|
|
127
156
|
include=include,
|
|
128
157
|
exclude=exclude,
|
|
129
158
|
by_alias=by_alias,
|
|
130
159
|
exclude_none=exclude_none,
|
|
131
|
-
exclude_defaults=exclude_defaults,
|
|
132
160
|
exclude_unset=exclude_unset,
|
|
161
|
+
exclude_defaults=exclude_defaults,
|
|
162
|
+
custom_serializer=custom_serializer,
|
|
133
163
|
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
for encoder, classes_tuple in encoders_by_class_tuples.items():
|
|
140
|
-
if isinstance(obj, classes_tuple):
|
|
141
|
-
return encoder(obj)
|
|
142
|
-
|
|
143
|
-
# Use custom serializer if present
|
|
144
|
-
if custom_serializer:
|
|
145
|
-
return custom_serializer(obj)
|
|
146
|
-
|
|
147
|
-
# Default
|
|
148
|
-
return _dump_other(
|
|
149
|
-
obj=obj,
|
|
150
|
-
include=include,
|
|
151
|
-
exclude=exclude,
|
|
152
|
-
by_alias=by_alias,
|
|
153
|
-
exclude_none=exclude_none,
|
|
154
|
-
exclude_unset=exclude_unset,
|
|
155
|
-
exclude_defaults=exclude_defaults,
|
|
156
|
-
)
|
|
164
|
+
except ValueError as exc:
|
|
165
|
+
raise SerializationError(
|
|
166
|
+
f"Unable to serialize the object {obj} as it is not a supported type. Error details: {exc}",
|
|
167
|
+
"See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#serializing-objects",
|
|
168
|
+
) from exc
|
|
157
169
|
|
|
158
170
|
|
|
159
171
|
def _dump_base_model(
|
|
@@ -197,9 +209,15 @@ def _dump_dict(
|
|
|
197
209
|
by_alias: bool = True,
|
|
198
210
|
exclude_unset: bool = False,
|
|
199
211
|
exclude_none: bool = False,
|
|
212
|
+
custom_serializer: Callable[[Any], str] | None = None,
|
|
200
213
|
) -> dict[str, Any]:
|
|
201
214
|
"""
|
|
202
215
|
Dump a dict to a dict, using the same parameters as jsonable_encoder
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
custom_serializer : Callable, optional
|
|
220
|
+
A custom serializer to use for encoding the object, when everything else fails.
|
|
203
221
|
"""
|
|
204
222
|
encoded_dict = {}
|
|
205
223
|
allowed_keys = set(obj.keys())
|
|
@@ -218,12 +236,14 @@ def _dump_dict(
|
|
|
218
236
|
by_alias=by_alias,
|
|
219
237
|
exclude_unset=exclude_unset,
|
|
220
238
|
exclude_none=exclude_none,
|
|
239
|
+
custom_serializer=custom_serializer,
|
|
221
240
|
)
|
|
222
241
|
encoded_value = jsonable_encoder(
|
|
223
242
|
value,
|
|
224
243
|
by_alias=by_alias,
|
|
225
244
|
exclude_unset=exclude_unset,
|
|
226
245
|
exclude_none=exclude_none,
|
|
246
|
+
custom_serializer=custom_serializer,
|
|
227
247
|
)
|
|
228
248
|
encoded_dict[encoded_key] = encoded_value
|
|
229
249
|
return encoded_dict
|
|
@@ -238,9 +258,10 @@ def _dump_sequence(
|
|
|
238
258
|
exclude_unset: bool = False,
|
|
239
259
|
exclude_none: bool = False,
|
|
240
260
|
exclude_defaults: bool = False,
|
|
261
|
+
custom_serializer: Callable[[Any], str] | None = None,
|
|
241
262
|
) -> list[Any]:
|
|
242
263
|
"""
|
|
243
|
-
Dump a sequence to a list, using the same parameters as jsonable_encoder
|
|
264
|
+
Dump a sequence to a list, using the same parameters as jsonable_encoder.
|
|
244
265
|
"""
|
|
245
266
|
encoded_list = []
|
|
246
267
|
for item in obj:
|
|
@@ -253,6 +274,7 @@ def _dump_sequence(
|
|
|
253
274
|
exclude_unset=exclude_unset,
|
|
254
275
|
exclude_defaults=exclude_defaults,
|
|
255
276
|
exclude_none=exclude_none,
|
|
277
|
+
custom_serializer=custom_serializer,
|
|
256
278
|
),
|
|
257
279
|
)
|
|
258
280
|
return encoded_list
|
|
@@ -267,6 +289,7 @@ def _dump_other(
|
|
|
267
289
|
exclude_unset: bool = False,
|
|
268
290
|
exclude_none: bool = False,
|
|
269
291
|
exclude_defaults: bool = False,
|
|
292
|
+
custom_serializer: Callable[[Any], str] | None = None,
|
|
270
293
|
) -> Any:
|
|
271
294
|
"""
|
|
272
295
|
Dump an object to a hashable object, using the same parameters as jsonable_encoder
|
|
@@ -288,6 +311,7 @@ def _dump_other(
|
|
|
288
311
|
exclude_unset=exclude_unset,
|
|
289
312
|
exclude_defaults=exclude_defaults,
|
|
290
313
|
exclude_none=exclude_none,
|
|
314
|
+
custom_serializer=custom_serializer,
|
|
291
315
|
)
|
|
292
316
|
|
|
293
317
|
|
|
@@ -21,3 +21,15 @@ class RequestValidationError(ValidationException):
|
|
|
21
21
|
def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
|
|
22
22
|
super().__init__(errors)
|
|
23
23
|
self.body = body
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SerializationError(Exception):
|
|
27
|
+
"""
|
|
28
|
+
Base exception for all encoding errors
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SchemaValidationError(ValidationException):
|
|
33
|
+
"""
|
|
34
|
+
Raised when the OpenAPI schema validation fails
|
|
35
|
+
"""
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Any, Dict, List, Literal, Optional, Set, Union
|
|
4
4
|
|
|
5
|
-
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
|
|
5
|
+
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, model_validator
|
|
6
6
|
from typing_extensions import Annotated
|
|
7
7
|
|
|
8
8
|
from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild
|
|
9
|
+
from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError
|
|
9
10
|
|
|
10
11
|
MODEL_CONFIG_ALLOW = ConfigDict(extra="allow")
|
|
11
12
|
MODEL_CONFIG_IGNORE = ConfigDict(extra="ignore")
|
|
@@ -16,6 +17,38 @@ These models can be used to parse OpenAPI JSON/YAML files into Python objects, o
|
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
class OpenAPIExtensions(BaseModel):
|
|
21
|
+
"""
|
|
22
|
+
This class serves as a Pydantic proxy model to add OpenAPI extensions.
|
|
23
|
+
|
|
24
|
+
OpenAPI extensions are arbitrary fields, so we remove openapi_extensions when dumping
|
|
25
|
+
and add only the provided value in the schema.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
openapi_extensions: Optional[Dict[str, Any]] = None
|
|
29
|
+
|
|
30
|
+
# If the 'openapi_extensions' field is present in the 'values' dictionary,
|
|
31
|
+
# And if the extension starts with x- (must respect the RFC)
|
|
32
|
+
# update the 'values' dictionary with the contents of 'openapi_extensions',
|
|
33
|
+
# and then remove the 'openapi_extensions' field from the 'values' dictionary
|
|
34
|
+
model_config = {"extra": "allow"}
|
|
35
|
+
|
|
36
|
+
@model_validator(mode="before")
|
|
37
|
+
def serialize_openapi_extension_v2(self):
|
|
38
|
+
if isinstance(self, dict) and self.get("openapi_extensions"):
|
|
39
|
+
|
|
40
|
+
openapi_extension_value = self.get("openapi_extensions")
|
|
41
|
+
|
|
42
|
+
for extension_key in openapi_extension_value:
|
|
43
|
+
if not str(extension_key).startswith("x-"):
|
|
44
|
+
raise SchemaValidationError("An OpenAPI extension key must start with x-")
|
|
45
|
+
|
|
46
|
+
self.update(openapi_extension_value)
|
|
47
|
+
self.pop("openapi_extensions", None)
|
|
48
|
+
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
|
|
19
52
|
# https://swagger.io/specification/#contact-object
|
|
20
53
|
class Contact(BaseModel):
|
|
21
54
|
name: Optional[str] = None
|
|
@@ -57,7 +90,7 @@ class ServerVariable(BaseModel):
|
|
|
57
90
|
|
|
58
91
|
|
|
59
92
|
# https://swagger.io/specification/#server-object
|
|
60
|
-
class Server(
|
|
93
|
+
class Server(OpenAPIExtensions):
|
|
61
94
|
url: Union[AnyUrl, str]
|
|
62
95
|
description: Optional[str] = None
|
|
63
96
|
variables: Optional[Dict[str, ServerVariable]] = None
|
|
@@ -287,7 +320,7 @@ class Tag(BaseModel):
|
|
|
287
320
|
|
|
288
321
|
|
|
289
322
|
# https://swagger.io/specification/#operation-object
|
|
290
|
-
class Operation(
|
|
323
|
+
class Operation(OpenAPIExtensions):
|
|
291
324
|
tags: Optional[List[str]] = None
|
|
292
325
|
summary: Optional[str] = None
|
|
293
326
|
description: Optional[str] = None
|
|
@@ -332,7 +365,7 @@ class SecuritySchemeType(Enum):
|
|
|
332
365
|
openIdConnect = "openIdConnect"
|
|
333
366
|
|
|
334
367
|
|
|
335
|
-
class SecurityBase(
|
|
368
|
+
class SecurityBase(OpenAPIExtensions):
|
|
336
369
|
type_: SecuritySchemeType = Field(alias="type")
|
|
337
370
|
description: Optional[str] = None
|
|
338
371
|
|
|
@@ -428,7 +461,7 @@ class Components(BaseModel):
|
|
|
428
461
|
|
|
429
462
|
|
|
430
463
|
# https://swagger.io/specification/#openapi-object
|
|
431
|
-
class OpenAPI(
|
|
464
|
+
class OpenAPI(OpenAPIExtensions):
|
|
432
465
|
openapi: str
|
|
433
466
|
info: Info
|
|
434
467
|
jsonSchemaDialect: Optional[str] = None
|
|
@@ -8,7 +8,6 @@ if TYPE_CHECKING:
|
|
|
8
8
|
|
|
9
9
|
def generate_swagger_html(
|
|
10
10
|
spec: str,
|
|
11
|
-
path: str,
|
|
12
11
|
swagger_js: str,
|
|
13
12
|
swagger_css: str,
|
|
14
13
|
swagger_base_url: str,
|
|
@@ -22,8 +21,6 @@ def generate_swagger_html(
|
|
|
22
21
|
----------
|
|
23
22
|
spec: str
|
|
24
23
|
The OpenAPI spec
|
|
25
|
-
path: str
|
|
26
|
-
The path to the Swagger documentation
|
|
27
24
|
swagger_js: str
|
|
28
25
|
Swagger UI JavaScript source code or URL
|
|
29
26
|
swagger_css: str
|
|
@@ -97,7 +94,7 @@ def generate_swagger_html(
|
|
|
97
94
|
}}
|
|
98
95
|
|
|
99
96
|
var ui = SwaggerUIBundle(swaggerUIOptions)
|
|
100
|
-
ui.specActions.updateUrl(
|
|
97
|
+
ui.specActions.updateUrl(currentUrl.pathname + "?format=json");
|
|
101
98
|
{oauth2_content}
|
|
102
99
|
</script>
|
|
103
100
|
</html>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Mapping
|
|
3
|
+
from typing import Any, Dict, List, Mapping
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class _FrozenDict(dict):
|
|
@@ -18,7 +18,31 @@ class _FrozenDict(dict):
|
|
|
18
18
|
return hash(frozenset(self.keys()))
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
class _FrozenListDict(List[Dict[str, List[str]]]):
|
|
22
|
+
"""
|
|
23
|
+
Freezes a list of dictionaries containing lists of strings.
|
|
24
|
+
|
|
25
|
+
This function takes a list of dictionaries where the values are lists of strings and converts it into
|
|
26
|
+
a frozen set of frozen sets of frozen dictionaries. This is done by iterating over the input list,
|
|
27
|
+
converting each dictionary's values (lists of strings) into frozen sets of strings, and then
|
|
28
|
+
converting the resulting dictionary into a frozen dictionary. Finally, all these frozen dictionaries
|
|
29
|
+
are collected into a frozen set of frozen sets.
|
|
30
|
+
|
|
31
|
+
This operation is useful when you want to ensure the immutability of the data structure and make it
|
|
32
|
+
hashable, which is required for certain operations like using it as a key in a dictionary or as an
|
|
33
|
+
element in a set.
|
|
34
|
+
|
|
35
|
+
Example: [{"TestAuth": ["test", "test1"]}]
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __hash__(self):
|
|
39
|
+
hashable_items = []
|
|
40
|
+
for item in self:
|
|
41
|
+
hashable_items.extend((key, frozenset(value)) for key, value in item.items())
|
|
42
|
+
return hash(frozenset(hashable_items))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def extract_origin_header(resolved_headers: Mapping[str, Any]):
|
|
22
46
|
"""
|
|
23
47
|
Extracts the 'origin' or 'Origin' header from the provided resolver headers.
|
|
24
48
|
|
|
@@ -34,3 +58,32 @@ def extract_origin_header(resolved_headers: Mapping[str, Any]) -> str | None:
|
|
|
34
58
|
if isinstance(resolved_header, list):
|
|
35
59
|
return resolved_header[0]
|
|
36
60
|
return resolved_header
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _validate_openapi_security_parameters(
|
|
64
|
+
security: list[dict[str, list[str]]],
|
|
65
|
+
security_schemes: dict[str, Any] | None = None,
|
|
66
|
+
) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
This function checks if all security requirements listed in the 'security'
|
|
69
|
+
parameter are defined in the 'security_schemes' dictionary, as specified
|
|
70
|
+
in the OpenAPI schema.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
security: List[Dict[str, List[str]]]
|
|
75
|
+
A list of security requirements
|
|
76
|
+
security_schemes: Optional[Dict[str, Any]]
|
|
77
|
+
A dictionary mapping security scheme names to their corresponding security scheme objects.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
bool
|
|
82
|
+
Whether list of security schemes match allowed security_schemes.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
security_schemes = security_schemes or {}
|
|
86
|
+
|
|
87
|
+
security_schema_match = all(key in security_schemes for sec in security for key in sec)
|
|
88
|
+
|
|
89
|
+
return bool(security_schema_match and security_schemes)
|
|
@@ -7,6 +7,7 @@ if TYPE_CHECKING:
|
|
|
7
7
|
from aws_lambda_powertools.logging.logger import Logger
|
|
8
8
|
|
|
9
9
|
PACKAGE_LOGGER = "aws_lambda_powertools"
|
|
10
|
+
LOGGER = logging.getLogger(__name__)
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def copy_config_to_registered_loggers(
|
|
@@ -59,7 +60,7 @@ def copy_config_to_registered_loggers(
|
|
|
59
60
|
loggers = exclude
|
|
60
61
|
filter_func = _exclude_registered_loggers_filter
|
|
61
62
|
|
|
62
|
-
registered_loggers = _find_registered_loggers(
|
|
63
|
+
registered_loggers = _find_registered_loggers(loggers=loggers, filter_func=filter_func)
|
|
63
64
|
for logger in registered_loggers:
|
|
64
65
|
_configure_logger(source_logger=source_logger, logger=logger, level=level, ignore_log_level=ignore_log_level)
|
|
65
66
|
|
|
@@ -75,13 +76,12 @@ def _exclude_registered_loggers_filter(loggers: set[str]) -> list[logging.Logger
|
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
def _find_registered_loggers(
|
|
78
|
-
source_logger: Logger,
|
|
79
79
|
loggers: set[str],
|
|
80
80
|
filter_func: Callable[[set[str]], list[logging.Logger]],
|
|
81
81
|
) -> list[logging.Logger]:
|
|
82
82
|
"""Filter root loggers based on provided parameters."""
|
|
83
83
|
root_loggers = filter_func(loggers)
|
|
84
|
-
|
|
84
|
+
LOGGER.debug(f"Filtered root loggers: {root_loggers}")
|
|
85
85
|
return root_loggers
|
|
86
86
|
|
|
87
87
|
|
|
@@ -94,7 +94,7 @@ def _configure_logger(
|
|
|
94
94
|
# customers may not want to copy the same log level from Logger to discovered loggers
|
|
95
95
|
if not ignore_log_level:
|
|
96
96
|
logger.setLevel(level)
|
|
97
|
-
|
|
97
|
+
LOGGER.debug(f"Logger {logger} reconfigured to use logging level {level}")
|
|
98
98
|
|
|
99
99
|
logger.handlers = []
|
|
100
100
|
logger.propagate = False # ensure we don't propagate logs to existing loggers, #1073
|
|
@@ -102,4 +102,4 @@ def _configure_logger(
|
|
|
102
102
|
|
|
103
103
|
for source_handler in source_logger.handlers:
|
|
104
104
|
logger.addHandler(source_handler)
|
|
105
|
-
|
|
105
|
+
LOGGER.debug(f"Logger {logger} reconfigured to use {source_handler}")
|
|
@@ -101,10 +101,10 @@ class Cookie:
|
|
|
101
101
|
|
|
102
102
|
if self.max_age:
|
|
103
103
|
if self.max_age > 0:
|
|
104
|
-
payload.write(f";
|
|
104
|
+
payload.write(f"; Max-Age={self.max_age}")
|
|
105
105
|
else:
|
|
106
106
|
# negative or zero max-age should be set to 0
|
|
107
|
-
payload.write(";
|
|
107
|
+
payload.write("; Max-Age=0")
|
|
108
108
|
|
|
109
109
|
if self.http_only:
|
|
110
110
|
payload.write("; HttpOnly")
|
|
@@ -72,6 +72,19 @@ class TypeDeserializer:
|
|
|
72
72
|
return value
|
|
73
73
|
|
|
74
74
|
def _deserialize_n(self, value: str) -> Decimal:
|
|
75
|
+
# value is None or "."? It's zero
|
|
76
|
+
# then return early
|
|
77
|
+
value = value.lstrip("0")
|
|
78
|
+
if not value or value == ".":
|
|
79
|
+
return DYNAMODB_CONTEXT.create_decimal(0)
|
|
80
|
+
|
|
81
|
+
if len(value) > 38:
|
|
82
|
+
# See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number
|
|
83
|
+
# Calculate the number of trailing zeros after the 38th character
|
|
84
|
+
tail = len(value[38:]) - len(value[38:].rstrip("0"))
|
|
85
|
+
# Trim the value: remove trailing zeros if any, or just take the first 38 characters
|
|
86
|
+
value = value[:-tail] if tail > 0 else value[:38]
|
|
87
|
+
|
|
75
88
|
return DYNAMODB_CONTEXT.create_decimal(value)
|
|
76
89
|
|
|
77
90
|
def _deserialize_s(self, value: str) -> str:
|
|
@@ -6,7 +6,7 @@ import functools
|
|
|
6
6
|
import inspect
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Callable, Sequence, cast, overload
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, Sequence, TypeVar, cast, overload
|
|
10
10
|
|
|
11
11
|
from aws_lambda_powertools.shared import constants
|
|
12
12
|
from aws_lambda_powertools.shared.functions import (
|
|
@@ -27,6 +27,8 @@ logger = logging.getLogger(__name__)
|
|
|
27
27
|
|
|
28
28
|
aws_xray_sdk = LazyLoader(constants.XRAY_SDK_MODULE, globals(), constants.XRAY_SDK_MODULE)
|
|
29
29
|
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
|
|
30
32
|
|
|
31
33
|
class Tracer:
|
|
32
34
|
"""Tracer using AWS-XRay to provide decorators with known defaults for Lambda functions
|
|
@@ -255,7 +257,7 @@ class Tracer:
|
|
|
255
257
|
|
|
256
258
|
def capture_lambda_handler(
|
|
257
259
|
self,
|
|
258
|
-
lambda_handler: Callable[[
|
|
260
|
+
lambda_handler: Callable[[T, Any], Any] | Callable[[T, Any, Any], Any] | None = None,
|
|
259
261
|
capture_response: bool | None = None,
|
|
260
262
|
capture_error: bool | None = None,
|
|
261
263
|
):
|
|
@@ -229,7 +229,12 @@ class BasePartialProcessor(ABC):
|
|
|
229
229
|
class BasePartialBatchProcessor(BasePartialProcessor): # noqa
|
|
230
230
|
DEFAULT_RESPONSE: PartialItemFailureResponse = {"batchItemFailures": []}
|
|
231
231
|
|
|
232
|
-
def __init__(
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
event_type: EventType,
|
|
235
|
+
model: BatchTypeModels | None = None,
|
|
236
|
+
raise_on_entire_batch_failure: bool = True,
|
|
237
|
+
):
|
|
233
238
|
"""Process batch and partially report failed items
|
|
234
239
|
|
|
235
240
|
Parameters
|
|
@@ -238,6 +243,9 @@ class BasePartialBatchProcessor(BasePartialProcessor): # noqa
|
|
|
238
243
|
Whether this is a SQS, DynamoDB Streams, or Kinesis Data Stream event
|
|
239
244
|
model: BatchTypeModels | None
|
|
240
245
|
Parser's data model using either SqsRecordModel, DynamoDBStreamRecordModel, KinesisDataStreamRecord
|
|
246
|
+
raise_on_entire_batch_failure: bool
|
|
247
|
+
Raise an exception when the entire batch has failed processing.
|
|
248
|
+
When set to False, partial failures are reported in the response
|
|
241
249
|
|
|
242
250
|
Exceptions
|
|
243
251
|
----------
|
|
@@ -246,6 +254,7 @@ class BasePartialBatchProcessor(BasePartialProcessor): # noqa
|
|
|
246
254
|
"""
|
|
247
255
|
self.event_type = event_type
|
|
248
256
|
self.model = model
|
|
257
|
+
self.raise_on_entire_batch_failure = raise_on_entire_batch_failure
|
|
249
258
|
self.batch_response: PartialItemFailureResponse = copy.deepcopy(self.DEFAULT_RESPONSE)
|
|
250
259
|
self._COLLECTOR_MAPPING = {
|
|
251
260
|
EventType.SQS: self._collect_sqs_failures,
|
|
@@ -281,7 +290,7 @@ class BasePartialBatchProcessor(BasePartialProcessor): # noqa
|
|
|
281
290
|
if not self._has_messages_to_report():
|
|
282
291
|
return
|
|
283
292
|
|
|
284
|
-
if self._entire_batch_failed():
|
|
293
|
+
if self._entire_batch_failed() and self.raise_on_entire_batch_failure:
|
|
285
294
|
raise BatchProcessingError(
|
|
286
295
|
msg=f"All records failed processing. {len(self.exceptions)} individual errors logged "
|
|
287
296
|
f"separately below.",
|
|
@@ -478,7 +487,7 @@ class BatchProcessor(BasePartialBatchProcessor): # Keep old name for compatibil
|
|
|
478
487
|
Raises
|
|
479
488
|
------
|
|
480
489
|
BatchProcessingError
|
|
481
|
-
When all batch records fail processing
|
|
490
|
+
When all batch records fail processing and raise_on_entire_batch_failure is True
|
|
482
491
|
|
|
483
492
|
Limitations
|
|
484
493
|
-----------
|
|
@@ -627,7 +636,7 @@ class AsyncBatchProcessor(BasePartialBatchProcessor):
|
|
|
627
636
|
Raises
|
|
628
637
|
------
|
|
629
638
|
BatchProcessingError
|
|
630
|
-
When all batch records fail processing
|
|
639
|
+
When all batch records fail processing and raise_on_entire_batch_failure is True
|
|
631
640
|
|
|
632
641
|
Limitations
|
|
633
642
|
-----------
|