diff --git a/examples/SDK/python/data_plane/WorkingWithItems/put_item_conditional.py b/examples/SDK/python/data_plane/WorkingWithItems/put_item_conditional.py index 1e726b7..5a0e925 100644 --- a/examples/SDK/python/data_plane/WorkingWithItems/put_item_conditional.py +++ b/examples/SDK/python/data_plane/WorkingWithItems/put_item_conditional.py @@ -1,2 +1,48 @@ -import boto3, json -from boto3.dynamodb.conditions import Key \ No newline at end of file +from __future__ import print_function # Python 2/3 compatibility +import boto3 +from boto3.dynamodb.conditions import Attr +from botocore.exceptions import ClientError + +dynamodb = boto3.resource("dynamodb", region_name="us-west-2") +table = dynamodb.Table("RetailDatabase") + + +def create_item_if_not_exist(created_at): + try: + table.put_item( + Item={ + "pk": "jim.bob@somewhere.com", + "sk": "metadata", + "name": "Jim Bob", + "first_name": "Jim", + "last_name": "Bob", + "created_at": created_at, + }, + # If this item exists, then an item exists with this pk and/or sk so we should fail if + # we see a matching item with this pk. + ConditionExpression=Attr("pk").not_exists(), + ) + except dynamodb.meta.client.exceptions.ConditionalCheckFailedException: + print("PutItem failed since the item already exists") + return False + + print("PutItem succeeded") + return True + + +def get_created_at(email): + response = table.get_item( + Key={"pk": email, "sk": "metadata"}, + AttributesToGet=["created_at"], + ) + return response.get("Item", {}).get("created_at") + + +print("The first PutItem should succeed because the item does not exist") +assert create_item_if_not_exist(100) + +print("The same PutItem command should now fail because the item already exists") +assert not create_item_if_not_exist(200) + +assert get_created_at("jim.bob@somewhere.com") == 100 +print("As expected, The second PutItem command failed and the data was not overwritten") diff --git a/examples/SDK/python/data_plane/WorkingWithItems/update_item_conditional.py b/examples/SDK/python/data_plane/WorkingWithItems/update_item_conditional.py index b525012..2999d25 100644 --- a/examples/SDK/python/data_plane/WorkingWithItems/update_item_conditional.py +++ b/examples/SDK/python/data_plane/WorkingWithItems/update_item_conditional.py @@ -1,2 +1,105 @@ -import boto3, json -from boto3.dynamodb.conditions import Key +from __future__ import print_function # Python 2/3 compatibility +import boto3 +from boto3.dynamodb.conditions import Attr +from botocore.exceptions import ClientError + +dynamodb = boto3.resource("dynamodb", region_name="us-west-2") +table = dynamodb.Table("RetailDatabase") + + +class ConditionalCheckFailedError(Exception): + """ + An error indicating that a DynamoDB conditional check failed. + Wrapped as a separate error for readability. + """ + + +def delete_item(email): + response = table.delete_item( + Key={ + "pk": email, + "sk": "metadata", + }, + ) + + +def create_user(email, name): + initial_change_version = 0 + + try: + table.put_item( + Item={ + "pk": email, + "sk": "metadata", + "name": name, + "change_version": initial_change_version, + }, + ConditionExpression=Attr("pk").not_exists(), + ) + except dynamodb.meta.client.exceptions.ConditionalCheckFailedException as e: + raise ConditionalCheckFailedError() from e + + return initial_change_version + + +def update_name(email, name, last_change_version): + try: + response = table.update_item( + Key={"pk": email, "sk": "metadata"}, + UpdateExpression="SET #n = :nm, #cv = #cv + :one", + ExpressionAttributeNames={"#n": "name", "#cv": "change_version"}, + ExpressionAttributeValues={":nm": name, ":one": 1}, + ReturnValues="UPDATED_NEW", + ConditionExpression=Attr("change_version").eq(last_change_version), + ) + except dynamodb.meta.client.exceptions.ConditionalCheckFailedException as e: + raise ConditionalCheckFailedError() from e + + return int(response.get("Attributes", {}).get("change_version")) + + +def get_user(email): + response = table.get_item( + Key={"pk": email, "sk": "metadata"}, + AttributesToGet=["name", "change_version"], + ) + return { + "email": email, + "name": response["Item"]["name"], + "change_version": int(response["Item"]["change_version"]), + } + + +def main(): + delete_item("jim.bob@somewhere.com") + + print("Creating an item with change_version = 0") + last_change_version = create_user("jim.bob@somewhere.com", "Jim Bob") + current_user = get_user("jim.bob@somewhere.com") + print("current_user = ", current_user) + + print("Change the user's name") + last_change_version = update_name( + "jim.bob@somewhere.com", "Jim Roberts", last_change_version + ) + current_user = get_user("jim.bob@somewhere.com") + print("current_user = ", current_user) + + print( + "Try to update the name with an old change_version imitating a race condition" + ) + try: + last_change_version = update_name( + "jim.bob@somewhere.com", "Jonathan Roberts", last_change_version - 1 + ) + except ConditionalCheckFailedError: + print("Yup, this failed as expected, the user information did not change") + else: + raise RuntimeError("This should have failed") + + current_user = get_user("jim.bob@somewhere.com") + print("current_user = ", current_user) + + +if __name__ == "__main__": + main()