.. _userguide: ========== User guide ========== Base concepts ============= In μMongo 3 worlds are considered: .. figure:: data_flow.png :alt: data flow in μMongo Client world ------------ This is the data from outside μMongo, it can be a 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 a regular Python dict with Python-typed data. JSON dict example .. code-block:: python >>> {"str_field": "hello world", "int_field": 42, "date_field": "2015-01-01T00:00:00Z"} Python dict example .. code-block:: python >>> {"str_field": "hello world", "int_field": 42, "date_field": datetime(2015, 1, 1)} To be integrated into μMongo, those data need to be deserialized and to leave μMongo they need to be serialized (under the hood μMongo uses `marshmallow`_ schemas). The deserialization 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 --------------------- :class:`umongo.Document` allows you to work with your data as objects and to guarantee their validity against a model. First let's define a document with few :mod:`umongo.fields` .. code-block:: python @instance.register class Dog(Document): name = fields.StrField(required=True) breed = fields.StrField(default="Mongrel") birthday = fields.DateTimeField() Don't pay attention to the ``@instance.register`` for now. 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 deserialization it will take this value). Now we can play back and forth between OO and client worlds .. code-block:: python >>> 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'} .. note:: 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 name clashes with :class:`umongo.Document`'s attributes. OO world enforces model validation for each modification .. code-block:: python >>> odwin.bad_field = 42 [...] AttributeError: bad_field >>> odwin.birthday = "not_a_date" [...] ValidationError: "Not a valid datetime." .. note: Just one exception: ``required`` attribute is validate at insertion time, we'll talk about that later. Object orientation means inheritance, of course you can do that .. code-block:: python @instance.register class Animal(Document): breed = fields.StrField() birthday = fields.DateTimeField() class Meta: abstract = True @instance.register class Dog(Animal): name = fields.StrField(required=True) @instance.register class Duck(Animal): pass The ``Meta`` subclass 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 inherited and to make it abstract. .. code-block:: python >>> Animal.opts , template=, abstract=True, collection_name=None, is_child=False, base_schema_cls=, indexes=[], offspring={, })> >>> Dog.opts , template=, abstract=False, collection_name=dog, is_child=False, base_schema_cls=, indexes=[], offspring=set())> >>> class NotAllowedSubDog(Dog): pass [...] DocumentDefinitionError: Document doesn't allow inheritance >>> Animal(breed="Mutant") [...] AbstractDocumentError: Cannot instantiate an abstract Document Mongo world ----------- Mongo world consist of data returned in a format suitable for a MongoDB driver (`pymongo `_ for instance). .. code-block:: python >>> odwin.to_mongo() {'birthday': datetime.datetime(2001, 9, 22, 0, 0), 'name': 'Odwin'} In this case, the data is unchanged. Let's consider something more complex: .. code-block:: python @instance.register class Dog(Document): name = fields.StrField(attribute='_id') We use the name of the dog as our ``_id`` key, but for readability we keep it as ``name`` inside our document. .. code-block:: python >>> 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: .. code-block:: python >>> class AutoId(Document): ... pass >>> AutoId.find_one() To retrieve the ``_id`` field whatever its name is, use the ``pk`` property: .. code-block:: python >>> odwin.pk 'Odwin' >>> Duck().pk None Most of the time, the user doesn't need to use ``to_mongo`` directly. It is called internally by :meth:`umongo.Document.commit`` which is the method used to commit changes to the database. .. code-block:: python >>> odwin = Dog(name='Odwin', breed='Labrador') >>> odwin.commit() μMongo provides access to Object Oriented versions of driver methods: .. code-block:: python >>> Dog.find() >>> next(Dog.find()) Dog.find_one({'_id': 'Odwin'}) The user can also access the collection used by the document at any time to perform more low-level operations: .. code-block:: python >>> 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, through the ``Meta`` class, the collection to use for a document with the ``collection_name`` meta attribute. Multi-driver support ==================== The idea behind μMongo is to allow the same document definition to be used with different MongoDB drivers. To achieve that the user only defines document templates. Templates which will be implemented when registered by an instance: .. figure:: instance_template.png :alt: instance/template mechanism in μMongo Basically an instance provide three informations: - the mongoDB driver type to use - the database to use - the implemented documents This way a template can be implemented by multiple instances, this can be useful for example to: - store the same documents in differents databases - define an instance with async driver for a web server and a sync one for shell interactions Here's how to create and use an instance: .. code-block:: python >>> from umongo.frameworks import PyMongoInstance >>> import pymongo >>> con = pymongo.MongoClient() >>> instance1 = PyMongoInstance(con.db1) >>> instance2 = PyMongoInstance(con.db2) Now we can define & register documents, then work with them: .. code-block:: python >>> class Dog(Document): ... pass >>> Dog # mark as a template in repr