Skip to content

Commit 2174b91

Browse files
author
Michael Brewer
committed
Merge branch 'develop' into refactoring-idempotent-ux
2 parents b482152 + b4d0baa commit 2174b91

File tree

12 files changed

+468
-91
lines changed

12 files changed

+468
-91
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .alb_event import ALBEvent
22
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
33
from .cloud_watch_logs_event import CloudWatchLogsEvent
4+
from .connect_contact_flow_event import ConnectContactFlowEvent
45
from .dynamo_db_stream_event import DynamoDBStreamEvent
56
from .event_bridge_event import EventBridgeEvent
67
from .kinesis_stream_event import KinesisStreamEvent
@@ -14,6 +15,7 @@
1415
"APIGatewayProxyEventV2",
1516
"ALBEvent",
1617
"CloudWatchLogsEvent",
18+
"ConnectContactFlowEvent",
1719
"DynamoDBStreamEvent",
1820
"EventBridgeEvent",
1921
"KinesisStreamEvent",
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from enum import Enum, auto
2+
from typing import Dict, Optional
3+
4+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
5+
6+
7+
class ConnectContactFlowChannel(Enum):
8+
VOICE = auto()
9+
CHAT = auto()
10+
11+
12+
class ConnectContactFlowEndpointType(Enum):
13+
TELEPHONE_NUMBER = auto()
14+
15+
16+
class ConnectContactFlowInitiationMethod(Enum):
17+
INBOUND = auto()
18+
OUTBOUND = auto()
19+
TRANSFER = auto()
20+
CALLBACK = auto()
21+
API = auto()
22+
23+
24+
class ConnectContactFlowEndpoint(DictWrapper):
25+
@property
26+
def address(self) -> str:
27+
"""The phone number."""
28+
return self["Address"]
29+
30+
@property
31+
def endpoint_type(self) -> ConnectContactFlowEndpointType:
32+
"""The enpoint type."""
33+
return ConnectContactFlowEndpointType[self["Type"]]
34+
35+
36+
class ConnectContactFlowQueue(DictWrapper):
37+
@property
38+
def arn(self) -> str:
39+
"""The unique queue ARN."""
40+
return self["ARN"]
41+
42+
@property
43+
def name(self) -> str:
44+
"""The queue name."""
45+
return self["Name"]
46+
47+
48+
class ConnectContactFlowMediaStreamAudio(DictWrapper):
49+
@property
50+
def start_fragment_number(self) -> Optional[str]:
51+
"""The number that identifies the Kinesis Video Streams fragment, in the stream used for Live media streaming,
52+
in which the customer audio stream started.
53+
"""
54+
return self["StartFragmentNumber"]
55+
56+
@property
57+
def start_timestamp(self) -> Optional[str]:
58+
"""When the customer audio stream started."""
59+
return self["StartTimestamp"]
60+
61+
@property
62+
def stream_arn(self) -> Optional[str]:
63+
"""The ARN of the Kinesis Video stream used for Live media streaming that includes the customer data to
64+
reference.
65+
"""
66+
return self["StreamARN"]
67+
68+
69+
class ConnectContactFlowMediaStreamCustomer(DictWrapper):
70+
@property
71+
def audio(self) -> ConnectContactFlowMediaStreamAudio:
72+
return ConnectContactFlowMediaStreamAudio(self["Audio"])
73+
74+
75+
class ConnectContactFlowMediaStreams(DictWrapper):
76+
@property
77+
def customer(self) -> ConnectContactFlowMediaStreamCustomer:
78+
return ConnectContactFlowMediaStreamCustomer(self["Customer"])
79+
80+
81+
class ConnectContactFlowData(DictWrapper):
82+
@property
83+
def attributes(self) -> Dict[str, str]:
84+
"""These are attributes that have been previously associated with a contact,
85+
such as when using a Set contact attributes block in a contact flow.
86+
This map may be empty if there aren't any saved attributes.
87+
"""
88+
return self["Attributes"]
89+
90+
@property
91+
def channel(self) -> ConnectContactFlowChannel:
92+
"""The method used to contact your contact center."""
93+
return ConnectContactFlowChannel[self["Channel"]]
94+
95+
@property
96+
def contact_id(self) -> str:
97+
"""The unique identifier of the contact."""
98+
return self["ContactId"]
99+
100+
@property
101+
def customer_endpoint(self) -> Optional[ConnectContactFlowEndpoint]:
102+
"""Contains the customer’s address (number) and type of address."""
103+
if self["CustomerEndpoint"] is not None:
104+
return ConnectContactFlowEndpoint(self["CustomerEndpoint"])
105+
return None
106+
107+
@property
108+
def initial_contact_id(self) -> str:
109+
"""The unique identifier for the contact associated with the first interaction between the customer and your
110+
contact center. Use the initial contact ID to track contacts between contact flows.
111+
"""
112+
return self["InitialContactId"]
113+
114+
@property
115+
def initiation_method(self) -> ConnectContactFlowInitiationMethod:
116+
"""How the contact was initiated."""
117+
return ConnectContactFlowInitiationMethod[self["InitiationMethod"]]
118+
119+
@property
120+
def instance_arn(self) -> str:
121+
"""The ARN for your Amazon Connect instance."""
122+
return self["InstanceARN"]
123+
124+
@property
125+
def previous_contact_id(self) -> str:
126+
"""The unique identifier for the contact before it was transferred.
127+
Use the previous contact ID to trace contacts between contact flows.
128+
"""
129+
return self["PreviousContactId"]
130+
131+
@property
132+
def queue(self) -> Optional[ConnectContactFlowQueue]:
133+
"""The current queue."""
134+
if self["Queue"] is not None:
135+
return ConnectContactFlowQueue(self["Queue"])
136+
return None
137+
138+
@property
139+
def system_endpoint(self) -> Optional[ConnectContactFlowEndpoint]:
140+
"""Contains the address (number) the customer dialed to call your contact center and type of address."""
141+
if self["SystemEndpoint"] is not None:
142+
return ConnectContactFlowEndpoint(self["SystemEndpoint"])
143+
return None
144+
145+
@property
146+
def media_streams(self) -> ConnectContactFlowMediaStreams:
147+
return ConnectContactFlowMediaStreams(self["MediaStreams"])
148+
149+
150+
class ConnectContactFlowEvent(DictWrapper):
151+
"""Amazon Connect contact flow event
152+
153+
Documentation:
154+
-------------
155+
- https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
156+
"""
157+
158+
@property
159+
def contact_data(self) -> ConnectContactFlowData:
160+
"""This is always passed by Amazon Connect for every contact. Some parameters are optional."""
161+
return ConnectContactFlowData(self["Details"]["ContactData"])
162+
163+
@property
164+
def parameters(self) -> Dict[str, str]:
165+
"""These are parameters specific to this call that were defined when you created the Lambda function."""
166+
return self["Details"]["Parameters"]

aws_lambda_powertools/utilities/idempotency/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
from typing import Dict
2+
3+
14
class IdempotencyConfig:
25
def __init__(
36
self,
47
event_key_jmespath: str = "",
58
payload_validation_jmespath: str = "",
9+
jmespath_options: Dict = None,
610
raise_on_no_idempotency_key: bool = False,
711
expires_after_seconds: int = 60 * 60, # 1 hour default
812
use_local_cache: bool = False,
@@ -31,6 +35,7 @@ def __init__(
3135
"""
3236
self.event_key_jmespath = event_key_jmespath
3337
self.payload_validation_jmespath = payload_validation_jmespath
38+
self.jmespath_options = jmespath_options
3439
self.raise_on_no_idempotency_key = raise_on_no_idempotency_key
3540
self.expires_after_seconds = expires_after_seconds
3641
self.use_local_cache = use_local_cache

aws_lambda_powertools/utilities/idempotency/persistence/base.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import jmespath
1515

1616
from aws_lambda_powertools.shared.cache_dict import LRUDict
17+
from aws_lambda_powertools.shared.jmespath_functions import PowertoolsFunctions
1718
from aws_lambda_powertools.shared.json_encoder import Encoder
1819
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
1920
from aws_lambda_powertools.utilities.idempotency.exceptions import (
@@ -112,6 +113,7 @@ def __init__(self):
112113

113114
self.event_key_jmespath: Optional[str] = None
114115
self.event_key_compiled_jmespath = None
116+
self.jmespath_options: Optional[dict] = None
115117
self.payload_validation_enabled = False
116118
self.validation_key_jmespath = None
117119
self.raise_on_no_idempotency_key = False
@@ -136,6 +138,9 @@ def configure(self, config: IdempotencyConfig,) -> None:
136138
self.event_key_jmespath = config.event_key_jmespath
137139
if config.event_key_jmespath:
138140
self.event_key_compiled_jmespath = jmespath.compile(config.event_key_jmespath)
141+
self.jmespath_options = config.jmespath_options
142+
if not self.jmespath_options:
143+
self.jmespath_options = {"custom_functions": PowertoolsFunctions()}
139144
if config.payload_validation_jmespath:
140145
self.validation_key_jmespath = jmespath.compile(config.payload_validation_jmespath)
141146
self.payload_validation_enabled = True
@@ -162,8 +167,11 @@ def _get_hashed_idempotency_key(self, lambda_event: Dict[str, Any]) -> str:
162167
163168
"""
164169
data = lambda_event
170+
165171
if self.event_key_jmespath:
166-
data = self.event_key_compiled_jmespath.search(lambda_event)
172+
data = self.event_key_compiled_jmespath.search(
173+
lambda_event, options=jmespath.Options(**self.jmespath_options)
174+
)
167175

168176
if self.is_missing_idempotency_key(data):
169177
warnings.warn(f"No value found for idempotency_key. jmespath: {self.event_key_jmespath}")

aws_lambda_powertools/utilities/validation/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import jmespath
66
from jmespath.exceptions import LexerError
77

8+
from aws_lambda_powertools.shared.jmespath_functions import PowertoolsFunctions
9+
810
from .exceptions import InvalidEnvelopeExpressionError, InvalidSchemaFormatError, SchemaValidationError
9-
from .jmespath_functions import PowertoolsFunctions
1011

1112
logger = logging.getLogger(__name__)
1213

0 commit comments

Comments
 (0)