Skip to content

Commit

Permalink
return Item in cancellation_reasons when transaction fails and return…
Browse files Browse the repository at this point in the history
…_values=ALL_OLD (#1226)
  • Loading branch information
knap1930 authored Feb 16, 2024
1 parent 84469c4 commit 117cb78
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 1 deletion.
5 changes: 4 additions & 1 deletion docs/transaction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
condition=(
(BankStatement.account_balance >= transfer_amount) &
(BankStatement.is_active == True)
)
),
return_values=ALL_OLD
)
transaction.update(
BankStatement(user_id='user2'),
Expand All @@ -107,6 +108,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
assert e.cause_response_code == 'TransactionCanceledException'
# the first 'update' was a reason for the cancellation
assert e.cancellation_reasons[0].code == 'ConditionalCheckFailed'
# when return_values=ALL_OLD, the old values can be accessed from the raw_item property
assert BankStatement.from_dynamodb_dict(e.cancellation_reasons[0].raw_item) == user1_statement
# the second 'update' wasn't a reason, but was cancelled too
assert e.cancellation_reasons[1] is None
Expand Down
1 change: 1 addition & 0 deletions pynamodb/connection/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ def _make_api_call(self, operation_name: str, operation_kwargs: Dict) -> Dict:
CancellationReason(
code=d['Code'],
message=d.get('Message'),
raw_item=cast(Optional[Dict[str, Dict[str, Any]]], d.get('Item')),
) if d['Code'] != 'None' else None
)
for d in cancellation_reasons
Expand Down
1 change: 1 addition & 0 deletions pynamodb/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class CancellationReason:
"""
code: str
message: Optional[str] = None
raw_item: Optional[Dict[str, Dict[str, Any]]] = None


class TransactWriteError(PynamoDBException):
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/test_transaction_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

from pynamodb.connection import Connection
from pynamodb.constants import ALL_OLD
from pynamodb.exceptions import CancellationReason
from pynamodb.exceptions import DoesNotExist, TransactWriteError, InvalidStateError

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


@pytest.mark.ddblocal
def test_transact_write__error__transaction_cancelled__condition_check_failure__return_all_old(connection):
# create a users and a bank statements for them
User(1).save()

# attempt to do this as a transaction with the condition that they don't already exist
with pytest.raises(TransactWriteError) as exc_info:
with TransactWrite(connection=connection) as transaction:
transaction.save(User(1), condition=(User.user_id.does_not_exist()), return_values=ALL_OLD)
assert exc_info.value.cause_response_code == TRANSACTION_CANCELLED
assert 'ConditionalCheckFailed' in exc_info.value.cause_response_message
assert exc_info.value.cancellation_reasons == [
CancellationReason(code='ConditionalCheckFailed', message='The conditional request failed', raw_item=User(1).to_dynamodb_dict()),
]


@pytest.mark.ddblocal
def test_transact_write__error__transaction_cancelled__partial_failure(connection):
User(2).delete()
Expand Down

0 comments on commit 117cb78

Please sign in to comment.