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.