Skip to content

Commit 01baa52

Browse files
committed
Don't require upstream patches for boto3
The AWS Python SDK, [boto3], has [resource] objects that provide high-level interfaces to AWS services. The [DynamoDB resource] greatly simplifies marshalling and unmarshalling data. We rely on the resource method for [TransactWriteItems] among others that are absent from boto3. We opened PR boto/boto3#4010 to add that method. The resource methods are synthesized at runtime from a data file. Fortunately, boto3 has a [Loader] mechanism that allows the user to add extra data files, and the [loader search path] is configurable. In order to not depend upon our upstream PR for boto3, we distribute the extra data files and fix up the loader search path by putting it in a [.pth file] which Python executes automatically during startup. The data and .pth file are now part of an external package, [boto3-missing]. [boto3]: https://github.com/boto/boto3 [resource]: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/resources.html [DynamoDB resource]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#resources [TransactWriteItems]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html [Loader]: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/loaders.html [loader search path]: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/loaders.html#the-search-path [.pth file]: https://docs.python.org/3/library/site.html [boto3-missing]: https://github.com/nasa-gcn/boto3-missing
1 parent c6a4b29 commit 01baa52

File tree

4 files changed

+15
-30
lines changed

4 files changed

+15
-30
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,3 @@ Use optimistic locking to put DynamoDB records with auto-incrementing attributes
66

77
- https://aws.amazon.com/blogs/aws/new-amazon-dynamodb-transactions/
88
- https://bitesizedserverless.com/bite/reliable-auto-increments-in-dynamodb/
9-
10-
## FIXME
11-
12-
This package currently depends on code that is in a pull request for boto3 that is not yet merged or released.
13-
See https://github.com/boto/boto3/pull/4010.

dynamodb_autoincrement.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
from mypy_boto3_dynamodb.service_resource import DynamoDBServiceResource
1111

12+
# FIXME: remove instances of 'type: ignore[attr-defined]' below once
13+
# boto3-missing becomes unnecessary.
14+
1215

1316
PrimitiveDynamoDBValues = Optional[Union[str, int, float, Decimal, bool]]
1417
DynamoDBValues = Union[
@@ -33,18 +36,6 @@ class BaseDynamoDBAutoIncrement(ABC):
3336
def next(self, item: DynamoDBItem) -> tuple[Iterable[dict[str, Any]], str]:
3437
raise NotImplementedError
3538

36-
def _put_item(self, *, TableName, **kwargs):
37-
# FIXME: DynamoDB resource does not have put_item method; emulate it
38-
self.dynamodb.Table(TableName).put_item(**kwargs)
39-
40-
def _get_item(self, *, TableName, **kwargs):
41-
# FIXME: DynamoDB resource does not have get_item method; emulate it
42-
return self.dynamodb.Table(TableName).get_item(**kwargs)
43-
44-
def _query(self, *, TableName, **kwargs):
45-
# FIXME: DynamoDB resource does not have put_item method; emulate it
46-
return self.dynamodb.Table(TableName).query(**kwargs)
47-
4839
def put(self, item: DynamoDBItem):
4940
TransactionCanceledException = (
5041
self.dynamodb.meta.client.exceptions.TransactionCanceledException
@@ -53,11 +44,9 @@ def put(self, item: DynamoDBItem):
5344
puts, next_counter = self.next(item)
5445
if self.dangerously:
5546
for put in puts:
56-
self._put_item(**put)
47+
self.dynamodb.put_item(**put) # type: ignore[attr-defined]
5748
else:
5849
try:
59-
# FIXME: depends on an unmerged PR for boto3.
60-
# See https://github.com/boto/boto3/pull/4010
6150
self.dynamodb.transact_write_items( # type: ignore[attr-defined]
6251
TransactItems=[{"Put": put} for put in puts]
6352
)
@@ -69,7 +58,7 @@ def put(self, item: DynamoDBItem):
6958
class DynamoDBAutoIncrement(BaseDynamoDBAutoIncrement):
7059
def next(self, item):
7160
counter = (
72-
self._get_item(
61+
self.dynamodb.get_item(
7362
AttributesToGet=[self.attribute_name],
7463
Key=self.counter_table_key,
7564
TableName=self.counter_table_name,
@@ -117,7 +106,7 @@ def next(self, item):
117106

118107
class DynamoDBHistoryAutoIncrement(BaseDynamoDBAutoIncrement):
119108
def list(self) -> list[int]:
120-
result = self._query(
109+
result = self.dynamodb.query( # type: ignore[attr-defined]
121110
TableName=self.table_name,
122111
ExpressionAttributeNames={
123112
**{f"#{i}": key for i, key in enumerate(self.counter_table_key.keys())},
@@ -145,10 +134,10 @@ def get(self, version: Optional[int] = None) -> DynamoDBItem:
145134
"TableName": self.table_name,
146135
"Key": {**self.counter_table_key, self.attribute_name: version},
147136
}
148-
return self._get_item(**kwargs).get("Item")
137+
return self.dynamodb.get_item(**kwargs).get("Item") # type: ignore[attr-defined]
149138

150139
def next(self, item):
151-
existing_item = self._get_item(
140+
existing_item = self.dynamodb.get_item(
152141
TableName=self.counter_table_name,
153142
Key=self.counter_table_key,
154143
).get("Item")

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ classifiers = [
1616
"Topic :: Database",
1717
]
1818
dependencies = [
19-
"boto3 @ git+https://github.com/lpsinger/boto3@dynamodb-resource-transact-write-items",
19+
"boto3",
20+
"boto3-missing",
2021
"boto3-stubs[dynamodb]",
2122
]
2223
requires-python = ">=3.9"

test_dynamodb_autoincrement.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,19 @@ def test_autoincrement_safely(autoincrement_safely, dynamodb, last_id):
9191
if last_id is None:
9292
next_id = 1
9393
else:
94-
dynamodb.Table("autoincrement").put_item(
94+
dynamodb.put_item(TableName="autoincrement",
9595
Item={"tableName": "widgets", "widgetID": last_id}
9696
)
9797
next_id = last_id + 1
9898

9999
result = autoincrement_safely.put({"widgetName": "runcible spoon"})
100100
assert result == next_id
101101

102-
assert dynamodb.Table("widgets").scan()["Items"] == [
102+
assert dynamodb.scan(TableName="widgets")["Items"] == [
103103
{"widgetID": next_id, "widgetName": "runcible spoon"},
104104
]
105105

106-
assert dynamodb.Table("autoincrement").scan()["Items"] == [
106+
assert dynamodb.scan(TableName="autoincrement")["Items"] == [
107107
{
108108
"tableName": "widgets",
109109
"widgetID": next_id,
@@ -152,7 +152,7 @@ def test_autoincrement_dangerously_fails_on_many_parallel_puts(
152152
@pytest.fixture(params=[None, {"widgetID": 1}, {"widgetID": 1, "version": 1}])
153153
def initial_item(request, create_tables, dynamodb):
154154
if request.param is not None:
155-
dynamodb.Table("widgets").put_item(Item=request.param)
155+
dynamodb.put_item(TableName="widgets", Item=request.param)
156156
return request.param
157157

158158

@@ -174,7 +174,7 @@ def test_autoincrement_version(
174174
)
175175
assert new_version == 1 + has_initial_item
176176

177-
history_items = dynamodb.Table("widgetHistory").query(
177+
history_items = dynamodb.query(
178178
TableName="widgetHistory",
179179
KeyConditionExpression="widgetID = :widgetID",
180180
ExpressionAttributeValues={

0 commit comments

Comments
 (0)