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 thedynamorm.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 MarshmallowSchema
or SchematicsModel
.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.
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.
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")