Transactions¶
Bloop supports reading and updating items in transactions similar to the way you already load, save, and delete items using an engine. A single read or write transaction can have at most 10 items.
To create a new transaction, call Engine.transaction(mode="w")
and specify
a mode:
wx = engine.transaction(mode="w")
rx = engine.transaction(mode="r")
When used as a context manager the transaction will call
commit()
on exit if no exception occurs:
# mode defaults to "w"
with engine.transaction() as tx:
tx.save(some_obj)
tx.delete(other_obj)
# read transaction loads all objects at once
user = User(id="numberoverzero")
meta = Metadata(id=to_load.id)
with engine.transaction(mode="r") as tx:
tx.load(user, meta)
You may also call prepare()
and
commit()
yourself:
import bloop
tx = engine.transaction()
tx.save(some_obj)
p = tx.prepare()
try:
p.commit()
except bloop.TransactionCanceled:
print("failed to commit")
See TransactionCanceled
for the conditions that can cause each type of transaction to fail.
Write Transactions¶
A write transaction can save and delete items, and specify additional conditions on objects not being modified.
As with Engine.save and Engine.delete you can provide multiple
objects to each WriteTransaction.save()
or
WriteTransaction.delete()
call:
with engine.transaction() as tx:
tx.delete(*old_tweets)
tx.save(new_user, new_tweet)
Item Conditions¶
You can specify a condition
with each save or delete call:
with engine.transaction() as tx:
tx.delete(auth_token, condition=Token.last_used <= now())
Transaction Conditions¶
In addition to specifying conditions on the objects being modified, you can also specify a condition for the transaction on an object that won't be modified. This can be useful if you want to check another table without changing its value:
user_meta = Metadata(id="numberoverzero")
with engine.transaction() as tx:
tx.save(new_tweet)
tx.check(user_meta, condition=Metadata.verified.is_(True))
In the above example the transaction doesn't modify the user metadata. If we want to modify that object we should instead use a condition on the object being modified:
user_meta = Metadata(id="numberoverzero")
engine.load(user_meta)
user_meta.tweets += 1
with engine.transaction() as tx:
tx.save(new_tweet)
tx.save(user_meta, condition=Metadata.tweets <= 500)
Idempotency¶
Bloop automatically generates timestamped unique tokens (tx_id
and
first_commit_at
)
to guard against committing a write transaction twice or accidentally committing a transaction that was prepared a
long time ago. While these are generated for both read and write commits, only TransactWriteItems respects the
"ClientRequestToken" stored in tx_id.
When the first_commit_at
value is too old,
committing will raise TransactionTokenExpired
.
Read Transactions¶
By default engine.transaction(mode="w")
will create a
WriteTransaction
. To create a ReadTransaction
pass
mode="r"
:
with engine.transaction(mode="r") as rx:
rx.load(user, tweet)
rx.load(meta)
All objects in the read transaction will be loaded at the same time, when
commit()
is called or the transaction context closes.
Multiple Commits¶
Every time you call commit on the prepared transaction, the objects will be loaded again:
rx = engine.transaction(mode="r")
rx.load(user, tweet)
prepared = rx.prepare()
prepared.commit() # first load
prepared.commit() # second load
Missing Objects¶
As with Engine.load if any objects in the transaction are missing when commit is called,
bloop will raise MissingObjects
with the list of objects that were not found:
import bloop
engine = bloop.Engine()
...
def tx_load(*objs):
with engine.transaction(mode="r") as rx:
rx.load(*objs)
...
try:
tx_load(user, tweet)
except bloop.MissingObjects as exc:
missing = exc.objects
print(f"failed to load {len(missing)} objects: {missing}")