Skip to content

Commit 32a4ca2

Browse files
author
Michael Brewer
committed
fix(idempotency): include function name in hash
closes #855
1 parent 81fc02e commit 32a4ca2

File tree

4 files changed

+41
-21
lines changed

4 files changed

+41
-21
lines changed

aws_lambda_powertools/utilities/idempotency/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __init__(
5656
self.fn_args = function_args
5757
self.fn_kwargs = function_kwargs
5858

59-
persistence_store.configure(config)
59+
persistence_store.configure(config, self.function.__name__)
6060
self.persistence_store = persistence_store
6161

6262
def handle(self) -> Any:

aws_lambda_powertools/utilities/idempotency/persistence/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def __init__(self):
124124
self._cache: Optional[LRUDict] = None
125125
self.hash_function = None
126126

127-
def configure(self, config: IdempotencyConfig) -> None:
127+
def configure(self, config: IdempotencyConfig, function_name: str) -> None:
128128
"""
129129
Initialize the base persistence layer from the configuration settings
130130
@@ -133,6 +133,7 @@ def configure(self, config: IdempotencyConfig) -> None:
133133
config: IdempotencyConfig
134134
Idempotency configuration settings
135135
"""
136+
self.function_name = function_name
136137
if self.configured:
137138
# Prevent being reconfigured multiple times
138139
return
@@ -178,7 +179,7 @@ def _get_hashed_idempotency_key(self, data: Dict[str, Any]) -> str:
178179
warnings.warn(f"No value found for idempotency_key. jmespath: {self.event_key_jmespath}")
179180

180181
generated_hash = self._generate_hash(data=data)
181-
function_name = os.getenv(constants.LAMBDA_FUNCTION_NAME_ENV, "test-func")
182+
function_name = os.getenv(constants.LAMBDA_FUNCTION_NAME_ENV, "test-func") + "." + self.function_name
182183
return f"{function_name}#{generated_hash}"
183184

184185
@staticmethod

tests/functional/idempotency/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ def expected_params_put_item_with_validation(hashed_idempotency_key, hashed_vali
150150
def hashed_idempotency_key(lambda_apigw_event, default_jmespath, lambda_context):
151151
compiled_jmespath = jmespath.compile(default_jmespath)
152152
data = compiled_jmespath.search(lambda_apigw_event)
153-
return "test-func#" + hashlib.md5(serialize(data).encode()).hexdigest()
153+
return "test-func.lambda_handler#" + hashlib.md5(serialize(data).encode()).hexdigest()
154154

155155

156156
@pytest.fixture
157157
def hashed_idempotency_key_with_envelope(lambda_apigw_event):
158158
event = extract_data_from_envelope(
159159
data=lambda_apigw_event, envelope=envelopes.API_GATEWAY_HTTP, jmespath_options={}
160160
)
161-
return "test-func#" + hashlib.md5(serialize(event).encode()).hexdigest()
161+
return "test-func.lambda_handler#" + hashlib.md5(serialize(event).encode()).hexdigest()
162162

163163

164164
@pytest.fixture

tests/functional/idempotency/test_idempotency.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ def test_in_progress_never_saved_to_cache(
648648
):
649649
# GIVEN a data record with status "INPROGRESS"
650650
# and persistence_store has use_local_cache = True
651-
persistence_store.configure(idempotency_config)
651+
persistence_store.configure(idempotency_config, "handler")
652652
data_record = DataRecord("key", status="INPROGRESS")
653653

654654
# WHEN saving to local cache
@@ -661,7 +661,7 @@ def test_in_progress_never_saved_to_cache(
661661
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}], indirect=True)
662662
def test_user_local_disabled(idempotency_config: IdempotencyConfig, persistence_store: DynamoDBPersistenceLayer):
663663
# GIVEN a persistence_store with use_local_cache = False
664-
persistence_store.configure(idempotency_config)
664+
persistence_store.configure(idempotency_config, "")
665665

666666
# WHEN calling any local cache options
667667
data_record = DataRecord("key", status="COMPLETED")
@@ -683,7 +683,7 @@ def test_delete_from_cache_when_empty(
683683
idempotency_config: IdempotencyConfig, persistence_store: DynamoDBPersistenceLayer
684684
):
685685
# GIVEN use_local_cache is True AND the local cache is empty
686-
persistence_store.configure(idempotency_config)
686+
persistence_store.configure(idempotency_config, "handler")
687687

688688
try:
689689
# WHEN we _delete_from_cache
@@ -735,15 +735,16 @@ def test_default_no_raise_on_missing_idempotency_key(
735735
idempotency_config: IdempotencyConfig, persistence_store: DynamoDBPersistenceLayer, lambda_context
736736
):
737737
# GIVEN a persistence_store with use_local_cache = False and event_key_jmespath = "body"
738-
persistence_store.configure(idempotency_config)
738+
function_name = "foo"
739+
persistence_store.configure(idempotency_config, function_name)
739740
assert persistence_store.use_local_cache is False
740741
assert "body" in persistence_store.event_key_jmespath
741742

742743
# WHEN getting the hashed idempotency key for an event with no `body` key
743744
hashed_key = persistence_store._get_hashed_idempotency_key({})
744745

745746
# THEN return the hash of None
746-
expected_value = "test-func#" + md5(serialize(None).encode()).hexdigest()
747+
expected_value = f"test-func.{function_name}#" + md5(serialize(None).encode()).hexdigest()
747748
assert expected_value == hashed_key
748749

749750

@@ -754,7 +755,7 @@ def test_raise_on_no_idempotency_key(
754755
idempotency_config: IdempotencyConfig, persistence_store: DynamoDBPersistenceLayer, lambda_context
755756
):
756757
# GIVEN a persistence_store with raise_on_no_idempotency_key and no idempotency key in the request
757-
persistence_store.configure(idempotency_config)
758+
persistence_store.configure(idempotency_config, "handler")
758759
persistence_store.raise_on_no_idempotency_key = True
759760
assert persistence_store.use_local_cache is False
760761
assert "body" in persistence_store.event_key_jmespath
@@ -781,7 +782,7 @@ def test_jmespath_with_powertools_json(
781782
idempotency_config: IdempotencyConfig, persistence_store: DynamoDBPersistenceLayer, lambda_context
782783
):
783784
# GIVEN an event_key_jmespath with powertools_json custom function
784-
persistence_store.configure(idempotency_config)
785+
persistence_store.configure(idempotency_config, "handler")
785786
sub_attr_value = "cognito_user"
786787
static_pk_value = "some_key"
787788
expected_value = [sub_attr_value, static_pk_value]
@@ -794,7 +795,7 @@ def test_jmespath_with_powertools_json(
794795
result = persistence_store._get_hashed_idempotency_key(api_gateway_proxy_event)
795796

796797
# THEN the hashed idempotency key should match the extracted values generated hash
797-
assert result == "test-func#" + persistence_store._generate_hash(expected_value)
798+
assert result == "test-func.handler#" + persistence_store._generate_hash(expected_value)
798799

799800

800801
@pytest.mark.parametrize("config_with_jmespath_options", ["powertools_json(data).payload"], indirect=True)
@@ -803,7 +804,7 @@ def test_custom_jmespath_function_overrides_builtin_functions(
803804
):
804805
# GIVEN an persistence store with a custom jmespath_options
805806
# AND use a builtin powertools custom function
806-
persistence_store.configure(config_with_jmespath_options)
807+
persistence_store.configure(config_with_jmespath_options, "handler")
807808

808809
with pytest.raises(jmespath.exceptions.UnknownFunctionError, match="Unknown function: powertools_json()"):
809810
# WHEN calling _get_hashed_idempotency_key
@@ -871,7 +872,9 @@ def _delete_record(self, data_record: DataRecord) -> None:
871872
def test_idempotent_lambda_event_source(lambda_context):
872873
# Scenario to validate that we can use the event_source decorator before or after the idempotent decorator
873874
mock_event = load_event("apiGatewayProxyV2Event.json")
874-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
875+
persistence_layer = MockPersistenceLayer(
876+
"test-func.lambda_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
877+
)
875878
expected_result = {"message": "Foo"}
876879

877880
# GIVEN an event_source decorator
@@ -891,7 +894,9 @@ def lambda_handler(event, _):
891894
def test_idempotent_function():
892895
# Scenario to validate we can use idempotent_function with any function
893896
mock_event = {"data": "value"}
894-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
897+
persistence_layer = MockPersistenceLayer(
898+
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
899+
)
895900
expected_result = {"message": "Foo"}
896901

897902
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
@@ -908,7 +913,9 @@ def test_idempotent_function_arbitrary_args_kwargs():
908913
# Scenario to validate we can use idempotent_function with a function
909914
# with an arbitrary number of args and kwargs
910915
mock_event = {"data": "value"}
911-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
916+
persistence_layer = MockPersistenceLayer(
917+
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
918+
)
912919
expected_result = {"message": "Foo"}
913920

914921
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
@@ -923,7 +930,9 @@ def record_handler(arg_one, arg_two, record, is_record):
923930

924931
def test_idempotent_function_invalid_data_kwarg():
925932
mock_event = {"data": "value"}
926-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
933+
persistence_layer = MockPersistenceLayer(
934+
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
935+
)
927936
expected_result = {"message": "Foo"}
928937
keyword_argument = "payload"
929938

@@ -940,7 +949,9 @@ def record_handler(record):
940949

941950
def test_idempotent_function_arg_instead_of_kwarg():
942951
mock_event = {"data": "value"}
943-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
952+
persistence_layer = MockPersistenceLayer(
953+
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
954+
)
944955
expected_result = {"message": "Foo"}
945956
keyword_argument = "record"
946957

@@ -958,13 +969,19 @@ def record_handler(record):
958969
def test_idempotent_function_and_lambda_handler(lambda_context):
959970
# Scenario to validate we can use both idempotent_function and idempotent decorators
960971
mock_event = {"data": "value"}
961-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(serialize(mock_event).encode()).hexdigest())
972+
persistence_layer = MockPersistenceLayer(
973+
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
974+
)
962975
expected_result = {"message": "Foo"}
963976

964977
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
965978
def record_handler(record):
966979
return expected_result
967980

981+
persistence_layer = MockPersistenceLayer(
982+
"test-func.lambda_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
983+
)
984+
968985
@idempotent(persistence_store=persistence_layer)
969986
def lambda_handler(event, _):
970987
return expected_result
@@ -986,7 +1003,9 @@ def test_idempotent_data_sorting():
9861003
data_two = {"more_data": "more data 1", "data": "test message 1"}
9871004

9881005
# Assertion will happen in MockPersistenceLayer
989-
persistence_layer = MockPersistenceLayer("test-func#" + hashlib.md5(json.dumps(data_one).encode()).hexdigest())
1006+
persistence_layer = MockPersistenceLayer(
1007+
"test-func.dummy#" + hashlib.md5(json.dumps(data_one).encode()).hexdigest()
1008+
)
9901009

9911010
# GIVEN
9921011
@idempotent_function(data_keyword_argument="payload", persistence_store=persistence_layer)

0 commit comments

Comments
 (0)