Like this project? it on

Basics

Welcome! This tutorial will help you to get up & running with Blitz. For a more comprehensive overview of Blitz, please consult the API documentation or the documentation of specific backends. Let’s get started!

Working with Documents

Just like in Python, in Blitz all documents are objects. To create a new type of document, you just define a class that derives from blitzdb.document.Document:

from blitzdb import Document

class Actor(Document):
    pass

class Movie(Document):
    pass

That’s it! We can now create and work with instances of Actor and Movie documents:

charlie_chaplin = Actor({
                         'first_name' : 'Charlie',
                         'last_name' : 'Chaplin',
                         'is_funny' : True,
                         'birth_year' : 1889,
                         'filmography' : [
                            ('The Kid',1921),
                            ('A Woman of Paris',1923),
                            #...
                            ('Modern Times', 1936)
                         ]
                        })

We can access the document attributes of the given instances as class attributes:

print "%s %s was born in %d" % (charlie_chaplin.first_name,
                                charlie_chaplin.last_name,
                                charlie_chaplin.birth_year)

Alternatively, we can use the attributes attribute to access them:

print "%(first_name)s %(last_name)s was born in %(birth_year)d" % charlie_chaplin.attributes

This is also pretty useful if you define attributes that have names which get shadowed by methods of the Document class (e.g. save or filter).

Connecting to a database

To store documents in a database, you need to create a backend first. Blitz supports multiple backends (currently a file-based one and one that wraps MongoDB). In this tutorial we will use a file-based backend, which you create like this:

from blitzdb import FileBackend

backend = FileBackend("./my-db")

This connects Blitz to a file-based database within the “./my-db” directory, or creates a new database there if none should be present. The backend provides various functions such as save, get, filter and delete, which can be used to store, retrieve, update and delete objects. Let’s have a look at these operations.

Note

You can choose between different formats to store your documents when using the file-based backend, using e.g. the json, pickle or marshal Python libraries. Choose the document format when creating the backend by passing a configuration dictionary, e.g. backend = FileBackend("./my-db", {'serializer_class': 'pickle'}) ('serializer_class' can also be 'json' or 'marshal'). By default, all documents will be stored as gzipped JSON files.

Warning

The default serializer class is 'json', but this does not allow a perfect roundtrip from python to JSON and back. Python supports many more datatypes than JSON, see the python JSON documentation.

Inserting Documents

We can store the Author object that we created before in our new database like this:

backend.save(charlie_chaplin)

Alternatively, we can also directly call the save function of the Actor instance with the backend as an argument:

charlie_chaplin.save(backend)

In addition, since Blitz is a transactional database, we have to call the commit function of the backend to write the new document to disk:

#Will commit changes to disk
backend.commit()

Note

Use the Backend.begin function to start a new database transaction and the Backend.rollback function to roll back the state of the database to the beginning of a transaction, if needed. By default, Blitz uses a local isolation level for transactions, so changes you make to the state of the database will be visible to parts of your program using the same backend, but will only be written to disk when Backend.commit is invoked. If you like autocommits set the Backend.autocomit to True after instantiating the backend

Retrieving Documents

Retrieving objects from the database is just as easy. If we want to get a single object, we can use the get() method, specifying the Document class and any combination of attributes that uniquely identifies the document:

actor = backend.get(Actor,{'first_name' : 'Charlie','last_name' : 'Chaplin'})

Alternatively, if we know the primary key of the object, we can just specify this:

the_kid = Movie({'title' : 'The Kid'})
actor = backend.get(Actor,{'pk' : charlie_chaplin.pk})

Note

Pro-Tip

If Blitz can’t find a document matching your query, it will raise a Document.DoesNotExist exception. Likewise, if it finds more than one document matching your query it will raise Document.MultipleDocumentsReturned. These exceptions are specific to the document class to which they belong and can be accessed as attributes of it, e.g. like this:

try:
    actor = backend.get(Actor,{'first_name' : 'Charlie'})
except Actor.DoesNotExist:
    #no 'Charlie' in the database
    pass
except Actor.MultipleDocumentsReturned:
    #more than one 'Charlie' in the database
    pass

If we want to retrieve all objects matching a given query, we can use the filter() method instead:

#Retrieve all actors that were born in 1889
actors = backend.filter(Actor,{'birth_year' : 1889})

This will return a QuerySet, which contains a list of keys of all objects that match our query. Query sets are iterables, so we can use them just like lists:

print "Found %d actors" % len(actors)
for actor in actors:
    print actor.first_name+" "+actor.last_name

Updating Documents

We can update documents on the database by changing or adding attributes on the object and then calling the save()

actor.death_year = 1977
actor.save()

Deleting Documents

We can delete documents from the database by calling the delete() method of the backend with an instance of the object that we wish to delete:

backend.delete(charlie_chaplin)

This will remove the document from the given collection and set its primary key to None. We can delete a whole query set in the same way by calling its delete() method:

#Retrieve all actors from the database
actors = backend.filter(Actor,{})
actors.delete()

Defining Relationships

Databases are pretty useless if there’s no way to define relationships between objects. Like MongoDB, Blitz supports defining references to other documents inside of documents. An example:

modern_times = Movie({
                      'title' : 'Modern Times',
                      'year' : 1936,
                      'budget' : 1500000,
                      'run_time_minutes' : 87,
                     })

charlie_chaplin.movies = [modern_times]
modern_times.actors = [charlie_chaplin]

#this will automatically save the movie object as well
backend.save(charlie_chaplin)

Internally, BlitzDB converts any Document instance that it encounters inside a document to a database reference that contains the primary key of the embedded document and the the name of the collection in which it is stored. Like this, if we reload the actor from the database, the embedded movie objects will get automatically (lazy-)loaded as well:

actor = backend.filter(Actor,{'first_name' : 'Charlie','last_name' : 'Chaplin'})

#check that the movies in the retrieved Actor document are instances of Movie
assert isinstance(actor.movies[0],Movie)

#will print 'Modern Times'
print actor.movies[0].title

Note

When an object gets loaded from the database, references to other objects that it contains will get loaded lazily, i.e. they will get initialized with only their primary key and the name of the collection they can be found in. Their attributes will get automatically loaded if (and only if) you should request them.

Like this, Blitz avoids performing multiple reads from the database unless they are really needed. As a bonus, lazy loading also solves the problem of cyclic document references (like in the example above).

Advanced Querying

Like MongoDB, Blitz supports advanced query operators, which you can include in your query be prefixing them with a $. Currently, the following operator expressions are supported:

  • $and : Performs a boolean AND on two or more expressions
  • $or : Performs a boolean OR on two or more expressions
  • $gt : Performs a > comparision between an attribute and a specified value
  • $gte : Performs a >= comparision between an attribute and a specified value
  • $lt : Performs a < comparision between an attribute and a specified value
  • $lte : Performs a <= comparision between an attribute and a specified value
  • $all : Returns documents containing all values in the argument list.
  • $in : Returns documents matching at least one of the values in the argument list.
  • $ne : Performs a not equal operation on the given expression
  • $not : Checks for non-equality between an attribute and the given value.
  • $regex : Provides regular expression capabilities for pattern matching.
  • $exists : Checks for field existing in all documents.

The syntax and semantics of these operators is identical to MongoDB, so for further information have a look at their documentation.

Example: Boolean AND

By default, if you specify more than one attribute in a query, an implicit $and query will be performed, returning only the documents that match all attribute/value pairs given in your query. You can also specify this behavior explicitly by using then $and operator, so the following two queries are identical:

backend.filter(Actor,{'first_name' : 'Charlie','last_name' : 'Chaplin'})
#is equivalent to...
backend.filter(Actor,{'$and' : [{'first_name' : 'Charlie'},{'last_name' : 'Chaplin'}]})

Using $and can be necessary if you want to reference the same document attribute more than once in your query, e.g. like this:

#Get all actors born between 1900 and 1940
backend.filter(Actor,{'$and' : [{'birth_year' : {'$gte' : 1900}},{'birth_year' : {'$lte' : 1940}}]})

Where to Go from Here

Currently there are no other tutorials available (this will change soon), so if you have further questions, feel free to send us an e-mail or post an issue on Github. The test suite also contains a large number of examples on how to use the API to work with documents.