CRUD¶
This section covers the operations you can do to save, read, update, and delete
items from the database. All of these methods exist on the
Engine
object and can be called on one or many items.
After being saved-to or loaded-from Dynamo, the items themselves will have
these methods attached to them as well. For example, these are both valid:
>>> engine.sync(tweet)
>>> tweet.sync()
Save¶
Save the item to Dynamo. This is intended for new items that were just created
and need to be added to the database. If you save()
an item that already
exists in Dynamo, it will raise an exception. You may optionally use
save(overwrite=True)
to instead clobber existing data and write your
version of the item to Dynamo.
>>> tweet = Tweet()
>>> engine.save(tweet)
>>> tweet.text = "Let's replace the whole item"
>>> tweet.save(overwrite=True)
Refresh¶
Query dynamo to get the most up-to-date version of a model. Clobbers any
existing data on the item. To force a consistent read use
refresh(consistent=True)
.
This call is very useful if you query indexes that use an incomplete projection
type. The results won’t have all of the item’s fields, so you can call
refresh()
to get any attributes that weren’t projected onto the index.
>>> tweet = engine.query(Tweet).filter(userid='abc123')\
... .index('ts-index').first(desc=True)
>>> tweet.refresh()
Get¶
Fetch an item from its primary key fields. This will be faster than a query, but requires you to know the primary keys of all items you want fetched.
>>> my_tweet = engine.get(Tweet, userid='abc123', id='1')
You can also fetch many at a time:
>>> key1 = {'userid': 'abc123', 'id': '1'}
>>> key2 = {'userid': 'abc123', 'id': '2'}
>>> key3 = {'userid': 'abc123', 'id': '3'}
>>> some_tweets = engine.get(Tweet, [key1, key2, key3])
Delete¶
Deletes an item. You may pass in delete(raise_on_conflict=True)
, which will
only delete the item if none of the values have changed since it was read.
>>> tweet = engine.query(Tweet).filter(userid='abc123', id='123').first()
>>> tweet.delete()
You may also delete an item from a primary key specification:
>>> engine.delete_key(Tweet, userid='abc123', id='1')
And you may delete many at once:
>>> key1 = {'userid': 'abc123', 'id': '1'}
>>> key2 = {'userid': 'abc123', 'id': '2'}
>>> key3 = {'userid': 'abc123', 'id': '3'}
>>> engine.delete_key(Tweet, [key1, key2, key3])
Sync¶
Save any fields that have been changed on an item. This will update changed fields in Dynamo and ensure that all fields exactly reflect the item in the database. This is usually used for updates, but it can be used to create new items as well.
>>> tweet = Tweet()
>>> engine.sync(tweet)
>>> tweet.text = "Update just this field"
>>> tweet.sync()
Models will automatically detect changes to mutable fields, such as dict
,
list
, and set
.
>>> tweet.tags.add('awscloud')
>>> tweet.sync()
Since sync does a partial update, it can tolerate concurrent writes of different fields.
>>> tweet = engine.query(Tweet).filter(userid='abc123', id='1234').first()
>>> tweet2 = engine.query(Tweet).filter(userid='abc123', id='1234').first()
>>> tweet.author = "The Pope"
>>> tweet.sync()
>>> tweet2.text = "Mo' money mo' problems"
>>> tweet2.sync() # it works!
>>> print tweet2.author
The Pope
This “merge” behavior is also what happens when you sync()
items to create
them. If the item to create already exists in Dynamo, that’s fine as long as
there are no conflicting fields. Note that this behavior is distinctly
different from save()
, so make sure you pick the right call for your use
case.
If you call sync()
on an object that has not been changed, it is equivalent
to calling refresh()
.
Safe Sync¶
If you use sync(raise_on_conflict=True)
, the sync operation will check that
the fields that you’re updating have not been changed since you last read them.
This is very useful for preventing concurrent writes.
Note
If you change a key that is part of a composite field, flywheel will force the sync to raise on conflict. This avoids the risk of corrupting the value of the composite field.
Atomic Increment¶
DynamoDB supports truly atomic increment/decrement of NUMBER fields. To use this functionality, there is a special call you need to make:
>>> # Increment the number of retweets by 1
>>> tweet.incr_(retweets=1)
>>> tweet.sync()
BOOM.
Note
Incrementing a field that is part of a composite field will also force the sync to raise on conflict.
Atomic Add/Remove¶
DynamoDB also supports truly atomic add/remove to SET fields. To use this functionality, there is another special call:
>>> # Add two users to the set of tagged users
>>> tweet.add_(tags=set(['stevearc', 'dsa']))
>>> tweet.sync()
And to delete:
>>> tweet.remove_(tags='stevearc')
>>> tweet.sync()
Note than you can pass in a single value or a set of values to both add_
and remove_
.
Sync-if-Constraints¶
New in 0.2.1
You may pass in a list of constraints to check upon sync. If any of the
constraints fail, then the sync will not complete. This should be used with
raise_on_conflict=True
. For example:
>>> account = engine.get(Account, username='dsa')
>>> account.incr_(moneys=-200)
>>> # atomically remove $200 from DSA's account, iff there is at least $200 to remove.
>>> account.sync(constraints=[Account.moneys >= 200])
Default Conflict Behavior¶
You can configure the default behavior for each of these endpoints using
default_conflict
. The default setting will
cause sync()
to check for conflicts, delete()
not to check for
conflicts, and save()
to overwrite existing values. Check the attribute
docs for more options. You can, of course, pass in the argument to the calls
directly to override this behavior on a case-by-case basis.