Skip to content

Commit a853f95

Browse files
authored
Merge pull request #152 from mkamioner/python-conditional-samples
Python conditional samples
2 parents cadd904 + 0202586 commit a853f95

File tree

2 files changed

+153
-4
lines changed

2 files changed

+153
-4
lines changed
Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,48 @@
1-
import boto3, json
2-
from boto3.dynamodb.conditions import Key
1+
from __future__ import print_function # Python 2/3 compatibility
2+
import boto3
3+
from boto3.dynamodb.conditions import Attr
4+
from botocore.exceptions import ClientError
5+
6+
dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
7+
table = dynamodb.Table("RetailDatabase")
8+
9+
10+
def create_item_if_not_exist(created_at):
11+
try:
12+
table.put_item(
13+
Item={
14+
15+
"sk": "metadata",
16+
"name": "Jim Bob",
17+
"first_name": "Jim",
18+
"last_name": "Bob",
19+
"created_at": created_at,
20+
},
21+
# If this item exists, then an item exists with this pk and/or sk so we should fail if
22+
# we see a matching item with this pk.
23+
ConditionExpression=Attr("pk").not_exists(),
24+
)
25+
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
26+
print("PutItem failed since the item already exists")
27+
return False
28+
29+
print("PutItem succeeded")
30+
return True
31+
32+
33+
def get_created_at(email):
34+
response = table.get_item(
35+
Key={"pk": email, "sk": "metadata"},
36+
AttributesToGet=["created_at"],
37+
)
38+
return response.get("Item", {}).get("created_at")
39+
40+
41+
print("The first PutItem should succeed because the item does not exist")
42+
assert create_item_if_not_exist(100)
43+
44+
print("The same PutItem command should now fail because the item already exists")
45+
assert not create_item_if_not_exist(200)
46+
47+
assert get_created_at("[email protected]") == 100
48+
print("As expected, The second PutItem command failed and the data was not overwritten")
Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,105 @@
1-
import boto3, json
2-
from boto3.dynamodb.conditions import Key
1+
from __future__ import print_function # Python 2/3 compatibility
2+
import boto3
3+
from boto3.dynamodb.conditions import Attr
4+
from botocore.exceptions import ClientError
5+
6+
dynamodb = boto3.resource("dynamodb", region_name="us-west-2")
7+
table = dynamodb.Table("RetailDatabase")
8+
9+
10+
class ConditionalCheckFailedError(Exception):
11+
"""
12+
An error indicating that a DynamoDB conditional check failed.
13+
Wrapped as a separate error for readability.
14+
"""
15+
16+
17+
def delete_item(email):
18+
response = table.delete_item(
19+
Key={
20+
"pk": email,
21+
"sk": "metadata",
22+
},
23+
)
24+
25+
26+
def create_user(email, name):
27+
initial_change_version = 0
28+
29+
try:
30+
table.put_item(
31+
Item={
32+
"pk": email,
33+
"sk": "metadata",
34+
"name": name,
35+
"change_version": initial_change_version,
36+
},
37+
ConditionExpression=Attr("pk").not_exists(),
38+
)
39+
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException as e:
40+
raise ConditionalCheckFailedError() from e
41+
42+
return initial_change_version
43+
44+
45+
def update_name(email, name, last_change_version):
46+
try:
47+
response = table.update_item(
48+
Key={"pk": email, "sk": "metadata"},
49+
UpdateExpression="SET #n = :nm, #cv = #cv + :one",
50+
ExpressionAttributeNames={"#n": "name", "#cv": "change_version"},
51+
ExpressionAttributeValues={":nm": name, ":one": 1},
52+
ReturnValues="UPDATED_NEW",
53+
ConditionExpression=Attr("change_version").eq(last_change_version),
54+
)
55+
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException as e:
56+
raise ConditionalCheckFailedError() from e
57+
58+
return int(response.get("Attributes", {}).get("change_version"))
59+
60+
61+
def get_user(email):
62+
response = table.get_item(
63+
Key={"pk": email, "sk": "metadata"},
64+
AttributesToGet=["name", "change_version"],
65+
)
66+
return {
67+
"email": email,
68+
"name": response["Item"]["name"],
69+
"change_version": int(response["Item"]["change_version"]),
70+
}
71+
72+
73+
def main():
74+
delete_item("[email protected]")
75+
76+
print("Creating an item with change_version = 0")
77+
last_change_version = create_user("[email protected]", "Jim Bob")
78+
current_user = get_user("[email protected]")
79+
print("current_user = ", current_user)
80+
81+
print("Change the user's name")
82+
last_change_version = update_name(
83+
"[email protected]", "Jim Roberts", last_change_version
84+
)
85+
current_user = get_user("[email protected]")
86+
print("current_user = ", current_user)
87+
88+
print(
89+
"Try to update the name with an old change_version imitating a race condition"
90+
)
91+
try:
92+
last_change_version = update_name(
93+
"[email protected]", "Jonathan Roberts", last_change_version - 1
94+
)
95+
except ConditionalCheckFailedError:
96+
print("Yup, this failed as expected, the user information did not change")
97+
else:
98+
raise RuntimeError("This should have failed")
99+
100+
current_user = get_user("[email protected]")
101+
print("current_user = ", current_user)
102+
103+
104+
if __name__ == "__main__":
105+
main()

0 commit comments

Comments
 (0)