Usage

Using DynamORM is straight forward. Simply define your models with some specific meta data to represent the DynamoDB Table structure as well as the document schema. You can then use class level methods to query for and get items, represented as instances of the class, as well as class level methods to interact with specific documents in the table.

Note

Not all functionality is covered in this documentation yet. See the tests for all “supported” functionality (like: batch puts, unique puts, etc).

Setting up Boto3

Make sure you have configured boto3 and can access DynamoDB from the Python console.

import boto3
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
list(dynamodb.tables.all())  # --> ['table1', 'table2', 'etc...']

Using Dynamo Local

If you’re using Dynamo Local for development you will need to configure DynamORM appropriately by manually calling get_resource on any model’s Table object, which takes the same parameters as boto3.resource. The dynamodb boto resource is shared globally by all models, so it only needs to be done once. For example:

MyModel.Table.get_resource(
    aws_access_key_id="anything",
    aws_secret_access_key="anything",
    region_name="us-west-2",
    endpoint_url="http://localhost:8000"
)

Defining your Models – Tables & Schemas

Models represent tables in DynamoDB and define the characteristics of the Dynamo service as well as the Marshmallow schema that is used for validating and marshalling your data.

class dynamorm.model.DynaModel(**raw)

DynaModel is the base class all of your models will extend from. This model definition encapsulates the parameters used to create and manage the table as well as the schema for validating and marshalling data into object attributes. It will also hold any custom business logic you need for your objects.

Your class must define two inner classes that specify the Dynamo Table options and the Schema, respectively.

The Dynamo Table options are defined in a class named Table. See the dynamorm.table module for more information.

The document schema is defined in a class named Schema, which should be filled out exactly as you would fill out any other Marshmallow Schema or Schematics Model.

For example:

# Marshmallow example
import os

from dynamorm import DynaModel

from marshmallow import fields, validate, validates, ValidationError

class Thing(DynaModel):
    class Table:
        name = '{env}-things'.format(env=os.environ.get('ENVIRONMENT', 'dev'))
        hash_key = 'id'
        read = 5
        write = 1

    class Schema:
        id = fields.String(required=True)
        name = fields.String()
        color = fields.String(validate=validate.OneOf(('purple', 'red', 'yellow')))
        compound = fields.Dict(required=True)

        @validates('name')
        def validate_name(self, value):
            # this is a very silly example just to illustrate that you can fill out the
            # inner Schema class just like any other Marshmallow class
            if name.lower() == 'evan':
                raise ValidationError("No Evan's allowed")

    def say_hello(self):
        print("Hello.  {name} here.  My ID is {id} and I'm colored {color}".format(
            id=self.id,
            name=self.name,
            color=self.color
        ))
# Schematics example
import os

from dynamorm import DynaModel

from schematics import types

class Thing(DynaModel):
    class Table:
        name = '{env}-things'.format(env=os.environ.get('ENVIRONMENT', 'dev'))
        hash_key = 'id'
        read = 5
        write = 1

    class Schema:
        id = types.StringType(required=True, max_length=10)
        name = types.StringType()
        color = types.StringType()
        compound = types.DictType(types.IntType, required=True)

    def say_hello(self):
        print("Hello.  {name} here.  My ID is {id} and I'm colored {color}".format(
            id=self.id,
            name=self.name,
            color=self.color
        ))

Table Data Model

The inner Table class on DynaModel definitions becomes an instance of our dynamorm.table.DynamoTable3 class.

The attributes you define on your inner Table class map to underlying boto data structures. This mapping is expressed through the following data model:

Attribute Required Type Description
name True str The name of the table, as stored in Dynamo.
hash_key True str The name of the field to use as the hash key. It must exist in the schema.
range_key False str The name of the field to use as the range_key, if one is used. It must exist in the schema.
read True int The provisioned read throughput.
write True int The provisioned write throughput.

Creating new documents

Using objects:

thing = Thing(id="thing1", name="Thing One", color="purple")
thing.save()
thing = Thing()
thing.id = "thing1"
thing.name = "Thing One"
thing.color = "purple"
thing.save()

Using raw documents:

Thing.put({
    "id": "thing1",
    "name": "Thing One",
    "color": "purple"
})

In all cases, the attributes go through validation against the Schema.

thing = Thing(id="thing1", name="Thing One", color="orange")

# the call to save will result in a ValidationError because orange is an invalid choice.
thing.save()

Note

Remember, if you have a String field it will use unicode (py2) or str (py3) on any value assigned to it, which means that if you assign a list, dict, int, etc then the validation will succeed and what will be stored is the representative string value.

Fetching existing documents

Get based on primary key

To fetch an existing document based on its primary key you use the .get class method on your models:

thing1 = Thing.get(id="thing1")
assert thing1.color == 'purple'

Querying

A Query operation uses the primary key of a table or a secondary index to directly access items from that table or index.

Table query docs

Like a get operation this takes arguments that map to the key names, but you can also specify a comparison operator for that key using the “double-under” syntax (<field>__<operator>). For example to query a Book model for all entries with the isbn field that start with a specific value you would use the begins_with comparison operator:

Book.query(isbn__begins_with="12345")

You can find the full list of supported comparison operators in the Table query docs.

Scanning

The Scan operation returns one or more items and item attributes by accessing every item in a table or a secondary index.

Table scan docs

Scanning works exactly the same as querying: comparison operators are specified using the “double-under” syntax (<field>__<operator>).

# Scan based on attributes
Book.scan(author="Mr. Bar")
Book.scan(author__ne="Mr. Bar")