def test_transaction_write_with_version_attribute_condition_failure( connection): foo = Foo(21) foo.save() foo2 = Foo(21) with pytest.raises(TransactWriteError) as exc_info: with TransactWrite(connection=connection) as transaction: transaction.save(Foo(21)) assert get_error_code(exc_info.value) == TRANSACTION_CANCELLED assert 'ConditionalCheckFailed' in get_error_message(exc_info.value) with pytest.raises(TransactWriteError) as exc_info: with TransactWrite(connection=connection) as transaction: transaction.update(foo2, actions=[ Foo.star.set('birdistheword'), ]) assert get_error_code(exc_info.value) == TRANSACTION_CANCELLED assert 'ConditionalCheckFailed' in get_error_message(exc_info.value) # Version attribute is not updated on failure. assert foo2.version is None with pytest.raises(TransactWriteError) as exc_info: with TransactWrite(connection=connection) as transaction: transaction.delete(foo2) assert get_error_code(exc_info.value) == TRANSACTION_CANCELLED assert 'ConditionalCheckFailed' in get_error_message(exc_info.value)
def test_transact_write(connection): # making sure these entries exist, and with the expected info BankStatement(1, balance=0).save() BankStatement(2, balance=100).save() # assert values are what we think they should be statement1 = BankStatement.get(1) statement2 = BankStatement.get(2) assert statement1.balance == 0 assert statement2.balance == 100 with TransactWrite(connection=connection) as transaction: # let the users send money to one another # create a credit line item to user 1's account transaction.save( LineItem(user_id=1, amount=50, currency='USD'), condition=(LineItem.user_id.does_not_exist()), ) # create a debit to user 2's account transaction.save( LineItem(user_id=2, amount=-50, currency='USD'), condition=(LineItem.user_id.does_not_exist()), ) # add credit to user 1's account transaction.update(statement1, actions=[BankStatement.balance.add(50)]) # debit from user 2's account if they have enough in the bank transaction.update(statement2, actions=[BankStatement.balance.add(-50)], condition=(BankStatement.balance >= 50)) statement1.refresh() statement2.refresh() assert statement1.balance == statement2.balance == 50
def test_transact_write__one_of_each(connection): User(1).save() User(2).save() statement = BankStatement(1, balance=100, active=True) statement.save() with TransactWrite(connection=connection) as transaction: transaction.condition_check(User, 1, condition=(User.user_id.exists())) transaction.delete(User(2)) transaction.save(LineItem(4, amount=100, currency='USD'), condition=(LineItem.user_id.does_not_exist())) transaction.update(statement, actions=[ BankStatement.active.set(False), BankStatement.balance.set(0), ]) # confirming transaction correct and successful assert User.get(1) with pytest.raises(DoesNotExist): User.get(2) new_line_item = next(LineItem.query(4, scan_index_forward=False, limit=1), None) assert new_line_item assert new_line_item.amount == 100 assert new_line_item.currency == 'USD' statement.refresh() assert not statement.active assert statement.balance == 0
def test_transaction_write_with_version_attribute(connection): foo1 = Foo(1) foo1.save() foo2 = Foo(2, star='bar') foo2.save() foo3 = Foo(3) foo3.save() with TransactWrite(connection=connection) as transaction: transaction.condition_check(Foo, 1, condition=(Foo.bar.exists())) transaction.delete(foo2) transaction.save(Foo(4)) transaction.update( foo3, actions=[ Foo.star.set('birdistheword'), ] ) assert Foo.get(1).version == 1 with pytest.raises(DoesNotExist): Foo.get(2) # Local object's version attribute is updated automatically. assert foo3.version == 2 assert Foo.get(4).version == 1
def test_transact_write__error__different_regions(connection): with pytest.raises(TransactWriteError) as exc_info: with TransactWrite(connection=connection) as transact_write: # creating a model in a table outside the region everyone else operates in transact_write.save(DifferentRegion(entry_index=0)) transact_write.save(BankStatement(1)) transact_write.save(User(1)) assert get_error_code(exc_info.value) == RESOURCE_NOT_FOUND
def test_transact_write__error__multiple_operations_on_same_record(connection): BankStatement(1).save() # attempt to do a transaction with multiple operations on the same record with pytest.raises(TransactWriteError) as exc_info: with TransactWrite(connection=connection) as transaction: transaction.condition_check(BankStatement, 1, condition=(BankStatement.user_id.exists())) transaction.update(BankStatement(1), actions=[(BankStatement.balance.add(10))]) assert get_error_code(exc_info.value) == VALIDATION_EXCEPTION assert BankStatement.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE
def test_transact_write__error__idempotent_parameter_mismatch(connection): client_token = str(uuid.uuid4()) with TransactWrite(connection=connection, client_request_token=client_token) as transaction: transaction.save(User(1)) transaction.save(User(2)) with pytest.raises(TransactWriteError) as exc_info: # committing the first time, then adding more info and committing again with TransactWrite(connection=connection, client_request_token=client_token) as transaction: transaction.save(User(3)) assert get_error_code(exc_info.value) == IDEMPOTENT_PARAMETER_MISMATCH assert User.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE # ensure that the first request succeeded in creating new users assert User.get(1) assert User.get(2) with pytest.raises(DoesNotExist): # ensure it did not create the user from second request User.get(3)
def test_transact_write__error__transaction_cancelled__condition_check_failure(connection): # create a users and a bank statements for them User(1).save() BankStatement(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())) transaction.save(BankStatement(1), condition=(BankStatement.user_id.does_not_exist())) assert get_error_code(exc_info.value) == TRANSACTION_CANCELLED assert 'ConditionalCheckFailed' in get_error_message(exc_info.value) assert User.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE assert BankStatement.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE
def _handle_transact_write(connection, auto_version_condition): old_auto_version_condition = MockModel.Meta.auto_version_condition MockModel.Meta.auto_version_condition = auto_version_condition with patch(PATCH_METHOD) as req: req.return_value = MOCK_TABLE_DESCRIPTOR with TransactWrite(connection=connection) as t: t.condition_check( MockModel, 1, 3, condition=(MockModel.mock_hash.does_not_exist())) t.delete(MockModel(2, 4)) t.save(MockModel(3, 5)) t.update(MockModel(4, 6), actions=[MockModel.mock_toot.set('hello')], return_values='ALL_OLD') MockModel.Meta.auto_version_condition = old_auto_version_condition
def handler(event, context): # Validation request = Request(**loads(event.get("body", {}))) # Business logic, running safely under a transaction with TransactWrite(connection=Connection(), client_request_token=generate_uuid()) as transaction: transaction.save( Store( name=request.name, delay=request.delay, working_since=request.working_since.strftime( "%Y-%m-%d %H:%M:%S"), )) for employee_name in request.employees: transaction.save(Employee(name=employee_name))
def test_condition_check__no_condition(self): with pytest.raises(TypeError): with TransactWrite(connection=Connection()) as transaction: transaction.condition_check(MockModel, hash_key=1, condition=None)
office_out_of_date.save() # After refreshing the local copy the operation will succeed. office_out_of_date.refresh() office_out_of_date.employees.remove(garrett) office_out_of_date.save() assert office_out_of_date.version == 3 # Condition check fails for delete. with assert_condition_check_fails(): office.delete() # Example failed transactions. connection = Connection(host='http://localhost:8000') with assert_condition_check_fails(), TransactWrite(connection=connection) as transaction: transaction.save(Office(office.office_id, name='newer name', employees=[])) with assert_condition_check_fails(), TransactWrite(connection=connection) as transaction: transaction.update( Office(office.office_id, name='newer name', employees=[]), actions=[ Office.name.set('Newer Office Name'), ] ) with assert_condition_check_fails(), TransactWrite(connection=connection) as transaction: transaction.delete(Office(office.office_id, name='newer name', employees=[])) # Example successful transaction. office2 = Office(office_id=str(uuid4()), name="second office", employees=[justin])
def test_commit(self, mocker): connection = Connection() mock_connection_transact_write = mocker.patch.object( connection, 'transact_write_items') with patch(PATCH_METHOD) as req: req.return_value = MOCK_TABLE_DESCRIPTOR with TransactWrite(connection=connection) as t: t.condition_check( MockModel, 1, 3, condition=(MockModel.mock_hash.does_not_exist())) t.delete(MockModel(2, 4)) t.save(MockModel(3, 5)) t.update(MockModel(4, 6), actions=[MockModel.mock_toot.set('hello')], return_values='ALL_OLD') expected_condition_checks = [{ 'ConditionExpression': 'attribute_not_exists (#0)', 'ExpressionAttributeNames': { '#0': 'mock_hash' }, 'Key': { 'MockHash': { 'N': '1' }, 'MockRange': { 'N': '3' } }, 'TableName': 'mock' }] expected_deletes = [{ 'ConditionExpression': 'attribute_not_exists (#0)', 'ExpressionAttributeNames': { '#0': 'mock_version' }, 'Key': { 'MockHash': { 'N': '2' }, 'MockRange': { 'N': '4' } }, 'TableName': 'mock' }] expected_puts = [{ 'ConditionExpression': 'attribute_not_exists (#0)', 'ExpressionAttributeNames': { '#0': 'mock_version' }, 'Item': { 'MockHash': { 'N': '3' }, 'MockRange': { 'N': '5' }, 'mock_version': { 'N': '1' } }, 'TableName': 'mock' }] expected_updates = [{ 'ConditionExpression': 'attribute_not_exists (#0)', 'TableName': 'mock', 'Key': { 'MockHash': { 'N': '4' }, 'MockRange': { 'N': '6' } }, 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD', 'UpdateExpression': 'SET #1 = :0, #0 = :1', 'ExpressionAttributeNames': { '#0': 'mock_version', '#1': 'mock_toot' }, 'ExpressionAttributeValues': { ':0': { 'S': 'hello' }, ':1': { 'N': '1' } } }] mock_connection_transact_write.assert_called_once_with( condition_check_items=expected_condition_checks, delete_items=expected_deletes, put_items=expected_puts, update_items=expected_updates, client_request_token=None, return_consumed_capacity=None, return_item_collection_metrics=None)