aws-lambda-powertools 3.10.1a10__py3-none-any.whl → 3.11.0__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/__init__.py +2 -0
- aws_lambda_powertools/event_handler/api_gateway.py +6 -20
- aws_lambda_powertools/event_handler/appsync.py +4 -29
- aws_lambda_powertools/event_handler/events_appsync/__init__.py +5 -0
- aws_lambda_powertools/event_handler/events_appsync/_registry.py +92 -0
- aws_lambda_powertools/event_handler/events_appsync/appsync_events.py +422 -0
- aws_lambda_powertools/event_handler/events_appsync/base.py +44 -0
- aws_lambda_powertools/event_handler/events_appsync/exceptions.py +25 -0
- aws_lambda_powertools/event_handler/events_appsync/functions.py +106 -0
- aws_lambda_powertools/event_handler/events_appsync/router.py +199 -0
- aws_lambda_powertools/event_handler/events_appsync/types.py +21 -0
- aws_lambda_powertools/event_handler/exception_handling.py +118 -0
- aws_lambda_powertools/shared/version.py +1 -1
- aws_lambda_powertools/utilities/data_classes/__init__.py +2 -0
- aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py +41 -34
- aws_lambda_powertools/utilities/data_classes/appsync_resolver_events_event.py +56 -0
- {aws_lambda_powertools-3.10.1a10.dist-info → aws_lambda_powertools-3.11.0.dist-info}/METADATA +1 -1
- {aws_lambda_powertools-3.10.1a10.dist-info → aws_lambda_powertools-3.11.0.dist-info}/RECORD +20 -10
- {aws_lambda_powertools-3.10.1a10.dist-info → aws_lambda_powertools-3.11.0.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.10.1a10.dist-info → aws_lambda_powertools-3.11.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from typing import Callable
|
5
|
+
|
6
|
+
DEFAULT_ROUTE = "/default/*"
|
7
|
+
|
8
|
+
|
9
|
+
class BaseRouter(ABC):
|
10
|
+
"""Abstract base class for Router (resolvers)"""
|
11
|
+
|
12
|
+
@abstractmethod
|
13
|
+
def on_publish(
|
14
|
+
self,
|
15
|
+
path: str = DEFAULT_ROUTE,
|
16
|
+
aggregate: bool = True,
|
17
|
+
) -> Callable:
|
18
|
+
raise NotImplementedError
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
def async_on_publish(
|
22
|
+
self,
|
23
|
+
path: str = DEFAULT_ROUTE,
|
24
|
+
aggregate: bool = True,
|
25
|
+
) -> Callable:
|
26
|
+
raise NotImplementedError
|
27
|
+
|
28
|
+
@abstractmethod
|
29
|
+
def on_subscribe(
|
30
|
+
self,
|
31
|
+
path: str = DEFAULT_ROUTE,
|
32
|
+
) -> Callable:
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
def append_context(self, **additional_context) -> None:
|
36
|
+
"""
|
37
|
+
Appends context information available under any route.
|
38
|
+
|
39
|
+
Parameters
|
40
|
+
-----------
|
41
|
+
**additional_context: dict
|
42
|
+
Additional context key-value pairs to append.
|
43
|
+
"""
|
44
|
+
raise NotImplementedError
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
|
4
|
+
class UnauthorizedException(Exception):
|
5
|
+
"""
|
6
|
+
Error to be thrown to communicate the subscription is unauthorized.
|
7
|
+
|
8
|
+
When this error is raised, the client will receive a 40x error code
|
9
|
+
and the subscription will be closed.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
message (str): The error message describing the unauthorized access.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, message: str | None = None, *args, **kwargs):
|
16
|
+
"""
|
17
|
+
Initialize the UnauthorizedException.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
message (str): A descriptive error message.
|
21
|
+
*args: Variable positional arguments.
|
22
|
+
**kwargs: Variable keyword arguments.
|
23
|
+
"""
|
24
|
+
super().__init__(message, *args, **kwargs)
|
25
|
+
self.name = "UnauthorizedException"
|
@@ -0,0 +1,106 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
from functools import lru_cache
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
PATH_REGEX = re.compile(r"^\/([^\/\*]+)(\/[^\/\*]+)*(\/\*)?$")
|
8
|
+
|
9
|
+
|
10
|
+
def is_valid_path(path: str) -> bool:
|
11
|
+
"""
|
12
|
+
Checks if a given path is valid based on specific rules.
|
13
|
+
|
14
|
+
Parameters
|
15
|
+
----------
|
16
|
+
path: str
|
17
|
+
The path to validate
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
--------
|
21
|
+
bool:
|
22
|
+
True if the path is valid, False otherwise
|
23
|
+
|
24
|
+
Examples:
|
25
|
+
>>> is_valid_path('/*')
|
26
|
+
True
|
27
|
+
>>> is_valid_path('/users')
|
28
|
+
True
|
29
|
+
>>> is_valid_path('/users/profile')
|
30
|
+
True
|
31
|
+
>>> is_valid_path('/users/*/details')
|
32
|
+
False
|
33
|
+
>>> is_valid_path('/users/*')
|
34
|
+
True
|
35
|
+
>>> is_valid_path('users')
|
36
|
+
False
|
37
|
+
"""
|
38
|
+
return True if path == "/*" else bool(PATH_REGEX.fullmatch(path))
|
39
|
+
|
40
|
+
|
41
|
+
def find_best_route(routes: dict[str, Any], path: str):
|
42
|
+
"""
|
43
|
+
Find the most specific matching route for a given path.
|
44
|
+
|
45
|
+
Examples of matches:
|
46
|
+
Route: /default/v1/* Path: /default/v1/users -> MATCH
|
47
|
+
Route: /default/v1/* Path: /default/v1/users/students -> MATCH
|
48
|
+
Route: /default/v1/users/* Path: /default/v1/users/123 -> MATCH (this wins over /default/v1/*)
|
49
|
+
Route: /* Path: /anything/here -> MATCH (lowest priority)
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
routes: dict[str, Any]
|
54
|
+
Dictionary containing routes and their handlers
|
55
|
+
Format: {
|
56
|
+
'resolvers': {
|
57
|
+
'/path/*': {'func': callable, 'aggregate': bool},
|
58
|
+
'/path/specific/*': {'func': callable, 'aggregate': bool}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
path: str
|
62
|
+
Actual path to match (e.g., '/default/v1/users')
|
63
|
+
|
64
|
+
Returns
|
65
|
+
-------
|
66
|
+
str: Most specific matching route or None if no match
|
67
|
+
"""
|
68
|
+
|
69
|
+
@lru_cache(maxsize=1024)
|
70
|
+
def pattern_to_regex(route):
|
71
|
+
"""
|
72
|
+
Convert a route pattern to a regex pattern with caching.
|
73
|
+
Examples:
|
74
|
+
/default/v1/* -> ^/default/v1/[^/]+$
|
75
|
+
/default/v1/users/* -> ^/default/v1/users/.*$
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
route: str
|
80
|
+
Route pattern with wildcards
|
81
|
+
|
82
|
+
Returns
|
83
|
+
-------
|
84
|
+
Pattern:
|
85
|
+
Compiled regex pattern
|
86
|
+
"""
|
87
|
+
# Escape special regex chars but convert * to regex pattern
|
88
|
+
pattern = re.escape(route).replace("\\*", "[^/]+")
|
89
|
+
|
90
|
+
# If pattern ends with [^/]+, replace with .* for multi-segment match
|
91
|
+
if pattern.endswith("[^/]+"):
|
92
|
+
pattern = pattern[:-6] + ".*"
|
93
|
+
|
94
|
+
# Compile and return the regex pattern
|
95
|
+
return re.compile(f"^{pattern}$")
|
96
|
+
|
97
|
+
# Find all matching routes
|
98
|
+
matches = [route for route in routes.keys() if pattern_to_regex(route).match(path)]
|
99
|
+
|
100
|
+
# Return the most specific route (longest length minus wildcards)
|
101
|
+
# Examples of specificity:
|
102
|
+
# - '/default/v1/users' -> score: 14 (len=14, wildcards=0)
|
103
|
+
# - '/default/v1/users/*' -> score: 14 (len=15, wildcards=1)
|
104
|
+
# - '/default/v1/*' -> score: 8 (len=9, wildcards=1)
|
105
|
+
# - '/*' -> score: 0 (len=2, wildcards=1)
|
106
|
+
return max(matches, key=lambda x: len(x) - x.count("*"), default=None)
|
@@ -0,0 +1,199 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
from aws_lambda_powertools.event_handler.events_appsync._registry import ResolverEventsRegistry
|
6
|
+
from aws_lambda_powertools.event_handler.events_appsync.base import DEFAULT_ROUTE, BaseRouter
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from collections.abc import Callable
|
10
|
+
|
11
|
+
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_events_event import AppSyncResolverEventsEvent
|
12
|
+
from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
|
13
|
+
|
14
|
+
|
15
|
+
class Router(BaseRouter):
|
16
|
+
"""
|
17
|
+
Router for AppSync real-time API event handling.
|
18
|
+
|
19
|
+
This class provides decorators to register resolver functions for publish and subscribe
|
20
|
+
operations in AppSync real-time APIs.
|
21
|
+
|
22
|
+
Parameters
|
23
|
+
----------
|
24
|
+
context : dict
|
25
|
+
Dictionary to store context information accessible across resolvers
|
26
|
+
current_event : AppSyncResolverEventsEvent
|
27
|
+
Current event being processed
|
28
|
+
lambda_context : LambdaContext
|
29
|
+
Lambda context from the AWS Lambda function
|
30
|
+
|
31
|
+
Examples
|
32
|
+
--------
|
33
|
+
Create a router and define resolvers:
|
34
|
+
|
35
|
+
>>> chat_router = Router()
|
36
|
+
>>>
|
37
|
+
>>> # Register a resolver for publish operations
|
38
|
+
>>> @chat_router.on_publish(path="/chat/message")
|
39
|
+
>>> def handle_message(payload):
|
40
|
+
>>> # Process message
|
41
|
+
>>> return {"success": True, "messageId": payload.get("id")}
|
42
|
+
>>>
|
43
|
+
>>> # Register an async resolver for publish operations
|
44
|
+
>>> @chat_router.async_on_publish(path="/chat/typing")
|
45
|
+
>>> async def handle_typing(event):
|
46
|
+
>>> # Process typing indicator
|
47
|
+
>>> await some_async_operation()
|
48
|
+
>>> return {"processed": True}
|
49
|
+
>>>
|
50
|
+
>>> # Register a resolver for subscribe operations
|
51
|
+
>>> @chat_router.on_subscribe(path="/chat/room/*")
|
52
|
+
>>> def handle_subscribe(event):
|
53
|
+
>>> # Handle subscription setup
|
54
|
+
>>> return {"allowed": True}
|
55
|
+
"""
|
56
|
+
|
57
|
+
context: dict
|
58
|
+
current_event: AppSyncResolverEventsEvent
|
59
|
+
lambda_context: LambdaContext
|
60
|
+
|
61
|
+
def __init__(self):
|
62
|
+
"""
|
63
|
+
Initialize a new Router instance.
|
64
|
+
|
65
|
+
Sets up empty context and registry containers for different types of resolvers.
|
66
|
+
"""
|
67
|
+
self.context = {} # early init as customers might add context before event resolution
|
68
|
+
self._publish_registry = ResolverEventsRegistry(kind_resolver="on_publish")
|
69
|
+
self._async_publish_registry = ResolverEventsRegistry(kind_resolver="async_on_publish")
|
70
|
+
self._subscribe_registry = ResolverEventsRegistry(kind_resolver="on_subscribe")
|
71
|
+
|
72
|
+
def on_publish(
|
73
|
+
self,
|
74
|
+
path: str = DEFAULT_ROUTE,
|
75
|
+
aggregate: bool = False,
|
76
|
+
) -> Callable:
|
77
|
+
"""
|
78
|
+
Register a resolver function for publish operations.
|
79
|
+
|
80
|
+
Parameters
|
81
|
+
----------
|
82
|
+
path : str, optional
|
83
|
+
The channel path pattern to match for this resolver, by default "/default/*"
|
84
|
+
aggregate : bool, optional
|
85
|
+
Whether to process events in aggregate (batch) mode, by default False
|
86
|
+
|
87
|
+
Returns
|
88
|
+
-------
|
89
|
+
Callable
|
90
|
+
Decorator function that registers the resolver
|
91
|
+
|
92
|
+
Examples
|
93
|
+
--------
|
94
|
+
>>> router = Router()
|
95
|
+
>>>
|
96
|
+
>>> # Basic usage
|
97
|
+
>>> @router.on_publish(path="/notifications/new")
|
98
|
+
>>> def handle_notification(payload):
|
99
|
+
>>> # Process a single notification
|
100
|
+
>>> return {"processed": True, "notificationId": payload.get("id")}
|
101
|
+
>>>
|
102
|
+
>>> # Aggregate mode for batch processing
|
103
|
+
>>> @router.on_publish(path="/notifications/batch", aggregate=True)
|
104
|
+
>>> def handle_batch_notifications(payload):
|
105
|
+
>>> # Process multiple notifications at once
|
106
|
+
>>> results = []
|
107
|
+
>>> for item in payload:
|
108
|
+
>>> # Process each item
|
109
|
+
>>> results.append({"processed": True, "id": item.get("id")})
|
110
|
+
>>> return results
|
111
|
+
"""
|
112
|
+
return self._publish_registry.register(path=path, aggregate=aggregate)
|
113
|
+
|
114
|
+
def async_on_publish(
|
115
|
+
self,
|
116
|
+
path: str = DEFAULT_ROUTE,
|
117
|
+
aggregate: bool = False,
|
118
|
+
) -> Callable:
|
119
|
+
"""
|
120
|
+
Register an asynchronous resolver function for publish operations.
|
121
|
+
|
122
|
+
Parameters
|
123
|
+
----------
|
124
|
+
path : str, optional
|
125
|
+
The channel path pattern to match for this resolver, by default "/default/*"
|
126
|
+
aggregate : bool, optional
|
127
|
+
Whether to process events in aggregate (batch) mode, by default False
|
128
|
+
|
129
|
+
Returns
|
130
|
+
-------
|
131
|
+
Callable
|
132
|
+
Decorator function that registers the async resolver
|
133
|
+
|
134
|
+
Examples
|
135
|
+
--------
|
136
|
+
>>> router = Router()
|
137
|
+
>>>
|
138
|
+
>>> # Basic async usage
|
139
|
+
>>> @router.async_on_publish(path="/messages/send")
|
140
|
+
>>> async def handle_message(event):
|
141
|
+
>>> # Perform async operations
|
142
|
+
>>> result = await database.save_message(event)
|
143
|
+
>>> return {"saved": True, "messageId": result.id}
|
144
|
+
>>>
|
145
|
+
>>> # Aggregate mode for batch processing
|
146
|
+
>>> @router.async_on_publish(path="/messages/batch", aggregate=True)
|
147
|
+
>>> async def handle_batch_messages(events):
|
148
|
+
>>> # Process multiple messages asynchronously
|
149
|
+
>>> tasks = [database.save_message(e) for e in events]
|
150
|
+
>>> results = await asyncio.gather(*tasks)
|
151
|
+
>>> return [{"saved": True, "id": r.id} for r in results]
|
152
|
+
"""
|
153
|
+
return self._async_publish_registry.register(path=path, aggregate=aggregate)
|
154
|
+
|
155
|
+
def on_subscribe(
|
156
|
+
self,
|
157
|
+
path: str = DEFAULT_ROUTE,
|
158
|
+
) -> Callable:
|
159
|
+
"""
|
160
|
+
Register a resolver function for subscribe operations.
|
161
|
+
|
162
|
+
Parameters
|
163
|
+
----------
|
164
|
+
path : str, optional
|
165
|
+
The channel path pattern to match for this resolver, by default "/default/*"
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
Callable
|
170
|
+
Decorator function that registers the resolver
|
171
|
+
|
172
|
+
Examples
|
173
|
+
--------
|
174
|
+
>>> router = Router()
|
175
|
+
>>>
|
176
|
+
>>> # Handle subscription request
|
177
|
+
>>> @router.on_subscribe(path="/chat/room/*")
|
178
|
+
>>> def authorize_subscription(event):
|
179
|
+
>>> # Verify if the client can subscribe to this room
|
180
|
+
>>> room_id = event.info.channel_path.split('/')[-1]
|
181
|
+
>>> user_id = event.identity.username
|
182
|
+
>>>
|
183
|
+
>>> # Check if user is allowed in this room
|
184
|
+
>>> is_allowed = check_permission(user_id, room_id)
|
185
|
+
>>>
|
186
|
+
>>> return {
|
187
|
+
>>> "allowed": is_allowed,
|
188
|
+
>>> "roomId": room_id
|
189
|
+
>>> }
|
190
|
+
"""
|
191
|
+
return self._subscribe_registry.register(path=path)
|
192
|
+
|
193
|
+
def append_context(self, **additional_context):
|
194
|
+
"""Append key=value data as routing context"""
|
195
|
+
self.context.update(**additional_context)
|
196
|
+
|
197
|
+
def clear_context(self):
|
198
|
+
"""Resets routing context"""
|
199
|
+
self.context.clear()
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from collections.abc import Callable
|
7
|
+
|
8
|
+
|
9
|
+
class ResolverTypeDef(TypedDict):
|
10
|
+
"""
|
11
|
+
Type definition for resolver dictionary
|
12
|
+
Parameters
|
13
|
+
----------
|
14
|
+
func: Callable[..., Any]
|
15
|
+
Resolver function
|
16
|
+
aggregate: bool
|
17
|
+
Aggregation flag or method
|
18
|
+
"""
|
19
|
+
|
20
|
+
func: Callable[..., Any]
|
21
|
+
aggregate: bool
|
@@ -0,0 +1,118 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Mapping
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from collections.abc import Callable
|
7
|
+
|
8
|
+
|
9
|
+
class ExceptionHandlerManager:
|
10
|
+
"""
|
11
|
+
A class to manage exception handlers for different exception types.
|
12
|
+
This class allows registering handler functions for specific exception types
|
13
|
+
and looking up the appropriate handler when an exception occurs.
|
14
|
+
Example usage:
|
15
|
+
-------------
|
16
|
+
handler_manager = ExceptionHandlerManager()
|
17
|
+
@handler_manager.exception_handler(ValueError)
|
18
|
+
def handle_value_error(e):
|
19
|
+
print(f"Handling ValueError: {e}")
|
20
|
+
return "Error handled"
|
21
|
+
# To handle multiple exception types with the same handler:
|
22
|
+
@handler_manager.exception_handler([KeyError, TypeError])
|
23
|
+
def handle_multiple_errors(e):
|
24
|
+
print(f"Handling {type(e).__name__}: {e}")
|
25
|
+
return "Multiple error types handled"
|
26
|
+
# To find and execute a handler:
|
27
|
+
try:
|
28
|
+
# some code that might raise an exception
|
29
|
+
raise ValueError("Invalid value")
|
30
|
+
except Exception as e:
|
31
|
+
handler = handler_manager.lookup_exception_handler(type(e))
|
32
|
+
if handler:
|
33
|
+
result = handler(e)
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize an empty dictionary to store exception handlers."""
|
38
|
+
self._exception_handlers: dict[type[Exception], Callable] = {}
|
39
|
+
|
40
|
+
def exception_handler(self, exc_class: type[Exception] | list[type[Exception]]):
|
41
|
+
"""
|
42
|
+
A decorator function that registers a handler for one or more exception types.
|
43
|
+
Parameters
|
44
|
+
----------
|
45
|
+
exc_class : type[Exception] | list[type[Exception]]
|
46
|
+
A single exception type or a list of exception types.
|
47
|
+
Returns
|
48
|
+
-------
|
49
|
+
Callable
|
50
|
+
A decorator function that registers the exception handler.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def register_exception_handler(func: Callable):
|
54
|
+
if isinstance(exc_class, list):
|
55
|
+
for exp in exc_class:
|
56
|
+
self._exception_handlers[exp] = func
|
57
|
+
else:
|
58
|
+
self._exception_handlers[exc_class] = func
|
59
|
+
return func
|
60
|
+
|
61
|
+
return register_exception_handler
|
62
|
+
|
63
|
+
def lookup_exception_handler(self, exp_type: type) -> Callable | None:
|
64
|
+
"""
|
65
|
+
Looks up the registered exception handler for the given exception type or its base classes.
|
66
|
+
Parameters
|
67
|
+
----------
|
68
|
+
exp_type : type
|
69
|
+
The exception type to look up the handler for.
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
Callable | None
|
73
|
+
The registered exception handler function if found, otherwise None.
|
74
|
+
"""
|
75
|
+
for cls in exp_type.__mro__:
|
76
|
+
if cls in self._exception_handlers:
|
77
|
+
return self._exception_handlers[cls]
|
78
|
+
return None
|
79
|
+
|
80
|
+
def update_exception_handlers(self, handlers: Mapping[type[Exception], Callable]) -> None:
|
81
|
+
"""
|
82
|
+
Updates the exception handlers dictionary with new handler mappings.
|
83
|
+
This method allows bulk updates of exception handlers by providing a dictionary
|
84
|
+
mapping exception types to handler functions.
|
85
|
+
Parameters
|
86
|
+
----------
|
87
|
+
handlers : Mapping[Type[Exception], Callable]
|
88
|
+
A dictionary mapping exception types to handler functions.
|
89
|
+
Example
|
90
|
+
-------
|
91
|
+
>>> def handle_value_error(e):
|
92
|
+
... print(f"Value error: {e}")
|
93
|
+
...
|
94
|
+
>>> def handle_key_error(e):
|
95
|
+
... print(f"Key error: {e}")
|
96
|
+
...
|
97
|
+
>>> handler_manager.update_exception_handlers({
|
98
|
+
... ValueError: handle_value_error,
|
99
|
+
... KeyError: handle_key_error
|
100
|
+
... })
|
101
|
+
"""
|
102
|
+
self._exception_handlers.update(handlers)
|
103
|
+
|
104
|
+
def get_registered_handlers(self) -> dict[type[Exception], Callable]:
|
105
|
+
"""
|
106
|
+
Returns all registered exception handlers.
|
107
|
+
Returns
|
108
|
+
-------
|
109
|
+
Dict[Type[Exception], Callable]
|
110
|
+
A dictionary mapping exception types to their handler functions.
|
111
|
+
"""
|
112
|
+
return self._exception_handlers.copy()
|
113
|
+
|
114
|
+
def clear_handlers(self) -> None:
|
115
|
+
"""
|
116
|
+
Clears all registered exception handlers.
|
117
|
+
"""
|
118
|
+
self._exception_handlers.clear()
|
@@ -6,6 +6,7 @@ from .alb_event import ALBEvent
|
|
6
6
|
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
|
7
7
|
from .api_gateway_websocket_event import APIGatewayWebSocketEvent
|
8
8
|
from .appsync_resolver_event import AppSyncResolverEvent
|
9
|
+
from .appsync_resolver_events_event import AppSyncResolverEventsEvent
|
9
10
|
from .aws_config_rule_event import AWSConfigRuleEvent
|
10
11
|
from .bedrock_agent_event import BedrockAgentEvent
|
11
12
|
from .cloud_watch_alarm_event import (
|
@@ -55,6 +56,7 @@ __all__ = [
|
|
55
56
|
"APIGatewayWebSocketEvent",
|
56
57
|
"SecretsManagerEvent",
|
57
58
|
"AppSyncResolverEvent",
|
59
|
+
"AppSyncResolverEventsEvent",
|
58
60
|
"ALBEvent",
|
59
61
|
"BedrockAgentEvent",
|
60
62
|
"CloudWatchAlarmData",
|
@@ -26,6 +26,46 @@ def get_identity_object(identity: dict | None) -> Any:
|
|
26
26
|
return AppSyncIdentityIAM(identity)
|
27
27
|
|
28
28
|
|
29
|
+
class AppSyncEventBase(DictWrapper):
|
30
|
+
"""AppSync resolver event base to work with AppSync GraphQL + Events"""
|
31
|
+
|
32
|
+
@property
|
33
|
+
def request_headers(self) -> dict[str, str]:
|
34
|
+
"""Request headers"""
|
35
|
+
return CaseInsensitiveDict(self["request"]["headers"])
|
36
|
+
|
37
|
+
@property
|
38
|
+
def domain_name(self) -> str | None:
|
39
|
+
"""The domain name when using custom domain"""
|
40
|
+
return self["request"].get("domainName")
|
41
|
+
|
42
|
+
@property
|
43
|
+
def prev_result(self) -> dict[str, Any] | None:
|
44
|
+
"""It represents the result of whatever previous operation was executed in a pipeline resolver."""
|
45
|
+
prev = self.get("prev")
|
46
|
+
return prev.get("result") if prev else None
|
47
|
+
|
48
|
+
@property
|
49
|
+
def stash(self) -> dict:
|
50
|
+
"""The stash is a map that is made available inside each resolver and function mapping template.
|
51
|
+
The same stash instance lives through a single resolver execution. This means that you can use the
|
52
|
+
stash to pass arbitrary data across request and response mapping templates, and across functions in
|
53
|
+
a pipeline resolver."""
|
54
|
+
return self.get("stash") or {}
|
55
|
+
|
56
|
+
@property
|
57
|
+
def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
|
58
|
+
"""An object that contains information about the caller.
|
59
|
+
Depending on the type of identify found:
|
60
|
+
- API_KEY authorization - returns None
|
61
|
+
- AWS_IAM authorization - returns AppSyncIdentityIAM
|
62
|
+
- AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
|
63
|
+
- AWS_LAMBDA authorization - returns None - NEED TO TEST
|
64
|
+
- OPENID_CONNECT authorization - returns None - NEED TO TEST
|
65
|
+
"""
|
66
|
+
return get_identity_object(self.get("identity"))
|
67
|
+
|
68
|
+
|
29
69
|
class AppSyncIdentityIAM(DictWrapper):
|
30
70
|
"""AWS_IAM authorization"""
|
31
71
|
|
@@ -141,7 +181,7 @@ class AppSyncResolverEventInfo(DictWrapper):
|
|
141
181
|
return self.get("selectionSetGraphQL")
|
142
182
|
|
143
183
|
|
144
|
-
class AppSyncResolverEvent(
|
184
|
+
class AppSyncResolverEvent(AppSyncEventBase):
|
145
185
|
"""AppSync resolver event
|
146
186
|
|
147
187
|
**NOTE:** AppSync Resolver Events can come in various shapes this data class
|
@@ -178,49 +218,16 @@ class AppSyncResolverEvent(DictWrapper):
|
|
178
218
|
"""A map that contains all GraphQL arguments for this field."""
|
179
219
|
return self["arguments"]
|
180
220
|
|
181
|
-
@property
|
182
|
-
def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
|
183
|
-
"""An object that contains information about the caller.
|
184
|
-
|
185
|
-
Depending on the type of identify found:
|
186
|
-
|
187
|
-
- API_KEY authorization - returns None
|
188
|
-
- AWS_IAM authorization - returns AppSyncIdentityIAM
|
189
|
-
- AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
|
190
|
-
"""
|
191
|
-
return get_identity_object(self.get("identity"))
|
192
|
-
|
193
221
|
@property
|
194
222
|
def source(self) -> dict[str, Any]:
|
195
223
|
"""A map that contains the resolution of the parent field."""
|
196
224
|
return self.get("source") or {}
|
197
225
|
|
198
|
-
@property
|
199
|
-
def request_headers(self) -> dict[str, str]:
|
200
|
-
"""Request headers"""
|
201
|
-
return CaseInsensitiveDict(self["request"]["headers"])
|
202
|
-
|
203
|
-
@property
|
204
|
-
def prev_result(self) -> dict[str, Any] | None:
|
205
|
-
"""It represents the result of whatever previous operation was executed in a pipeline resolver."""
|
206
|
-
prev = self.get("prev")
|
207
|
-
if not prev:
|
208
|
-
return None
|
209
|
-
return prev.get("result")
|
210
|
-
|
211
226
|
@property
|
212
227
|
def info(self) -> AppSyncResolverEventInfo:
|
213
228
|
"""The info section contains information about the GraphQL request."""
|
214
229
|
return self._info
|
215
230
|
|
216
|
-
@property
|
217
|
-
def stash(self) -> dict:
|
218
|
-
"""The stash is a map that is made available inside each resolver and function mapping template.
|
219
|
-
The same stash instance lives through a single resolver execution. This means that you can use the
|
220
|
-
stash to pass arbitrary data across request and response mapping templates, and across functions in
|
221
|
-
a pipeline resolver."""
|
222
|
-
return self.get("stash") or {}
|
223
|
-
|
224
231
|
@overload
|
225
232
|
def get_header_value(
|
226
233
|
self,
|