Skip to content

Commit a8df37d

Browse files
fix(idempotency): is_missing_idempotency_key iterates dict keys instead of values (#8391)
* fix(idempotency): is_missing_idempotency_key iterates dict keys instead of values is_missing_idempotency_key iterated `data` directly for dict input, which walks its keys, not its values. For a dict whose values are all None but whose keys are ordinary non-None strings -- exactly what a JMESPath multi-select expression like '{user: headers.user_id, order: body.order_id}' produces when the referenced event fields are absent -- this returns False ("not missing") when it should return True. With raise_on_no_idempotency_key=True, the safety check that's supposed to raise IdempotencyKeyError in this situation silently doesn't fire. With the default False, no warning is emitted and the persistence layer hashes the all-None dict into a real idempotency key, so unrelated invocations that both fail to populate those fields collapse onto the same idempotency key and get incorrectly deduplicated against each other. The existing test only covered a dict of {None: None} (None as the key), which happens to still pass under the old key-iterating behavior and so never caught this. Iterate data.values() for dict input instead, and add a test covering the realistic non-None-keys/all-None-values case. * test(idempotency): cover missing dictionary keys --------- Co-authored-by: Leandro <lcdama@amazon.pt>
1 parent f704837 commit a8df37d

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

aws_lambda_powertools/utilities/idempotency/persistence/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ def _get_hashed_idempotency_key(self, data: dict[str, Any]) -> str | None:
131131

132132
@staticmethod
133133
def is_missing_idempotency_key(data) -> bool:
134-
if isinstance(data, (tuple, list, dict)):
134+
if isinstance(data, dict):
135+
# JMESPath multi-select dicts retain their keys when all selected values are missing.
136+
return all(x is None for x in data.values())
137+
elif isinstance(data, (tuple, list)):
135138
return all(x is None for x in data)
136139
elif isinstance(data, (int, float, bool)):
137140
return False

tests/functional/idempotency/_boto3/test_idempotency.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,10 @@ def test_is_missing_idempotency_key():
10461046
assert BasePersistenceLayer.is_missing_idempotency_key((None, None))
10471047
# GIVEN a dict of Nones THEN is_missing_idempotency_key is True
10481048
assert BasePersistenceLayer.is_missing_idempotency_key({None: None})
1049+
# GIVEN a dict with all-None values THEN is_missing_idempotency_key is True
1050+
assert BasePersistenceLayer.is_missing_idempotency_key({"user": None, "order": None})
1051+
# GIVEN a dict with a non-None value THEN is_missing_idempotency_key is False
1052+
assert BasePersistenceLayer.is_missing_idempotency_key({"user": "abc"}) is False
10491053

10501054
# GIVEN True THEN is_missing_idempotency_key is False
10511055
assert BasePersistenceLayer.is_missing_idempotency_key(True) is False
@@ -1114,6 +1118,19 @@ def test_raise_on_no_idempotency_key(
11141118
assert "No data found to create a hashed idempotency_key" in str(excinfo.value)
11151119

11161120

1121+
def test_raise_on_no_idempotency_key_for_dict_jmespath(persistence_store: DynamoDBPersistenceLayer):
1122+
# GIVEN a dict multi-select expression whose values are missing
1123+
idempotency_config = IdempotencyConfig(
1124+
event_key_jmespath="{user: headers.user_id, order: body.order_id}",
1125+
raise_on_no_idempotency_key=True,
1126+
)
1127+
persistence_store.configure(idempotency_config)
1128+
1129+
# WHEN extracting the idempotency key THEN raise IdempotencyKeyError
1130+
with pytest.raises(IdempotencyKeyError, match="No data found to create a hashed idempotency_key"):
1131+
persistence_store._get_hashed_idempotency_key({"headers": {}, "body": {}})
1132+
1133+
11171134
@pytest.mark.parametrize(
11181135
"idempotency_config",
11191136
[

0 commit comments

Comments
 (0)