4. Mapping CouchDB documents to Python objects: couchdb.mapping

Mapping from raw JSON data structures to Python objects and vice versa.

>>> from couchdb import Server
>>> server = Server()
>>> db = server.create('python-tests')

To define a document mapping, you declare a Python class inherited from Document, and add any number of Field attributes:

>>> from datetime import datetime
>>> from couchdb.mapping import Document, TextField, IntegerField, DateTimeField
>>> class Person(Document):
...     name = TextField()
...     age = IntegerField()
...     added = DateTimeField(default=datetime.now)
>>> person = Person(name='John Doe', age=42)
>>> person.store(db) 
<Person ...>
>>> person.age
42

You can then load the data from the CouchDB server through your Document subclass, and conveniently access all attributes:

>>> person = Person.load(db, person.id)
>>> old_rev = person.rev
>>> person.name
u'John Doe'
>>> person.age
42
>>> person.added                
datetime.datetime(...)

To update a document, simply set the attributes, and then call the store() method:

>>> person.name = 'John R. Doe'
>>> person.store(db)            
<Person ...>

If you retrieve the document from the server again, you should be getting the updated data:

>>> person = Person.load(db, person.id)
>>> person.name
u'John R. Doe'
>>> person.rev != old_rev
True
>>> del server['python-tests']

4.1. Field types

class couchdb.mapping.TextField(name=None, default=None)

Mapping field for string values.

class couchdb.mapping.FloatField(name=None, default=None)

Mapping field for float values.

class couchdb.mapping.IntegerField(name=None, default=None)

Mapping field for integer values.

class couchdb.mapping.LongField(name=None, default=None)

Mapping field for long integer values.

class couchdb.mapping.BooleanField(name=None, default=None)

Mapping field for boolean values.

class couchdb.mapping.DecimalField(name=None, default=None)

Mapping field for decimal values.

class couchdb.mapping.DateField(name=None, default=None)

Mapping field for storing dates.

>>> field = DateField()
>>> field._to_python('2007-04-01')
datetime.date(2007, 4, 1)
>>> field._to_json(date(2007, 4, 1))
'2007-04-01'
>>> field._to_json(datetime(2007, 4, 1, 15, 30))
'2007-04-01'
class couchdb.mapping.DateTimeField(name=None, default=None)

Mapping field for storing date/time values.

>>> field = DateTimeField()
>>> field._to_python('2007-04-01T15:30:00Z')
datetime.datetime(2007, 4, 1, 15, 30)
>>> field._to_python('2007-04-01T15:30:00.009876Z')
datetime.datetime(2007, 4, 1, 15, 30, 0, 9876)
>>> field._to_json(datetime(2007, 4, 1, 15, 30, 0))
'2007-04-01T15:30:00Z'
>>> field._to_json(datetime(2007, 4, 1, 15, 30, 0, 9876))
'2007-04-01T15:30:00.009876Z'
>>> field._to_json(date(2007, 4, 1))
'2007-04-01T00:00:00Z'
class couchdb.mapping.DictField(mapping=None, name=None, default=None)

Field type for nested dictionaries.

>>> from couchdb import Server
>>> server = Server()
>>> db = server.create('python-tests')
>>> class Post(Document):
...     title = TextField()
...     content = TextField()
...     author = DictField(Mapping.build(
...         name = TextField(),
...         email = TextField()
...     ))
...     extra = DictField()
>>> post = Post(
...     title='Foo bar',
...     author=dict(name='John Doe',
...                 email='john@doe.com'),
...     extra=dict(foo='bar'),
... )
>>> post.store(db) 
<Post ...>
>>> post = Post.load(db, post.id)
>>> post.author.name
u'John Doe'
>>> post.author.email
u'john@doe.com'
>>> post.extra
{u'foo': u'bar'}
>>> del server['python-tests']
class couchdb.mapping.ListField(field, name=None, default=None)

Field type for sequences of other fields.

>>> from couchdb import Server
>>> server = Server()
>>> db = server.create('python-tests')
>>> class Post(Document):
...     title = TextField()
...     content = TextField()
...     pubdate = DateTimeField(default=datetime.now)
...     comments = ListField(DictField(Mapping.build(
...         author = TextField(),
...         content = TextField(),
...         time = DateTimeField()
...     )))
>>> post = Post(title='Foo bar')
>>> post.comments.append(author='myself', content='Bla bla',
...                      time=datetime.now())
>>> len(post.comments)
1
>>> post.store(db) 
<Post ...>
>>> post = Post.load(db, post.id)
>>> comment = post.comments[0]
>>> comment['author']
u'myself'
>>> comment['content']
u'Bla bla'
>>> comment['time'] 
u'...T...Z'
>>> del server['python-tests']
class couchdb.mapping.ViewField(design, map_fun, reduce_fun=None, name=None, language='javascript', wrapper=<object object>, **defaults)

Descriptor that can be used to bind a view definition to a property of a Document class.

>>> class Person(Document):
...     name = TextField()
...     age = IntegerField()
...     by_name = ViewField('people', '''\
...         function(doc) {
...             emit(doc.name, doc);
...         }''')
>>> Person.by_name
<ViewDefinition '_design/people/_view/by_name'>
>>> print(Person.by_name.map_fun)
function(doc) {
    emit(doc.name, doc);
}

That property can be used as a function, which will execute the view.

>>> from couchdb import Database
>>> db = Database('python-tests')
>>> Person.by_name(db, count=3)
<ViewResults <PermanentView '_design/people/_view/by_name'> {'count': 3}>

The results produced by the view are automatically wrapped in the Document subclass the descriptor is bound to. In this example, it would return instances of the Person class. But please note that this requires the values of the view results to be dictionaries that can be mapped to the mapping defined by the containing Document class. Alternatively, the include_docs query option can be used to inline the actual documents in the view results, which will then be used instead of the values.

If you use Python view functions, this class can also be used as a decorator:

>>> class Person(Document):
...     name = TextField()
...     age = IntegerField()
...
...     @ViewField.define('people')
...     def by_name(doc):
...         yield doc['name'], doc
>>> Person.by_name
<ViewDefinition '_design/people/_view/by_name'>
>>> print(Person.by_name.map_fun)
def by_name(doc):
    yield doc['name'], doc