Base Concepts

In μMongo 3 worlds are considered:

Data Flow

Client world

This is the data from outside μMongo, it can be JSON dict from your web framework (i.g. request.get_json() with flask or json.loads(request.raw_post_data) in django or it could be regular Python dict with Python-typed data

JSON dict example

{"str_field": "hello world", "int_field": 42, "date_field": "2015-01-01T00:00:00Z"}

Python dict example

{"str_field": "hello world", "int_field": 42, "date_field": datetime(2015, 1, 1)}

To be integrated into μMongo, those data need to be unserialized. Same thing to leave μMongo they need to be serialized (under the hood μMongo uses marshmallow schema). The unserialization operation is done automatically when instantiating a :class:umongo.Document. The serialization is done when calling :meth:umongo.Document.dump on a document instance.

Object Oriented world

So what's good about :class:umongo.Document ? Well it allows you to work with your data as Objects and to guarantee there validity against a model.

First let's define a document with few :mod:umongo.fields

@instance.register
class Dog(Document):
    name = fields.StrField(required=True)
    breed = fields.StrField(default="Mongrel")
    birthday = fields.DateTimeField()

First don't pay attention to the @instance.register, this is for later ;-)

Note that each field can be customized with special attributes like required (which is pretty self-explanatory) or default (if the field is missing during unserialization it will take this value).

Now we can play back and forth between OO and client worlds

>>> client_data = {'name': 'Odwin', 'birthday': '2001-09-22T00:00:00Z'}
>>> odwin = Dog(**client_data)
>>> odwin.breed
"Mongrel"

>>> odwin.birthday
datetime.datetime(2001, 9, 22, 0, 0)

>>> odwin.breed = "Labrador"
>>> odwin.dump()
{'birthday': '2001-09-22T00:00:00+00:00', 'breed': 'Labrador', 'name': 'Odwin'}

Info

You can access the data as attribute (i.g. odwin.name) or as item (i.g. odwin['name']). The latter is specially useful if one of your field names clashes with :class:umongo.Document's attributes.

OO world enforces model validation for each modification

>>> odwin.bad_field = 42
[...]
AttributeError: bad_field

>>> odwin.birthday = "not_a_date"
[...]
ValidationError: "Not a valid datetime."

Info

Just one exception: required attribute is validated at insertion time, we'll talk about that later.

Object orientation means inheritance, of course you can do that

@instance.register
class Animal(Document):
    breed = fields.StrField()
    birthday = fields.DateTimeField()

    class Meta:
        allow_inheritance = True
        abstract = True

@instance.register
class Dog(Animal):
    name = fields.StrField(required=True)

@instance.register
class Duck(Animal):
    pass

Note the Meta subclass, it is used (along with inherited Meta classes from parent documents) to configure the document class, you can access this final config through the opts attribute.

Here we use this to allow Animal to be inheritable and to make it abstract.

>>> Animal.opts
<DocumentOpts(instance=<umongo.frameworks.PyMongoInstance object at 0x7efe7daa9320>, template=<Document template class '__main__.Animal'>, abstract=True, allow_inheritance=True, collection_name=None, is_child=False, base_schema_cls=<class 'umongo.schema.Schema'>, indexes=[], offspring={<Implementation class '__main__.Duck'>, <Implementation class '__main__.Dog'>})>

>>> Dog.opts
<DocumentOpts(instance=<umongo.frameworks.PyMongoInstance object at 0x7efe7daa9320>, template=<Document template class '__main__.Dog'>, abstract=False, allow_inheritance=False, collection_name=dog, is_child=False, base_schema_cls=<class 'umongo.schema.Schema'>, indexes=[], offspring=set())>

>>> class NotAllowedSubDog(Dog): pass
[...]
DocumentDefinitionError: Document <class '__main__.Dog'> doesn't allow inheritance

>>> Animal(breed="Mutant")
[...]
AbstractDocumentError: Cannot instantiate an abstract Document

Mongo world

What the point of a MongoDB ODM without MongoDB ? So here it is !

Mongo world consist of data returned in a format comprehensible by a MongoDB driver (pymongo for instance).

>>> odwin.to_mongo()
{'birthday': datetime.datetime(2001, 9, 22, 0, 0), 'name': 'Odwin'}

Well it our case the data haven't change much (if any !). Let's consider something more complex:

@instance.register
class Dog(Document):
    name = fields.StrField(attribute='_id')

Here we decided to use the name of the dog as our _id key, but for readability we keep it as name inside our document.

>>> odwin = Dog(name='Odwin')
>>> odwin.dump()
{'name': 'Odwin'}

>>> odwin.to_mongo()
{'_id': 'Odwin'}

>>> Dog.build_from_mongo({'_id': 'Scruffy'}).dump()
{'name': 'Scruffy'}

Note

If no field refers to _id in the document, a dump-only field id will be automatically added:

>>> class AutoId(Document):
...     pass
>>> AutoId.find_one()
<object Document __main__.AutoId({'id': ObjectId('5714b9a61d41c8feb01222c8')})>

But what about if we what to retrieve the _id field whatever it name is ? No problem, use the pk property:

>>> odwin.pk
'Odwin'

>>> Duck().pk
None

Ok so now we got our data in a way we can insert it to MongoDB through our favorite driver. In fact most of the time you don't need to use to_mongo directly. Instead you can directly ask the document to commit it changes in database:

>>> odwin = Dog(name='Odwin', breed='Labrador')
>>> odwin.commit()

You get also access to Object Oriented version of your driver methods:

>>> Dog.find()
<umongo.dal.pymongo.WrappedCursor object at 0x7f169851ba68>

>>> next(Dog.find())
<object Document __main__.Dog({'id': 'Odwin', 'breed': 'Labrador'})>

>>> Dog.find_one({'_id': 'Odwin'})
<object Document __main__.Dog({'id': 'Odwin', 'breed': 'Labrador'})>

You can also access the collection used by the document at any time (for example to do more low-level operations):

>>> Dog.collection
Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test'), 'dog')

Note

By default the collection to use is the snake-cased version of the document's name (e.g. Dog => dog, HTTPError => http_error). However, you can configure (remember the Meta class ?) the collection to use for a document with the collection_name meta attribute.