Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,3 @@ Use optimistic locking to put DynamoDB records with auto-incrementing attributes

- https://aws.amazon.com/blogs/aws/new-amazon-dynamodb-transactions/
- https://bitesizedserverless.com/bite/reliable-auto-increments-in-dynamodb/

## FIXME

This package currently depends on code that is in a pull request for boto3 that is not yet merged or released.
See https://github.com/boto/boto3/pull/4010.
27 changes: 8 additions & 19 deletions dynamodb_autoincrement.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

from mypy_boto3_dynamodb.service_resource import DynamoDBServiceResource

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


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

def _put_item(self, *, TableName, **kwargs):
# FIXME: DynamoDB resource does not have put_item method; emulate it
self.dynamodb.Table(TableName).put_item(**kwargs)

def _get_item(self, *, TableName, **kwargs):
# FIXME: DynamoDB resource does not have get_item method; emulate it
return self.dynamodb.Table(TableName).get_item(**kwargs)

def _query(self, *, TableName, **kwargs):
# FIXME: DynamoDB resource does not have put_item method; emulate it
return self.dynamodb.Table(TableName).query(**kwargs)

def put(self, item: DynamoDBItem):
TransactionCanceledException = (
self.dynamodb.meta.client.exceptions.TransactionCanceledException
Expand All @@ -53,11 +44,9 @@ def put(self, item: DynamoDBItem):
puts, next_counter = self.next(item)
if self.dangerously:
for put in puts:
self._put_item(**put)
self.dynamodb.put_item(**put) # type: ignore[attr-defined]
else:
try:
# FIXME: depends on an unmerged PR for boto3.
# See https://github.com/boto/boto3/pull/4010
self.dynamodb.transact_write_items( # type: ignore[attr-defined]
TransactItems=[{"Put": put} for put in puts]
)
Expand All @@ -69,7 +58,7 @@ def put(self, item: DynamoDBItem):
class DynamoDBAutoIncrement(BaseDynamoDBAutoIncrement):
def next(self, item):
counter = (
self._get_item(
self.dynamodb.get_item(
AttributesToGet=[self.attribute_name],
Key=self.counter_table_key,
TableName=self.counter_table_name,
Expand Down Expand Up @@ -117,7 +106,7 @@ def next(self, item):

class DynamoDBHistoryAutoIncrement(BaseDynamoDBAutoIncrement):
def list(self) -> list[int]:
result = self._query(
result = self.dynamodb.query( # type: ignore[attr-defined]
TableName=self.table_name,
ExpressionAttributeNames={
**{f"#{i}": key for i, key in enumerate(self.counter_table_key.keys())},
Expand Down Expand Up @@ -145,10 +134,10 @@ def get(self, version: Optional[int] = None) -> DynamoDBItem:
"TableName": self.table_name,
"Key": {**self.counter_table_key, self.attribute_name: version},
}
return self._get_item(**kwargs).get("Item")
return self.dynamodb.get_item(**kwargs).get("Item") # type: ignore[attr-defined]

def next(self, item):
existing_item = self._get_item(
existing_item = self.dynamodb.get_item(
TableName=self.counter_table_name,
Key=self.counter_table_key,
).get("Item")
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ classifiers = [
"Topic :: Database",
]
dependencies = [
"boto3 @ git+https://github.com/lpsinger/boto3@dynamodb-resource-transact-write-items",
"boto3",
"boto3-missing",
"boto3-stubs[dynamodb]",
]
requires-python = ">=3.9"
Expand Down
13 changes: 7 additions & 6 deletions test_dynamodb_autoincrement.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,20 @@ def test_autoincrement_safely(autoincrement_safely, dynamodb, last_id):
if last_id is None:
next_id = 1
else:
dynamodb.Table("autoincrement").put_item(
Item={"tableName": "widgets", "widgetID": last_id}
dynamodb.put_item(
TableName="autoincrement",
Item={"tableName": "widgets", "widgetID": last_id},
)
next_id = last_id + 1

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

assert dynamodb.Table("widgets").scan()["Items"] == [
assert dynamodb.scan(TableName="widgets")["Items"] == [
{"widgetID": next_id, "widgetName": "runcible spoon"},
]

assert dynamodb.Table("autoincrement").scan()["Items"] == [
assert dynamodb.scan(TableName="autoincrement")["Items"] == [
{
"tableName": "widgets",
"widgetID": next_id,
Expand Down Expand Up @@ -152,7 +153,7 @@ def test_autoincrement_dangerously_fails_on_many_parallel_puts(
@pytest.fixture(params=[None, {"widgetID": 1}, {"widgetID": 1, "version": 1}])
def initial_item(request, create_tables, dynamodb):
if request.param is not None:
dynamodb.Table("widgets").put_item(Item=request.param)
dynamodb.put_item(TableName="widgets", Item=request.param)
return request.param


Expand All @@ -174,7 +175,7 @@ def test_autoincrement_version(
)
assert new_version == 1 + has_initial_item

history_items = dynamodb.Table("widgetHistory").query(
history_items = dynamodb.query(
TableName="widgetHistory",
KeyConditionExpression="widgetID = :widgetID",
ExpressionAttributeValues={
Expand Down