Skip to content

Commit

Permalink
Merge pull request #27 from xoeye/feat/single-item-write
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Gaultney authored Oct 5, 2021
2 parents 73f3244 + 2486f6a commit ef91cde
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.16.0

- `write_item` single item write helper for the `write_versioned`
higher level DynamoDB transaction API. Suitable for any form of
transactional write to a single item (creation/update/deletion).

### 1.15.1

Fixes to `versioned_transact_write_items`:
Expand Down
20 changes: 20 additions & 0 deletions tests/xoto3/dynamodb/write_versioned/api2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
update_existing,
update_if_exists,
versioned_transact_write_items,
write_item,
)

from .conftest import mock_next_run
Expand Down Expand Up @@ -96,3 +97,22 @@ def test_api2_create_or_update():
create_or_update(table, lambda x: dict(key, foo=1), key), **mock_next_run(vt),
)
assert table.require(key)(vt)["foo"] == 1


def test_api2_write_item_creates():
vt, table = _fake_table("felicity", "id")
key = dict(id="goodbye")
vt = versioned_transact_write_items(
write_item(table, lambda ox: dict(key, bar=8), key), **mock_next_run(vt),
)
assert table.require(key)(vt)["bar"] == 8


def test_api2_write_item_deletes():
vt, table = _fake_table("felicity", "id")
key = dict(id="goodbye")
vt = table.presume(key, dict(key, baz=9))(vt)
vt = versioned_transact_write_items(
write_item(table, lambda x: None, key), **mock_next_run(vt),
)
assert table.get(key)(vt) is None
2 changes: 1 addition & 1 deletion xoto3/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""xoto3"""
__version__ = "1.15.1"
__version__ = "1.16.0"
__author__ = "Peter Gaultney"
__author_email__ = "pgaultney@xoi.io"
9 changes: 8 additions & 1 deletion xoto3/dynamodb/write_versioned/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
here) is not guaranteed to remain, so don't do that. Import this
module and use only what it exposes.
"""
from .api2 import ItemTable, TypedTable, create_or_update, update_existing, update_if_exists # noqa
from .api2 import ( # noqa
ItemTable,
TypedTable,
create_or_update,
update_existing,
update_if_exists,
write_item,
)
from .errors import ( # noqa
ItemUndefinedException,
TableSchemaUnknownError,
Expand Down
37 changes: 37 additions & 0 deletions xoto3/dynamodb/write_versioned/api2.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ def _item_ident(item: Item) -> Item:
return TypedTable(table_name, deepcopy, _item_ident, item_name=item_name)


# The following are simple single-item-write helpers with various type
# signatures for different semantics around your expectations for
# whether an item already exists, what to do if it doesn't, and
# whether the item is guaranteed to exist at the end of a successful
# call. They are provided simply as a convenience - they do nothing
# that an individual application could not do on its own.


def update_if_exists(
table: TypedTable[T], updater: Callable[[T], T], key: ItemKey,
) -> TransactionBuilder:
Expand Down Expand Up @@ -168,3 +176,32 @@ def create_or_update_trans(vt: VersionedTransaction) -> VersionedTransaction:
return table.put(creator_updater(table.get(key)(vt)))(vt)

return create_or_update_trans


def write_item(
table: TypedTable[T], writer: Callable[[Optional[T]], Optional[T]], key: ItemKey
) -> TransactionBuilder:
"""The most general purpose single-item-write abstraction, with
necessarily weak type constraints on the writer implementation.
Note that in DynamoDB parlance, a write can be either a Put or a
Delete, and our usage of that terminology here parallels
theirs. Creation, Updating, and Deleting are all in view here.
Specifically, your writer function (as with all our other helpers
defined here) should return _exactly_ what it intends to have
represented in the database at the end of the transaction. If you
wish to make no change, simply return the unmodified item. If you
wish to _delete_ an item, return None - this indicates that you
want the value of the item to be null, i.e. deleted from the
table.
"""

def write_single_item(vt: VersionedTransaction) -> VersionedTransaction:
resulting_item = writer(table.get(key)(vt))
if resulting_item is None:
return table.delete(key)(vt)
else:
return table.put(resulting_item)(vt)

return write_single_item

0 comments on commit ef91cde

Please sign in to comment.