Skip to content

Commit dcab235

Browse files
committed
Merge remote-tracking branch 'michaelbrewer/docs/router' into docs/router
* michaelbrewer/docs/router: feat(data-classes): ActiveMQ and RabbitMQ support (aws-powertools#770) feat(appsync): add Router to allow large resolver composition (aws-powertools#776) chore(deps-dev): bump mkdocs-material from 7.3.3 to 7.3.5 (aws-powertools#781) chore(deps-dev): bump flake8-isort from 4.0.0 to 4.1.1 (aws-powertools#785) chore(deps): bump urllib3 from 1.26.4 to 1.26.5 (aws-powertools#787) chore(deps-dev): bump flake8-eradicate from 1.1.0 to 1.2.0 (aws-powertools#784) chore(deps): bump boto3 from 1.18.61 to 1.19.6 (aws-powertools#783) chore(deps-dev): bump pytest-asyncio from 0.15.1 to 0.16.0 (aws-powertools#782) docs: fix indentation of SAM snippets in install section (aws-powertools#778) Fix middleware sample (aws-powertools#772) Removed unused import, added typing imports, fixed typo in example. (aws-powertools#774) Fix middleware sample (aws-powertools#772) Removed unused import, added typing imports, fixed typo in example. (aws-powertools#774) Update docs/core/event_handler/api_gateway.md # Conflicts: # docs/core/event_handler/api_gateway.md
2 parents 3632d18 + 94493dc commit dcab235

File tree

13 files changed

+608
-79
lines changed

13 files changed

+608
-79
lines changed

Diff for: aws_lambda_powertools/event_handler/appsync.py

+48-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from abc import ABC
23
from typing import Any, Callable, Optional, Type, TypeVar
34

45
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
@@ -9,7 +10,33 @@
910
AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent)
1011

1112

12-
class AppSyncResolver:
13+
class BaseRouter(ABC):
14+
current_event: AppSyncResolverEventT # type: ignore[valid-type]
15+
lambda_context: LambdaContext
16+
17+
def __init__(self):
18+
self._resolvers: dict = {}
19+
20+
def resolver(self, type_name: str = "*", field_name: Optional[str] = None):
21+
"""Registers the resolver for field_name
22+
23+
Parameters
24+
----------
25+
type_name : str
26+
Type name
27+
field_name : str
28+
Field name
29+
"""
30+
31+
def register_resolver(func):
32+
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
33+
self._resolvers[f"{type_name}.{field_name}"] = {"func": func}
34+
return func
35+
36+
return register_resolver
37+
38+
39+
class AppSyncResolver(BaseRouter):
1340
"""
1441
AppSync resolver decorator
1542
@@ -40,29 +67,8 @@ def common_field() -> str:
4067
return str(uuid.uuid4())
4168
"""
4269

43-
current_event: AppSyncResolverEventT # type: ignore[valid-type]
44-
lambda_context: LambdaContext
45-
4670
def __init__(self):
47-
self._resolvers: dict = {}
48-
49-
def resolver(self, type_name: str = "*", field_name: Optional[str] = None):
50-
"""Registers the resolver for field_name
51-
52-
Parameters
53-
----------
54-
type_name : str
55-
Type name
56-
field_name : str
57-
Field name
58-
"""
59-
60-
def register_resolver(func):
61-
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
62-
self._resolvers[f"{type_name}.{field_name}"] = {"func": func}
63-
return func
64-
65-
return register_resolver
71+
super().__init__()
6672

6773
def resolve(
6874
self, event: dict, context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent
@@ -136,10 +142,10 @@ def lambda_handler(event, context):
136142
ValueError
137143
If we could not find a field resolver
138144
"""
139-
self.current_event = data_model(event)
140-
self.lambda_context = context
141-
resolver = self._get_resolver(self.current_event.type_name, self.current_event.field_name)
142-
return resolver(**self.current_event.arguments)
145+
BaseRouter.current_event = data_model(event)
146+
BaseRouter.lambda_context = context
147+
resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name)
148+
return resolver(**BaseRouter.current_event.arguments)
143149

144150
def _get_resolver(self, type_name: str, field_name: str) -> Callable:
145151
"""Get resolver for field_name
@@ -167,3 +173,18 @@ def __call__(
167173
) -> Any:
168174
"""Implicit lambda handler which internally calls `resolve`"""
169175
return self.resolve(event, context, data_model)
176+
177+
def include_router(self, router: "Router") -> None:
178+
"""Adds all resolvers defined in a router
179+
180+
Parameters
181+
----------
182+
router : Router
183+
A router containing a dict of field resolvers
184+
"""
185+
self._resolvers.update(router._resolvers)
186+
187+
188+
class Router(BaseRouter):
189+
def __init__(self):
190+
super().__init__()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import base64
2+
import json
3+
from typing import Any, Iterator, Optional
4+
5+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
6+
7+
8+
class ActiveMQMessage(DictWrapper):
9+
@property
10+
def message_id(self) -> str:
11+
"""Unique identifier for the message"""
12+
return self["messageID"]
13+
14+
@property
15+
def message_type(self) -> str:
16+
return self["messageType"]
17+
18+
@property
19+
def data(self) -> str:
20+
return self["data"]
21+
22+
@property
23+
def decoded_data(self) -> str:
24+
"""Decodes the data as a str"""
25+
return base64.b64decode(self.data.encode()).decode()
26+
27+
@property
28+
def json_data(self) -> Any:
29+
"""Parses the data as json"""
30+
return json.loads(self.decoded_data)
31+
32+
@property
33+
def connection_id(self) -> str:
34+
return self["connectionId"]
35+
36+
@property
37+
def redelivered(self) -> bool:
38+
"""true if the message is being resent to the consumer"""
39+
return self["redelivered"]
40+
41+
@property
42+
def timestamp(self) -> int:
43+
"""Time in milliseconds."""
44+
return self["timestamp"]
45+
46+
@property
47+
def broker_in_time(self) -> int:
48+
"""Time stamp (in milliseconds) for when the message arrived at the broker."""
49+
return self["brokerInTime"]
50+
51+
@property
52+
def broker_out_time(self) -> int:
53+
"""Time stamp (in milliseconds) for when the message left the broker."""
54+
return self["brokerOutTime"]
55+
56+
@property
57+
def destination_physicalname(self) -> str:
58+
return self["destination"]["physicalname"]
59+
60+
@property
61+
def delivery_mode(self) -> Optional[int]:
62+
"""persistent or non-persistent delivery"""
63+
return self.get("deliveryMode")
64+
65+
@property
66+
def correlation_id(self) -> Optional[str]:
67+
"""User defined correlation id"""
68+
return self.get("correlationID")
69+
70+
@property
71+
def reply_to(self) -> Optional[str]:
72+
"""User defined reply to"""
73+
return self.get("replyTo")
74+
75+
@property
76+
def get_type(self) -> Optional[str]:
77+
"""User defined message type"""
78+
return self.get("type")
79+
80+
@property
81+
def expiration(self) -> Optional[int]:
82+
"""Expiration attribute whose value is given in milliseconds"""
83+
return self.get("expiration")
84+
85+
@property
86+
def priority(self) -> Optional[int]:
87+
"""
88+
JMS defines a ten-level priority value, with 0 as the lowest priority and 9
89+
as the highest. In addition, clients should consider priorities 0-4 as
90+
gradations of normal priority and priorities 5-9 as gradations of expedited
91+
priority.
92+
93+
JMS does not require that a provider strictly implement priority ordering
94+
of messages; however, it should do its best to deliver expedited messages
95+
ahead of normal messages.
96+
"""
97+
return self.get("priority")
98+
99+
100+
class ActiveMQEvent(DictWrapper):
101+
"""Represents an Active MQ event sent to Lambda
102+
103+
Documentation:
104+
--------------
105+
- https://docs.aws.amazon.com/lambda/latest/dg/with-mq.html
106+
- https://aws.amazon.com/blogs/compute/using-amazon-mq-as-an-event-source-for-aws-lambda/
107+
"""
108+
109+
@property
110+
def event_source(self) -> str:
111+
return self["eventSource"]
112+
113+
@property
114+
def event_source_arn(self) -> str:
115+
"""The Amazon Resource Name (ARN) of the event source"""
116+
return self["eventSourceArn"]
117+
118+
@property
119+
def messages(self) -> Iterator[ActiveMQMessage]:
120+
for record in self["messages"]:
121+
yield ActiveMQMessage(record)
122+
123+
@property
124+
def message(self) -> ActiveMQMessage:
125+
return next(self.messages)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import base64
2+
import json
3+
from typing import Any, Dict, List
4+
5+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
6+
7+
8+
class BasicProperties(DictWrapper):
9+
@property
10+
def content_type(self) -> str:
11+
return self["contentType"]
12+
13+
@property
14+
def content_encoding(self) -> str:
15+
return self["contentEncoding"]
16+
17+
@property
18+
def headers(self) -> Dict[str, Any]:
19+
return self["headers"]
20+
21+
@property
22+
def delivery_mode(self) -> int:
23+
return self["deliveryMode"]
24+
25+
@property
26+
def priority(self) -> int:
27+
return self["priority"]
28+
29+
@property
30+
def correlation_id(self) -> str:
31+
return self["correlationId"]
32+
33+
@property
34+
def reply_to(self) -> str:
35+
return self["replyTo"]
36+
37+
@property
38+
def expiration(self) -> str:
39+
return self["expiration"]
40+
41+
@property
42+
def message_id(self) -> str:
43+
return self["messageId"]
44+
45+
@property
46+
def timestamp(self) -> str:
47+
return self["timestamp"]
48+
49+
@property
50+
def get_type(self) -> str:
51+
return self["type"]
52+
53+
@property
54+
def user_id(self) -> str:
55+
return self["userId"]
56+
57+
@property
58+
def app_id(self) -> str:
59+
return self["appId"]
60+
61+
@property
62+
def cluster_id(self) -> str:
63+
return self["clusterId"]
64+
65+
@property
66+
def body_size(self) -> int:
67+
return self["bodySize"]
68+
69+
70+
class RabbitMessage(DictWrapper):
71+
@property
72+
def basic_properties(self) -> BasicProperties:
73+
return BasicProperties(self["basicProperties"])
74+
75+
@property
76+
def redelivered(self) -> bool:
77+
return self["redelivered"]
78+
79+
@property
80+
def data(self) -> str:
81+
return self["data"]
82+
83+
@property
84+
def decoded_data(self) -> str:
85+
"""Decodes the data as a str"""
86+
return base64.b64decode(self.data.encode()).decode()
87+
88+
@property
89+
def json_data(self) -> Any:
90+
"""Parses the data as json"""
91+
return json.loads(self.decoded_data)
92+
93+
94+
class RabbitMQEvent(DictWrapper):
95+
"""Represents a Rabbit MQ event sent to Lambda
96+
97+
Documentation:
98+
--------------
99+
- https://docs.aws.amazon.com/lambda/latest/dg/with-mq.html
100+
- https://aws.amazon.com/blogs/compute/using-amazon-mq-for-rabbitmq-as-an-event-source-for-lambda/
101+
"""
102+
103+
def __init__(self, data: Dict[str, Any]):
104+
super().__init__(data)
105+
self._rmq_messages_by_queue = {
106+
key: [RabbitMessage(message) for message in messages]
107+
for key, messages in self["rmqMessagesByQueue"].items()
108+
}
109+
110+
@property
111+
def event_source(self) -> str:
112+
return self["eventSource"]
113+
114+
@property
115+
def event_source_arn(self) -> str:
116+
"""The Amazon Resource Name (ARN) of the event source"""
117+
return self["eventSourceArn"]
118+
119+
@property
120+
def rmq_messages_by_queue(self) -> Dict[str, List[RabbitMessage]]:
121+
return self._rmq_messages_by_queue

0 commit comments

Comments
 (0)