Skip to content

Commit 117cb78

Browse files
authored
return Item in cancellation_reasons when transaction fails and return_values=ALL_OLD (#1226)
1 parent 84469c4 commit 117cb78

File tree

4 files changed

+23
-1
lines changed

4 files changed

+23
-1
lines changed

docs/transaction.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
9494
condition=(
9595
(BankStatement.account_balance >= transfer_amount) &
9696
(BankStatement.is_active == True)
97-
)
97+
),
98+
return_values=ALL_OLD
9899
)
99100
transaction.update(
100101
BankStatement(user_id='user2'),
@@ -107,6 +108,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
107108
assert e.cause_response_code == 'TransactionCanceledException'
108109
# the first 'update' was a reason for the cancellation
109110
assert e.cancellation_reasons[0].code == 'ConditionalCheckFailed'
111+
# when return_values=ALL_OLD, the old values can be accessed from the raw_item property
112+
assert BankStatement.from_dynamodb_dict(e.cancellation_reasons[0].raw_item) == user1_statement
110113
# the second 'update' wasn't a reason, but was cancelled too
111114
assert e.cancellation_reasons[1] is None
112115

pynamodb/connection/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ def _make_api_call(self, operation_name: str, operation_kwargs: Dict) -> Dict:
357357
CancellationReason(
358358
code=d['Code'],
359359
message=d.get('Message'),
360+
raw_item=cast(Optional[Dict[str, Dict[str, Any]]], d.get('Item')),
360361
) if d['Code'] != 'None' else None
361362
)
362363
for d in cancellation_reasons

pynamodb/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ class CancellationReason:
134134
"""
135135
code: str
136136
message: Optional[str] = None
137+
raw_item: Optional[Dict[str, Dict[str, Any]]] = None
137138

138139

139140
class TransactWriteError(PynamoDBException):

tests/integration/test_transaction_integration.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
from pynamodb.connection import Connection
8+
from pynamodb.constants import ALL_OLD
89
from pynamodb.exceptions import CancellationReason
910
from pynamodb.exceptions import DoesNotExist, TransactWriteError, InvalidStateError
1011

@@ -168,6 +169,22 @@ def test_transact_write__error__transaction_cancelled__condition_check_failure(c
168169
assert BankStatement.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE
169170

170171

172+
@pytest.mark.ddblocal
173+
def test_transact_write__error__transaction_cancelled__condition_check_failure__return_all_old(connection):
174+
# create a users and a bank statements for them
175+
User(1).save()
176+
177+
# attempt to do this as a transaction with the condition that they don't already exist
178+
with pytest.raises(TransactWriteError) as exc_info:
179+
with TransactWrite(connection=connection) as transaction:
180+
transaction.save(User(1), condition=(User.user_id.does_not_exist()), return_values=ALL_OLD)
181+
assert exc_info.value.cause_response_code == TRANSACTION_CANCELLED
182+
assert 'ConditionalCheckFailed' in exc_info.value.cause_response_message
183+
assert exc_info.value.cancellation_reasons == [
184+
CancellationReason(code='ConditionalCheckFailed', message='The conditional request failed', raw_item=User(1).to_dynamodb_dict()),
185+
]
186+
187+
171188
@pytest.mark.ddblocal
172189
def test_transact_write__error__transaction_cancelled__partial_failure(connection):
173190
User(2).delete()

0 commit comments

Comments
 (0)