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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import TYPE_CHECKING, Any, Callable
|
|
4
5
|
|
|
5
6
|
from typing_extensions import override
|
|
@@ -10,10 +11,12 @@ from aws_lambda_powertools.event_handler.api_gateway import (
|
|
|
10
11
|
ProxyEventType,
|
|
11
12
|
ResponseBuilder,
|
|
12
13
|
)
|
|
14
|
+
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
15
17
|
from re import Match
|
|
16
18
|
|
|
19
|
+
from aws_lambda_powertools.event_handler.openapi.models import Contact, License, SecurityScheme, Server, Tag
|
|
17
20
|
from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse
|
|
18
21
|
from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent
|
|
19
22
|
|
|
@@ -107,6 +110,8 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
107
110
|
include_in_schema: bool = True,
|
|
108
111
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
109
112
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
113
|
+
|
|
114
|
+
openapi_extensions = None
|
|
110
115
|
security = None
|
|
111
116
|
|
|
112
117
|
return super().get(
|
|
@@ -122,6 +127,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
122
127
|
operation_id,
|
|
123
128
|
include_in_schema,
|
|
124
129
|
security,
|
|
130
|
+
openapi_extensions,
|
|
125
131
|
middlewares,
|
|
126
132
|
)
|
|
127
133
|
|
|
@@ -142,6 +148,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
142
148
|
include_in_schema: bool = True,
|
|
143
149
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
144
150
|
):
|
|
151
|
+
openapi_extensions = None
|
|
145
152
|
security = None
|
|
146
153
|
|
|
147
154
|
return super().post(
|
|
@@ -157,6 +164,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
157
164
|
operation_id,
|
|
158
165
|
include_in_schema,
|
|
159
166
|
security,
|
|
167
|
+
openapi_extensions,
|
|
160
168
|
middlewares,
|
|
161
169
|
)
|
|
162
170
|
|
|
@@ -177,6 +185,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
177
185
|
include_in_schema: bool = True,
|
|
178
186
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
179
187
|
):
|
|
188
|
+
openapi_extensions = None
|
|
180
189
|
security = None
|
|
181
190
|
|
|
182
191
|
return super().put(
|
|
@@ -192,6 +201,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
192
201
|
operation_id,
|
|
193
202
|
include_in_schema,
|
|
194
203
|
security,
|
|
204
|
+
openapi_extensions,
|
|
195
205
|
middlewares,
|
|
196
206
|
)
|
|
197
207
|
|
|
@@ -212,6 +222,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
212
222
|
include_in_schema: bool = True,
|
|
213
223
|
middlewares: list[Callable] | None = None,
|
|
214
224
|
):
|
|
225
|
+
openapi_extensions = None
|
|
215
226
|
security = None
|
|
216
227
|
|
|
217
228
|
return super().patch(
|
|
@@ -227,6 +238,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
227
238
|
operation_id,
|
|
228
239
|
include_in_schema,
|
|
229
240
|
security,
|
|
241
|
+
openapi_extensions,
|
|
230
242
|
middlewares,
|
|
231
243
|
)
|
|
232
244
|
|
|
@@ -247,6 +259,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
247
259
|
include_in_schema: bool = True,
|
|
248
260
|
middlewares: list[Callable[..., Any]] | None = None,
|
|
249
261
|
):
|
|
262
|
+
openapi_extensions = None
|
|
250
263
|
security = None
|
|
251
264
|
|
|
252
265
|
return super().delete(
|
|
@@ -262,6 +275,7 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
262
275
|
operation_id,
|
|
263
276
|
include_in_schema,
|
|
264
277
|
security,
|
|
278
|
+
openapi_extensions,
|
|
265
279
|
middlewares,
|
|
266
280
|
)
|
|
267
281
|
|
|
@@ -273,3 +287,110 @@ class BedrockAgentResolver(ApiGatewayResolver):
|
|
|
273
287
|
if match.groupdict() and self.current_event.parameters:
|
|
274
288
|
parameters = {parameter["name"]: parameter["value"] for parameter in self.current_event.parameters}
|
|
275
289
|
return parameters
|
|
290
|
+
|
|
291
|
+
@override
|
|
292
|
+
def get_openapi_json_schema( # type: ignore[override]
|
|
293
|
+
self,
|
|
294
|
+
*,
|
|
295
|
+
title: str = "Powertools API",
|
|
296
|
+
version: str = DEFAULT_API_VERSION,
|
|
297
|
+
openapi_version: str = DEFAULT_OPENAPI_VERSION,
|
|
298
|
+
summary: str | None = None,
|
|
299
|
+
description: str | None = None,
|
|
300
|
+
tags: list[Tag | str] | None = None,
|
|
301
|
+
servers: list[Server] | None = None,
|
|
302
|
+
terms_of_service: str | None = None,
|
|
303
|
+
contact: Contact | None = None,
|
|
304
|
+
license_info: License | None = None,
|
|
305
|
+
security_schemes: dict[str, SecurityScheme] | None = None,
|
|
306
|
+
security: list[dict[str, list[str]]] | None = None,
|
|
307
|
+
) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Returns the OpenAPI schema as a JSON serializable dict.
|
|
310
|
+
Since Bedrock Agents only support OpenAPI 3.0.0, we convert OpenAPI 3.1.0 schemas
|
|
311
|
+
and enforce 3.0.0 compatibility for seamless integration.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
title: str
|
|
316
|
+
The title of the application.
|
|
317
|
+
version: str
|
|
318
|
+
The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API
|
|
319
|
+
openapi_version: str, default = "3.0.0"
|
|
320
|
+
The version of the OpenAPI Specification (which the document uses).
|
|
321
|
+
summary: str, optional
|
|
322
|
+
A short summary of what the application does.
|
|
323
|
+
description: str, optional
|
|
324
|
+
A verbose explanation of the application behavior.
|
|
325
|
+
tags: list[Tag, str], optional
|
|
326
|
+
A list of tags used by the specification with additional metadata.
|
|
327
|
+
servers: list[Server], optional
|
|
328
|
+
An array of Server Objects, which provide connectivity information to a target server.
|
|
329
|
+
terms_of_service: str, optional
|
|
330
|
+
A URL to the Terms of Service for the API. MUST be in the format of a URL.
|
|
331
|
+
contact: Contact, optional
|
|
332
|
+
The contact information for the exposed API.
|
|
333
|
+
license_info: License, optional
|
|
334
|
+
The license information for the exposed API.
|
|
335
|
+
security_schemes: dict[str, SecurityScheme]], optional
|
|
336
|
+
A declaration of the security schemes available to be used in the specification.
|
|
337
|
+
security: list[dict[str, list[str]]], optional
|
|
338
|
+
A declaration of which security mechanisms are applied globally across the API.
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
str
|
|
343
|
+
The OpenAPI schema as a JSON serializable dict.
|
|
344
|
+
"""
|
|
345
|
+
from aws_lambda_powertools.event_handler.openapi.compat import model_json
|
|
346
|
+
|
|
347
|
+
openapi_extensions = None
|
|
348
|
+
|
|
349
|
+
schema = super().get_openapi_schema(
|
|
350
|
+
title=title,
|
|
351
|
+
version=version,
|
|
352
|
+
openapi_version=openapi_version,
|
|
353
|
+
summary=summary,
|
|
354
|
+
description=description,
|
|
355
|
+
tags=tags,
|
|
356
|
+
servers=servers,
|
|
357
|
+
terms_of_service=terms_of_service,
|
|
358
|
+
contact=contact,
|
|
359
|
+
license_info=license_info,
|
|
360
|
+
security_schemes=security_schemes,
|
|
361
|
+
security=security,
|
|
362
|
+
openapi_extensions=openapi_extensions,
|
|
363
|
+
)
|
|
364
|
+
schema.openapi = "3.0.3"
|
|
365
|
+
|
|
366
|
+
# Transform OpenAPI 3.1 into 3.0
|
|
367
|
+
def inner(yaml_dict):
|
|
368
|
+
if isinstance(yaml_dict, dict):
|
|
369
|
+
if "anyOf" in yaml_dict and isinstance((anyOf := yaml_dict["anyOf"]), list):
|
|
370
|
+
for i, item in enumerate(anyOf):
|
|
371
|
+
if isinstance(item, dict) and item.get("type") == "null":
|
|
372
|
+
anyOf.pop(i)
|
|
373
|
+
yaml_dict["nullable"] = True
|
|
374
|
+
if "examples" in yaml_dict:
|
|
375
|
+
examples = yaml_dict["examples"]
|
|
376
|
+
del yaml_dict["examples"]
|
|
377
|
+
if isinstance(examples, list) and len(examples):
|
|
378
|
+
yaml_dict["example"] = examples[0]
|
|
379
|
+
for value in yaml_dict.values():
|
|
380
|
+
inner(value)
|
|
381
|
+
elif isinstance(yaml_dict, list):
|
|
382
|
+
for item in yaml_dict:
|
|
383
|
+
inner(item)
|
|
384
|
+
|
|
385
|
+
model = json.loads(
|
|
386
|
+
model_json(
|
|
387
|
+
schema,
|
|
388
|
+
by_alias=True,
|
|
389
|
+
exclude_none=True,
|
|
390
|
+
indent=2,
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
inner(model)
|
|
395
|
+
|
|
396
|
+
return json.dumps(model)
|
|
@@ -39,7 +39,7 @@ class NotFoundError(ServiceError):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class InternalServerError(ServiceError):
|
|
42
|
-
"""API Gateway and ALB
|
|
42
|
+
"""API Gateway and ALB Internal Server Error (500)"""
|
|
43
43
|
|
|
44
44
|
def __init__(self, message: str):
|
|
45
45
|
super().__init__(HTTPStatus.INTERNAL_SERVER_ERROR, message)
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResolverRegistry:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.resolvers: dict[str, dict[str, Any]] = {}
|
|
12
|
+
|
|
13
|
+
def register(
|
|
14
|
+
self,
|
|
15
|
+
type_name: str = "*",
|
|
16
|
+
field_name: str | None = None,
|
|
17
|
+
raise_on_error: bool = False,
|
|
18
|
+
aggregate: bool = True,
|
|
19
|
+
) -> Callable:
|
|
20
|
+
"""Registers the resolver for field_name
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
type_name : str
|
|
25
|
+
Type name
|
|
26
|
+
field_name : str
|
|
27
|
+
Field name
|
|
28
|
+
raise_on_error: bool
|
|
29
|
+
A flag indicating whether to raise an error when processing batches
|
|
30
|
+
with failed items. Defaults to False, which means errors are handled without raising exceptions.
|
|
31
|
+
aggregate: bool
|
|
32
|
+
A flag indicating whether the batch items should be processed at once or individually.
|
|
33
|
+
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
34
|
+
If False, the batch resolver will process each item in the batch individually.
|
|
35
|
+
|
|
36
|
+
Return
|
|
37
|
+
----------
|
|
38
|
+
Callable
|
|
39
|
+
A Callable
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def _register(func) -> Callable:
|
|
43
|
+
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
|
|
44
|
+
self.resolvers[f"{type_name}.{field_name}"] = {
|
|
45
|
+
"func": func,
|
|
46
|
+
"raise_on_error": raise_on_error,
|
|
47
|
+
"aggregate": aggregate,
|
|
48
|
+
}
|
|
49
|
+
return func
|
|
50
|
+
|
|
51
|
+
return _register
|
|
52
|
+
|
|
53
|
+
def find_resolver(self, type_name: str, field_name: str) -> dict | None:
|
|
54
|
+
"""Find resolver based on type_name and field_name
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
type_name : str
|
|
59
|
+
Type name
|
|
60
|
+
field_name : str
|
|
61
|
+
Field name
|
|
62
|
+
Return
|
|
63
|
+
----------
|
|
64
|
+
Optional[Dict]
|
|
65
|
+
A dictionary with the resolver and if raise exception on error
|
|
66
|
+
"""
|
|
67
|
+
logger.debug(f"Looking for resolver for type={type_name}, field={field_name}.")
|
|
68
|
+
return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}"))
|
|
69
|
+
|
|
70
|
+
def merge(self, other_registry: ResolverRegistry):
|
|
71
|
+
"""Update current registry with incoming registry
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
other_registry : ResolverRegistry
|
|
76
|
+
Registry to merge from
|
|
77
|
+
"""
|
|
78
|
+
self.resolvers.update(**other_registry.resolvers)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseRouter(ABC):
|
|
8
|
+
"""Abstract base class for Router (resolvers)"""
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
|
|
12
|
+
"""
|
|
13
|
+
Retrieve a resolver function for a specific type and field.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
-----------
|
|
17
|
+
type_name: str
|
|
18
|
+
The name of the type.
|
|
19
|
+
field_name: str, optional
|
|
20
|
+
The name of the field (default is None).
|
|
21
|
+
|
|
22
|
+
Examples
|
|
23
|
+
--------
|
|
24
|
+
```python
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from aws_lambda_powertools.event_handler import AppSyncResolver
|
|
28
|
+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
|
|
29
|
+
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
30
|
+
|
|
31
|
+
app = AppSyncResolver()
|
|
32
|
+
|
|
33
|
+
@app.resolver(type_name="Query", field_name="getPost")
|
|
34
|
+
def related_posts(event: AppSyncResolverEvent) -> Optional[list]:
|
|
35
|
+
return {"success": "ok"}
|
|
36
|
+
|
|
37
|
+
def lambda_handler(event, context: LambdaContext) -> dict:
|
|
38
|
+
return app.resolve(event, context)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
Callable
|
|
44
|
+
The resolver function.
|
|
45
|
+
"""
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def batch_resolver(
|
|
50
|
+
self,
|
|
51
|
+
type_name: str = "*",
|
|
52
|
+
field_name: str | None = None,
|
|
53
|
+
raise_on_error: bool = False,
|
|
54
|
+
aggregate: bool = True,
|
|
55
|
+
) -> Callable:
|
|
56
|
+
"""
|
|
57
|
+
Retrieve a batch resolver function for a specific type and field.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
-----------
|
|
61
|
+
type_name: str
|
|
62
|
+
The name of the type.
|
|
63
|
+
field_name: str, optional
|
|
64
|
+
The name of the field (default is None).
|
|
65
|
+
raise_on_error: bool
|
|
66
|
+
A flag indicating whether to raise an error when processing batches
|
|
67
|
+
with failed items. Defaults to False, which means errors are handled without raising exceptions.
|
|
68
|
+
aggregate: bool
|
|
69
|
+
A flag indicating whether the batch items should be processed at once or individually.
|
|
70
|
+
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
71
|
+
If False, the batch resolver will process each item in the batch individually.
|
|
72
|
+
|
|
73
|
+
Examples
|
|
74
|
+
--------
|
|
75
|
+
```python
|
|
76
|
+
from typing import Optional
|
|
77
|
+
|
|
78
|
+
from aws_lambda_powertools.event_handler import AppSyncResolver
|
|
79
|
+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
|
|
80
|
+
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
81
|
+
|
|
82
|
+
app = AppSyncResolver()
|
|
83
|
+
|
|
84
|
+
@app.batch_resolver(type_name="Query", field_name="getPost")
|
|
85
|
+
def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]:
|
|
86
|
+
return {"post_id": id}
|
|
87
|
+
|
|
88
|
+
def lambda_handler(event, context: LambdaContext) -> dict:
|
|
89
|
+
return app.resolve(event, context)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
Callable
|
|
95
|
+
The batch resolver function.
|
|
96
|
+
"""
|
|
97
|
+
raise NotImplementedError
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def async_batch_resolver(
|
|
101
|
+
self,
|
|
102
|
+
type_name: str = "*",
|
|
103
|
+
field_name: str | None = None,
|
|
104
|
+
raise_on_error: bool = False,
|
|
105
|
+
aggregate: bool = True,
|
|
106
|
+
) -> Callable:
|
|
107
|
+
"""
|
|
108
|
+
Retrieve a batch resolver function for a specific type and field and runs async.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
-----------
|
|
112
|
+
type_name: str
|
|
113
|
+
The name of the type.
|
|
114
|
+
field_name: str, optional
|
|
115
|
+
The name of the field (default is None).
|
|
116
|
+
raise_on_error: bool
|
|
117
|
+
A flag indicating whether to raise an error when processing batches
|
|
118
|
+
with failed items. Defaults to False, which means errors are handled without raising exceptions.
|
|
119
|
+
aggregate: bool
|
|
120
|
+
A flag indicating whether the batch items should be processed at once or individually.
|
|
121
|
+
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
122
|
+
If False, the batch resolver will process each item in the batch individually.
|
|
123
|
+
|
|
124
|
+
Examples
|
|
125
|
+
--------
|
|
126
|
+
```python
|
|
127
|
+
from typing import Optional
|
|
128
|
+
|
|
129
|
+
from aws_lambda_powertools.event_handler import AppSyncResolver
|
|
130
|
+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
|
|
131
|
+
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
132
|
+
|
|
133
|
+
app = AppSyncResolver()
|
|
134
|
+
|
|
135
|
+
@app.async_batch_resolver(type_name="Query", field_name="getPost")
|
|
136
|
+
async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]:
|
|
137
|
+
return {"post_id": id}
|
|
138
|
+
|
|
139
|
+
def lambda_handler(event, context: LambdaContext) -> dict:
|
|
140
|
+
return app.resolve(event, context)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
Callable
|
|
146
|
+
The batch resolver function.
|
|
147
|
+
"""
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
@abstractmethod
|
|
151
|
+
def append_context(self, **additional_context) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Appends context information available under any route.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
-----------
|
|
157
|
+
**additional_context: dict
|
|
158
|
+
Additional context key-value pairs to append.
|
|
159
|
+
"""
|
|
160
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Callable
|
|
4
|
+
|
|
5
|
+
from aws_lambda_powertools.event_handler.graphql_appsync._registry import ResolverRegistry
|
|
6
|
+
from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseRouter
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncResolverEvent
|
|
10
|
+
from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Router(BaseRouter):
|
|
14
|
+
context: dict
|
|
15
|
+
current_batch_event: list[AppSyncResolverEvent] = []
|
|
16
|
+
current_event: AppSyncResolverEvent | None = None
|
|
17
|
+
lambda_context: LambdaContext | None = None
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.context = {} # early init as customers might add context before event resolution
|
|
21
|
+
self._resolver_registry = ResolverRegistry()
|
|
22
|
+
self._batch_resolver_registry = ResolverRegistry()
|
|
23
|
+
self._async_batch_resolver_registry = ResolverRegistry()
|
|
24
|
+
|
|
25
|
+
def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
|
|
26
|
+
return self._resolver_registry.register(field_name=field_name, type_name=type_name)
|
|
27
|
+
|
|
28
|
+
def batch_resolver(
|
|
29
|
+
self,
|
|
30
|
+
type_name: str = "*",
|
|
31
|
+
field_name: str | None = None,
|
|
32
|
+
raise_on_error: bool = False,
|
|
33
|
+
aggregate: bool = True,
|
|
34
|
+
) -> Callable:
|
|
35
|
+
return self._batch_resolver_registry.register(
|
|
36
|
+
field_name=field_name,
|
|
37
|
+
type_name=type_name,
|
|
38
|
+
raise_on_error=raise_on_error,
|
|
39
|
+
aggregate=aggregate,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def async_batch_resolver(
|
|
43
|
+
self,
|
|
44
|
+
type_name: str = "*",
|
|
45
|
+
field_name: str | None = None,
|
|
46
|
+
raise_on_error: bool = False,
|
|
47
|
+
aggregate: bool = True,
|
|
48
|
+
) -> Callable:
|
|
49
|
+
return self._async_batch_resolver_registry.register(
|
|
50
|
+
field_name=field_name,
|
|
51
|
+
type_name=type_name,
|
|
52
|
+
raise_on_error=raise_on_error,
|
|
53
|
+
aggregate=aggregate,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def append_context(self, **additional_context):
|
|
57
|
+
"""Append key=value data as routing context"""
|
|
58
|
+
self.context.update(**additional_context)
|
|
59
|
+
|
|
60
|
+
def clear_context(self):
|
|
61
|
+
"""Resets routing context"""
|
|
62
|
+
self.context.clear()
|
|
@@ -232,7 +232,7 @@ class OpenAPIValidationMiddleware(BaseMiddlewareHandler):
|
|
|
232
232
|
for k, v in res.items()
|
|
233
233
|
}
|
|
234
234
|
elif dataclasses.is_dataclass(res):
|
|
235
|
-
return dataclasses.asdict(res)
|
|
235
|
+
return dataclasses.asdict(res) # type: ignore[call-overload]
|
|
236
236
|
return res
|
|
237
237
|
|
|
238
238
|
def _get_body(self, app: EventHandlerInstance) -> dict[str, Any]:
|