Skip to content

Commit cbd08ec

Browse files
fix(idempotency): include sk in error msgs when using composite key (#6325)
Improve IdempotencyAlreadyInProgressError message
1 parent 792f6eb commit cbd08ec

File tree

4 files changed

+59
-2
lines changed

4 files changed

+59
-2
lines changed

Diff for: aws_lambda_powertools/utilities/idempotency/base.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,14 @@ def _handle_for_status(self, data_record: DataRecord) -> Any | None:
250250
"item should have been expired in-progress because it already time-outed.",
251251
)
252252

253-
raise IdempotencyAlreadyInProgressError(
253+
inprogress_error_message = (
254254
f"Execution already in progress with idempotency key: "
255-
f"{self.persistence_store.event_key_jmespath}={data_record.idempotency_key}",
255+
f"{self.persistence_store.event_key_jmespath}={data_record.idempotency_key}"
256256
)
257+
if data_record.sort_key is not None:
258+
inprogress_error_message += f" and sort key: {data_record.sort_key}"
259+
260+
raise IdempotencyAlreadyInProgressError(inprogress_error_message)
257261

258262
response_dict = data_record.response_json_as_dict()
259263
serialized_response = self.output_serializer.from_dict(response_dict) if response_dict else None

Diff for: aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
in_progress_expiry_timestamp: int | None = None,
2828
response_data: str = "",
2929
payload_hash: str = "",
30+
sort_key: str | None = None,
3031
) -> None:
3132
"""
3233
@@ -44,13 +45,16 @@ def __init__(
4445
hashed representation of payload
4546
response_data: str, optional
4647
response data from previous executions using the record
48+
sort_key: str, optional
49+
sort key when using composite key
4750
"""
4851
self.idempotency_key = idempotency_key
4952
self.payload_hash = payload_hash
5053
self.expiry_timestamp = expiry_timestamp
5154
self.in_progress_expiry_timestamp = in_progress_expiry_timestamp
5255
self._status = status
5356
self.response_data = response_data
57+
self.sort_key = sort_key
5458

5559
@property
5660
def is_expired(self) -> bool:

Diff for: aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py

+1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def _item_to_data_record(self, item: dict[str, Any]) -> DataRecord:
168168
in_progress_expiry_timestamp=data.get(self.in_progress_expiry_attr),
169169
response_data=data.get(self.data_attr),
170170
payload_hash=data.get(self.validation_key_attr),
171+
sort_key=data[self.sort_key_attr] if self.sort_key_attr is not None else None,
171172
)
172173

173174
def _get_record(self, idempotency_key) -> DataRecord:

Diff for: tests/functional/idempotency/_boto3/test_idempotency.py

+48
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,54 @@ def lambda_handler(event, context):
770770
stubber.deactivate()
771771

772772

773+
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
774+
def test_idempotent_lambda_expires_in_progress_before_expire_with_sort_key(
775+
idempotency_config: IdempotencyConfig,
776+
persistence_store_compound_static_pk_value: DynamoDBPersistenceLayer,
777+
lambda_apigw_event,
778+
timestamp_future,
779+
lambda_response,
780+
hashed_idempotency_key,
781+
lambda_context,
782+
):
783+
stubber = stub.Stubber(persistence_store_compound_static_pk_value.client)
784+
785+
stubber.add_client_error("put_item", "ConditionalCheckFailedException")
786+
787+
now = datetime.datetime.now()
788+
period = datetime.timedelta(seconds=5)
789+
timestamp_expires_in_progress = int((now + period).timestamp() * 1000)
790+
791+
expected_params_get_item = {
792+
"TableName": TABLE_NAME,
793+
"Key": {"id": {"S": "static-value"}, "sk": {"S": hashed_idempotency_key}},
794+
"ConsistentRead": True,
795+
}
796+
ddb_response_get_item = {
797+
"Item": {
798+
"id": {"S": "static-value"},
799+
"expiration": {"N": timestamp_future},
800+
"in_progress_expiration": {"N": str(timestamp_expires_in_progress)},
801+
"data": {"S": '{"message": "test", "statusCode": 200'},
802+
"status": {"S": "INPROGRESS"},
803+
"sk": {"S": hashed_idempotency_key},
804+
},
805+
}
806+
stubber.add_response("get_item", ddb_response_get_item, expected_params_get_item)
807+
808+
stubber.activate()
809+
810+
@idempotent(config=idempotency_config, persistence_store=persistence_store_compound_static_pk_value)
811+
def lambda_handler(event, context):
812+
return lambda_response
813+
814+
with pytest.raises(IdempotencyAlreadyInProgressError, match="and sort key"):
815+
lambda_handler(lambda_apigw_event, lambda_context)
816+
817+
stubber.assert_no_pending_responses()
818+
stubber.deactivate()
819+
820+
773821
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
774822
def test_idempotent_lambda_expires_in_progress_after_expire(
775823
idempotency_config: IdempotencyConfig,

0 commit comments

Comments
 (0)