Skip to content

Commit dfb5b0f

Browse files
author
Michael Brewer
committed
refactor(idempotent): Create a config class
1 parent 12c512b commit dfb5b0f

File tree

6 files changed

+160
-148
lines changed

6 files changed

+160
-148
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class IdempotencyConfig:
2+
def __init__(
3+
self,
4+
event_key_jmespath: str = "",
5+
payload_validation_jmespath: str = "",
6+
raise_on_no_idempotency_key: bool = False,
7+
expires_after_seconds: int = 60 * 60, # 1 hour default
8+
use_local_cache: bool = False,
9+
local_cache_max_items: int = 256,
10+
hash_function: str = "md5",
11+
):
12+
"""
13+
Initialize the base persistence layer
14+
15+
Parameters
16+
----------
17+
event_key_jmespath: str
18+
A jmespath expression to extract the idempotency key from the event record
19+
payload_validation_jmespath: str
20+
A jmespath expression to extract the payload to be validated from the event record
21+
raise_on_no_idempotency_key: bool, optional
22+
Raise exception if no idempotency key was found in the request, by default False
23+
expires_after_seconds: int
24+
The number of seconds to wait before a record is expired
25+
use_local_cache: bool, optional
26+
Whether to locally cache idempotency results, by default False
27+
local_cache_max_items: int, optional
28+
Max number of items to store in local cache, by default 1024
29+
hash_function: str, optional
30+
Function to use for calculating hashes, by default md5.
31+
"""
32+
self.event_key_jmespath = event_key_jmespath
33+
self.payload_validation_jmespath = payload_validation_jmespath
34+
self.raise_on_no_idempotency_key = raise_on_no_idempotency_key
35+
self.expires_after_seconds = expires_after_seconds
36+
self.use_local_cache = use_local_cache
37+
self.local_cache_max_items = local_cache_max_items
38+
self.hash_function = hash_function

aws_lambda_powertools/utilities/idempotency/idempotency.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Any, Callable, Dict, Optional
66

77
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
8+
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
89
from aws_lambda_powertools.utilities.idempotency.exceptions import (
910
IdempotencyAlreadyInProgressError,
1011
IdempotencyInconsistentStateError,
@@ -29,6 +30,7 @@ def idempotent(
2930
event: Dict[str, Any],
3031
context: LambdaContext,
3132
persistence_store: BasePersistenceLayer,
33+
config: IdempotencyConfig = None,
3234
) -> Any:
3335
"""
3436
Middleware to handle idempotency
@@ -43,20 +45,24 @@ def idempotent(
4345
Lambda's Context
4446
persistence_store: BasePersistenceLayer
4547
Instance of BasePersistenceLayer to store data
48+
config: IdempotencyConfig
49+
Configutation
4650
4751
Examples
4852
--------
4953
**Processes Lambda's event in an idempotent manner**
50-
>>> from aws_lambda_powertools.utilities.idempotency import idempotent, DynamoDBPersistenceLayer
54+
>>> from aws_lambda_powertools.utilities.idempotency import (
55+
>>> idempotent, DynamoDBPersistenceLayer
56+
>>> )
5157
>>>
52-
>>> persistence_store = DynamoDBPersistenceLayer(event_key_jmespath="body", table_name="idempotency_store")
58+
>>> persistence_layer = DynamoDBPersistenceLayer(table_name="idempotency_store")
5359
>>>
54-
>>> @idempotent(persistence_store=persistence_store)
60+
>>> @idempotent(persistence_store=persistence_layer, config=IdempotencyConfig(event_key_jmespath="body"))
5561
>>> def handler(event, context):
5662
>>> return {"StatusCode": 200}
5763
"""
5864

59-
idempotency_handler = IdempotencyHandler(handler, event, context, persistence_store)
65+
idempotency_handler = IdempotencyHandler(handler, event, context, config, persistence_store)
6066

6167
# IdempotencyInconsistentStateError can happen under rare but expected cases when persistent state changes in the
6268
# small time between put & get requests. In most cases we can retry successfully on this exception.
@@ -82,6 +88,7 @@ def __init__(
8288
lambda_handler: Callable[[Any, LambdaContext], Any],
8389
event: Dict[str, Any],
8490
context: LambdaContext,
91+
config: IdempotencyConfig,
8592
persistence_store: BasePersistenceLayer,
8693
):
8794
"""
@@ -98,6 +105,7 @@ def __init__(
98105
persistence_store : BasePersistenceLayer
99106
Instance of persistence layer to store idempotency records
100107
"""
108+
persistence_store.configure(config)
101109
self.persistence_store = persistence_store
102110
self.context = context
103111
self.event = event

aws_lambda_powertools/utilities/idempotency/persistence/base.py

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
IdempotencyKeyError,
2222
IdempotencyValidationError,
2323
)
24+
from aws_lambda_powertools.utilities.idempotency.idempotency import IdempotencyConfig
2425

2526
logger = logging.getLogger(__name__)
2627

@@ -106,49 +107,39 @@ class BasePersistenceLayer(ABC):
106107
Abstract Base Class for Idempotency persistence layer.
107108
"""
108109

109-
def __init__(
110-
self,
111-
event_key_jmespath: str = "",
112-
payload_validation_jmespath: str = "",
113-
expires_after_seconds: int = 60 * 60, # 1 hour default
114-
use_local_cache: bool = False,
115-
local_cache_max_items: int = 256,
116-
hash_function: str = "md5",
117-
raise_on_no_idempotency_key: bool = False,
118-
) -> None:
110+
def __init__(self):
111+
self.event_key_jmespath = None
112+
self.event_key_compiled_jmespath = None
113+
self.payload_validation_enabled = None
114+
self.validation_key_jmespath = None
115+
self.raise_on_no_idempotency_key = None
116+
self.expires_after_seconds = None
117+
self.use_local_cache = None
118+
self._cache = None
119+
self.hash_function = None
120+
121+
def configure(self, config: IdempotencyConfig,) -> None:
119122
"""
120123
Initialize the base persistence layer
121124
122125
Parameters
123126
----------
124-
event_key_jmespath: str
125-
A jmespath expression to extract the idempotency key from the event record
126-
payload_validation_jmespath: str
127-
A jmespath expression to extract the payload to be validated from the event record
128-
expires_after_seconds: int
129-
The number of seconds to wait before a record is expired
130-
use_local_cache: bool, optional
131-
Whether to locally cache idempotency results, by default False
132-
local_cache_max_items: int, optional
133-
Max number of items to store in local cache, by default 1024
134-
hash_function: str, optional
135-
Function to use for calculating hashes, by default md5.
136-
raise_on_no_idempotency_key: bool, optional
137-
Raise exception if no idempotency key was found in the request, by default False
138-
"""
139-
self.event_key_jmespath = event_key_jmespath
127+
config: IdempotencyConfig
128+
Configuration settings
129+
"""
130+
self.event_key_jmespath = config.event_key_jmespath
140131
if self.event_key_jmespath:
141-
self.event_key_compiled_jmespath = jmespath.compile(event_key_jmespath)
142-
self.expires_after_seconds = expires_after_seconds
143-
self.use_local_cache = use_local_cache
144-
if self.use_local_cache:
145-
self._cache = LRUDict(max_items=local_cache_max_items)
132+
self.event_key_compiled_jmespath = jmespath.compile(config.event_key_jmespath)
146133
self.payload_validation_enabled = False
147-
if payload_validation_jmespath:
148-
self.validation_key_jmespath = jmespath.compile(payload_validation_jmespath)
134+
if config.payload_validation_jmespath:
135+
self.validation_key_jmespath = jmespath.compile(config.payload_validation_jmespath)
149136
self.payload_validation_enabled = True
150-
self.hash_function = getattr(hashlib, hash_function)
151-
self.raise_on_no_idempotency_key = raise_on_no_idempotency_key
137+
self.raise_on_no_idempotency_key = config.raise_on_no_idempotency_key
138+
self.expires_after_seconds = config.expires_after_seconds
139+
self.use_local_cache = config.use_local_cache
140+
if self.use_local_cache:
141+
self._cache = LRUDict(max_items=config.local_cache_max_items)
142+
self.hash_function = getattr(hashlib, config.hash_function)
152143

153144
def _get_hashed_idempotency_key(self, lambda_event: Dict[str, Any]) -> str:
154145
"""

aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ def __init__(
2626
validation_key_attr: str = "validation",
2727
boto_config: Optional[Config] = None,
2828
boto3_session: Optional[boto3.session.Session] = None,
29-
*args,
30-
**kwargs,
3129
):
3230
"""
3331
Initialize the DynamoDB client
@@ -57,9 +55,9 @@ def __init__(
5755
**Create a DynamoDB persistence layer with custom settings**
5856
>>> from aws_lambda_powertools.utilities.idempotency import idempotent, DynamoDBPersistenceLayer
5957
>>>
60-
>>> persistence_store = DynamoDBPersistenceLayer(event_key="body", table_name="idempotency_store")
58+
>>> persistence_store = DynamoDBPersistenceLayer(table_name="idempotency_store")
6159
>>>
62-
>>> @idempotent(persistence_store=persistence_store)
60+
>>> @idempotent(persistence_store=persistence_store, event_key="body")
6361
>>> def handler(event, context):
6462
>>> return {"StatusCode": 200}
6563
"""
@@ -74,7 +72,7 @@ def __init__(
7472
self.status_attr = status_attr
7573
self.data_attr = data_attr
7674
self.validation_key_attr = validation_key_attr
77-
super(DynamoDBPersistenceLayer, self).__init__(*args, **kwargs)
75+
super(DynamoDBPersistenceLayer, self).__init__()
7876

7977
def _item_to_data_record(self, item: Dict[str, Any]) -> DataRecord:
8078
"""

tests/functional/idempotency/conftest.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from aws_lambda_powertools.shared.json_encoder import Encoder
1414
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer
15+
from aws_lambda_powertools.utilities.idempotency.idempotency import IdempotencyConfig
1516
from aws_lambda_powertools.utilities.validation import envelopes
1617
from aws_lambda_powertools.utilities.validation.base import unwrap_event_from_envelope
1718

@@ -150,34 +151,30 @@ def hashed_validation_key(lambda_apigw_event):
150151

151152

152153
@pytest.fixture
153-
def persistence_store(config, request, default_jmespath):
154-
persistence_store = DynamoDBPersistenceLayer(
154+
def persistence_store(config):
155+
return DynamoDBPersistenceLayer(table_name=TABLE_NAME, boto_config=config,)
156+
157+
158+
@pytest.fixture
159+
def idempotency_config(config, request, default_jmespath):
160+
return IdempotencyConfig(
155161
event_key_jmespath=request.param.get("event_key_jmespath") or default_jmespath,
156-
table_name=TABLE_NAME,
157-
boto_config=config,
158162
use_local_cache=request.param["use_local_cache"],
159163
)
160-
return persistence_store
161164

162165

163166
@pytest.fixture
164-
def persistence_store_without_jmespath(config, request):
165-
persistence_store = DynamoDBPersistenceLayer(
166-
table_name=TABLE_NAME, boto_config=config, use_local_cache=request.param["use_local_cache"],
167-
)
168-
return persistence_store
167+
def config_without_jmespath(config, request):
168+
return IdempotencyConfig(use_local_cache=request.param["use_local_cache"],)
169169

170170

171171
@pytest.fixture
172-
def persistence_store_with_validation(config, request, default_jmespath):
173-
persistence_store = DynamoDBPersistenceLayer(
172+
def config_with_validation(config, request, default_jmespath):
173+
return IdempotencyConfig(
174174
event_key_jmespath=default_jmespath,
175-
table_name=TABLE_NAME,
176-
boto_config=config,
177175
use_local_cache=request.param,
178176
payload_validation_jmespath="requestContext",
179177
)
180-
return persistence_store
181178

182179

183180
@pytest.fixture

0 commit comments

Comments
 (0)