Using MongoMotor - An introduction

In this introductory tutorial lets create a simple music catalog. In this catalog we’ll have artists, albums and musics.

Connecting to a database

The first thing to do is to connect to a database (an instance of mongod). To do so we will use connect(). In this example all we need is to call the function with a single parameter, the database name.

from mongomotor import connect

connect('music-catalog')

Note

connect() accepts a async_framework named parameter that indicates which asynchronous framework we should use, either asyncio or tornado. It defaults to asyncio. The other arguments to connect() are passed to mongoengine’s connect() function. See the Connecting guide at mongoengine docs.

Defining documents

MongoDB does not enforce a schema in the database making life a lot easier when we want to add or remove fields. However is very useful to have a schemata defined to help us in the development process this is why we’ll define some documents here.

Artists

In our example we will create a collection of documents to store information about artists. To create a document that have its own collection we need to subclass Document. Let’s say that we want to distinguish between two kinds of artists, single musicians, those that release solo albums and musical groups. To achieve that we can have a base Artist and a SingleMusician and a MusicalGroup inheriting from Artist. Doing so all artists will be stored in the same collection and we can query for them separately (or together if we want to to so).

from mongomotor import Document
from mongomotor.fields import ListField, StringField


class Artist(Document):
    name = StringField()

    # here we say that this call may be subclassed.
    # If we do not set it to True we will not be able
    # to subclass Artist.
    meta = {'allow_inheritance': True}


class SingleMusician(Artist):
    real_name = StringField()


class MusicalGroup(Artist):
    people = ListField(StringField())

With the documents created here we have a document Artist with a name field that accepts strings.The SingleMusician document has, other than name, the real_name that accepts a string too. The MusicalGroup document has a people field that is a list of strings (the names of the members of the group).

Musics & Albums

Now we will create a collection of documents for albums and the musics will be embedded documents in the album document. Albums will have references for artists. For more about embedded documents and references take a look at the MongoDB docs.

from mongomotor import EmbeddedDocument
from mongomotor.fields import ReferenceField, EmbeddedDocumentField, IntField()


class Music(EmbeddedDocument):
    number = IntField()
    title = StringField()


class Album(Document):
    title = StringField()
    musics = ListField(EmbeddedDocumentField(Music))
    artists = ListField(ReferenceField(Artist))

In these documents we have some different things. First we have the document Music that is an embedded document in the document collection. To reference to an embedded document we need to use EmbeddedDocumentField. Other than that we have a ReferenceField referencing Artist. In both cases we are using a list of embedded documents or references, but if we wanted, for example, only one artist per album we could use:

class Album(Document):
    title = StringField()
    tracks = ListField(EmbeddedDocumentField(Music))
    # this is only to show how that could works. Let's keep with
    # our list of artists in the rest of the example.
    artist = ReferenceField(Artist)

For more see Defining documents.

Insert and retrieving data

With our documents’ schema defined let’s add some documents to our database.

Inserting data

First let’s create some artists by creating an instance of SingleMusician or MusicalGroup and then use the save() with an await statement.

Note

All mongomotor database operations are done in coroutines and need a event loop running to succed. In these examples we will run only the database methods inside a coroutine and consume this coroutine with run_until_complete. In real life usually things are different we usually call run_until_complete only once. For more information see: asyncio loop.

>>> import asyncio
>>> loop = asyncio.get_event_loop()
>>> artist = SingleMusician(name='Tim Maia', real_name='Sebastião Maia')
>>> group = MusicalGroup()
>>> group.name = 'j.m.k.e.'
>>> group.people = ['Villu', 'Reimo', 'Andres', 'Livia', 'Promille']
>>>
>>> async def insert_artist():
...     await artist.save()
...     await group.save()
...     print(artist.id)
...     print(group.id)
...
>>> loop.run_until_complete(insert_artist())
57ac52e27c1c8440398a347e
57ac56767c1c8440398a347f

As you can see, an ID was created automatically when the document was saved to the database. Now, let’s create some albums and reference the artists in the albums.

>>> album1 = Album(title="Racional Vol. 1", artists=[artist])
>>> titles = ['Imunização Racional (Que beleza)', 'O Grão Mestre Varonil']
>>> album1.tracks = [Music(title=t, number=i) for i, t in enumerate(titles)]
>>> album2 = Album(title='Mälestusi Eesti NSV-st')
>>> titles = ['Medal', 'Ma ei saa sust aru']
>>> album2.tracks = [Music(title=t, number=i) for i, t in enumerate(titles)]
>>> # Now we will save the documents to the db. We don't use save() for
>>> # embedded documents.
>>> async def insert_albums():
...     await album1.save()
...     await from album2.save()
...
>>> loop.run_until_complete(insert_albums())

Retrieving data

Now we have some data and it is time to retrieve it from database. This is done throught the attribuite objects, that is a instance of QuerySet, in the subclasses of Document.

The simplest way of retrieving data is quering for a specific document using get().

>>> async def get_artist():
...     artist = await Artist.objects.get(name='Tim Maia')
...     print(artist.id, artist.real_name)
...
>>> loop.run_until_complete(get_artist())

Note

If a query does not return any documents or returns more than one document, the method get() will raise an exception.

We can query for more than one document we may use filter(). This method returns a queryset. To iterate over a queryset we use async for.

>>> async def list_artists():
...     async for artist in Artist.objects:
...         print(artist.name)
...         async for album in Album.objects.filter(artists=artist):
...             print(' - {}'.format(album.title))
...             for track in album.tracks:
...                 print('  - {}'.format(track.title))
...
>>> loop.run_until_complete(list_artists())

For more information see Querying the database.