Skip to content

Commit 8caa1d3

Browse files
refactoring
1 parent 023cfc1 commit 8caa1d3

File tree

5 files changed

+70
-90
lines changed

5 files changed

+70
-90
lines changed

aws_lambda_powertools/event_handler/appsync_events.py

Lines changed: 9 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import TYPE_CHECKING, Any, Callable
66

77
from aws_lambda_powertools.event_handler.events_appsync.router import Router
8-
from aws_lambda_powertools.event_handler.exception_handling import ExceptionHandlerManager
98
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_events_event import AppSyncResolverEventsEvent
109

1110
if TYPE_CHECKING:
@@ -20,52 +19,43 @@ class AppSyncEventsResolver(Router):
2019
def __init__(self):
2120
super().__init__()
2221
self.context = {} # early init as customers might add context before event resolution
23-
self.exception_handler_manager = ExceptionHandlerManager()
2422
self._exception_handlers: dict[type, Callable] = {}
2523

2624
def __call__(
2725
self,
2826
event: dict,
2927
context: LambdaContext,
30-
data_model: type[AppSyncResolverEventsEvent] = AppSyncResolverEventsEvent,
3128
) -> Any:
3229
"""Implicit lambda handler which internally calls `resolve`"""
33-
return self.resolve(event, context, data_model)
30+
return self.resolve(event, context)
3431

3532
def resolve(
3633
self,
3734
event: AppSyncResolverEventsEvent,
3835
context: LambdaContext,
39-
data_model: type[AppSyncResolverEventsEvent] = AppSyncResolverEventsEvent,
4036
) -> Any:
4137
"""Resolves the response based on the provide event and decorator operation and namespaces"""
4238

4339
self.lambda_context = context
4440
Router.lambda_context = context
4541

46-
Router.current_event = data_model(event)
47-
self.current_event = data_model(event)
42+
Router.current_event = AppSyncResolverEventsEvent(event)
43+
self.current_event = Router.current_event
4844

49-
try:
50-
if self.current_event.info.operation == "PUBLISH":
51-
response = self._call_publish_events(payload=self.current_event.events, data_model=data_model)
52-
else:
53-
response = self._call_subscribe_events(event=event, data_model=data_model)
54-
except Exception as exp:
55-
response_builder = self.exception_handler_manager.lookup_exception_handler(type(exp))
56-
if response_builder:
57-
return response_builder(exp)
58-
raise
45+
if self.current_event.info.operation == "PUBLISH":
46+
response = self._call_publish_events(payload=self.current_event.events)
47+
48+
response = self._call_subscribe_events()
5949

6050
self.clear_context()
6151

6252
return response
6353

64-
def _call_subscribe_events(self, payload: list[dict[str, Any]], data_model: type[AppSyncResolverEventsEvent]) -> Any:
54+
def _call_subscribe_events(self) -> Any:
6555
# PLACEHOLDER
6656
pass
6757

68-
def _call_publish_events(self, payload: list[dict[str, Any]], data_model: type[AppSyncResolverEventsEvent]) -> Any:
58+
def _call_publish_events(self, payload: list[dict[str, Any]]) -> Any:
6959
"""Call single event resolver
7060
7161
Parameters
@@ -92,46 +82,3 @@ def _call_publish_events(self, payload: list[dict[str, Any]], data_model: type[A
9282
result.append(resolver["func"](payload=i))
9383

9484
return result
95-
96-
def on_publish(
97-
self,
98-
path: str = "/default/*",
99-
aggregate: bool = True,
100-
) -> Callable:
101-
return self._publish_registry.register(path=path, aggregate=aggregate)
102-
103-
def async_on_publish(
104-
self,
105-
path: str = "/default/*",
106-
aggregate: bool = True,
107-
) -> Callable:
108-
return self._async_publish_registry.register(path=path, aggregate=aggregate)
109-
110-
def on_subscribe(
111-
self,
112-
path: str = "/default/*",
113-
) -> Callable:
114-
return self._subscribe_registry.register(path=path)
115-
116-
def async_on_subscribe(
117-
self,
118-
path: str = "/default/*",
119-
) -> Callable:
120-
return self._async_subscribe_registry.register(path=path)
121-
122-
def exception_handler(self, exc_class: type[Exception] | list[type[Exception]]):
123-
"""
124-
A decorator function that registers a handler for one or more exception types.
125-
126-
Parameters
127-
----------
128-
exc_class (type[Exception] | list[type[Exception]])
129-
A single exception type or a list of exception types.
130-
131-
Returns
132-
-------
133-
Callable:
134-
A decorator function that registers the exception handler.
135-
"""
136-
137-
return self.exception_handler_manager.exception_handler(exc_class=exc_class)

aws_lambda_powertools/event_handler/events_appsync/_registry.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import Any, Callable
4+
import warnings
5+
from typing import Any, Callable, Literal
56

6-
from aws_lambda_powertools.event_handler.events_appsync.functions import find_best_route
7+
from aws_lambda_powertools.event_handler.events_appsync.functions import find_best_route, is_valid_path
8+
from aws_lambda_powertools.warnings import PowertoolsUserWarning
79

810
logger = logging.getLogger(__name__)
911

10-
1112
class ResolverEventsRegistry:
1213
def __init__(self):
1314
self.resolvers: dict[str, dict[str, Any]] = {}
1415

1516
def register(
1617
self,
1718
path: str = "/default/*",
18-
aggregate: bool = True,
19+
aggregate: bool = False,
20+
operation: Literal["on_publish", "async_on_publish", "on_subscribe"] = "on_publish",
1921
) -> Callable:
2022
"""Registers the resolver for path that includes namespace + channel
2123
@@ -25,17 +27,27 @@ def register(
2527
Path including namespace + channel
2628
aggregate: bool
2729
A flag indicating whether the batch items should be processed at once or individually.
28-
If True (default), the batch resolver will process all items in the batch as a single event.
29-
If False, the batch resolver will process each item in the batch individually.
30+
If True , the batch resolver will process all items in the batch as a single event.
31+
If False (default), the batch resolver will process each item in the batch individually.
3032
3133
Return
3234
----------
3335
Callable
3436
A Callable
3537
"""
38+
if not is_valid_path(path):
39+
warnings.warn(
40+
f"The path `{path}` registered for `{operation}` is not valid and will be skipped."
41+
f"A path should always have a namespace starting with '/'"
42+
"A path can have multiple namespaces, all separated by '/'."
43+
"Wildcards are allowed only at the end of the path.",
44+
stacklevel=2,
45+
category=PowertoolsUserWarning,
46+
)
47+
3648

3749
def _register(func) -> Callable:
38-
logger.debug(f"Adding resolver `{func.__name__}` for path `{path}`")
50+
logger.debug(f"Adding resolver `{func.__name__}` for path `{path}` and event `{operation}`")
3951
self.resolvers[f"{path}"] = {
4052
"func": func,
4153
"aggregate": aggregate,

aws_lambda_powertools/event_handler/events_appsync/base.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ def on_subscribe(
3030
) -> Callable:
3131
raise NotImplementedError
3232

33-
@abstractmethod
34-
def async_on_subscribe(
35-
self,
36-
path: str = "/default/*",
37-
) -> Callable:
38-
raise NotImplementedError
39-
@abstractmethod
4033
def append_context(self, **additional_context) -> None:
4134
"""
4235
Appends context information available under any route.

aws_lambda_powertools/event_handler/events_appsync/functions.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,39 @@
44
from functools import lru_cache
55
from typing import Any
66

7+
PATH_REGEX = re.compile(r"^\/([^\/\*]+)(\/[^\/\*]+)*(\/\*)?$")
8+
9+
def is_valid_path(path: str) -> bool:
10+
"""
11+
Checks if a given path is valid based on specific rules.
12+
13+
Parameters
14+
----------
15+
path: str
16+
The path to validate
17+
18+
Returns:
19+
--------
20+
bool:
21+
True if the path is valid, False otherwise
22+
23+
Examples:
24+
>>> is_valid_path('/*')
25+
True
26+
>>> is_valid_path('/users')
27+
True
28+
>>> is_valid_path('/users/profile')
29+
True
30+
>>> is_valid_path('/users/*/details')
31+
False
32+
>>> is_valid_path('/users/*')
33+
True
34+
>>> is_valid_path('users')
35+
False
36+
"""
37+
if path == "/*":
38+
return True
39+
return bool(PATH_REGEX.fullmatch(path))
740

841
def find_best_route(routes: dict[str, Any], path: str):
942
"""
@@ -42,11 +75,13 @@ def pattern_to_regex(route):
4275
4376
Parameters
4477
----------
45-
route (str): Route pattern with wildcards
78+
route: str
79+
Route pattern with wildcards
4680
4781
Returns
4882
-------
49-
Pattern: Compiled regex pattern
83+
Pattern:
84+
Compiled regex pattern
5085
"""
5186
# Escape special regex chars but convert * to regex pattern
5287
pattern = re.escape(route).replace("\\*", "[^/]+")

aws_lambda_powertools/event_handler/events_appsync/router.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,26 @@ def __init__(self):
2222
self._publish_registry = ResolverEventsRegistry()
2323
self._async_publish_registry = ResolverEventsRegistry()
2424
self._subscribe_registry = ResolverEventsRegistry()
25-
self._async_subscribe_registry = ResolverEventsRegistry()
2625

2726
def on_publish(
2827
self,
2928
path: str = "/default/*",
30-
aggregate: bool = True,
29+
aggregate: bool = False,
3130
) -> Callable:
32-
return self._publish_registry.register(path=path, aggregate=aggregate)
31+
return self._publish_registry.register(path=path, aggregate=aggregate, operation="on_publish")
3332

3433
def async_on_publish(
3534
self,
3635
path: str = "/default/*",
37-
aggregate: bool = True,
36+
aggregate: bool = False,
3837
) -> Callable:
39-
return self._async_publish_registry.register(path=path, aggregate=aggregate)
38+
return self._async_publish_registry.register(path=path, aggregate=aggregate, operation="async_on_publish")
4039

4140
def on_subscribe(
4241
self,
4342
path: str = "/default/*",
4443
) -> Callable:
45-
return self._subscribe_registry.register(path=path)
46-
47-
def async_on_subscribe(
48-
self,
49-
path: str = "/default/*",
50-
) -> Callable:
51-
return self._async_subscribe_registry.register(path=path)
44+
return self._subscribe_registry.register(path=path, operation="on_subscribe")
5245

5346
def append_context(self, **additional_context):
5447
"""Append key=value data as routing context"""

0 commit comments

Comments
 (0)