Skip to content

Documents

As you are probably aware, MongoDB is a NoSQL database, which means it doesn't have tables but documents instead.

This also means that with documents and NoSQL, there are no joins and foreign keys.

Mongoz implements those documents in a more friendly interface if you are still familiar with ORMs or even if you use something like Mongoz. No reason to overcomplicate, right?

Declaring documents

When declaring documents by simply inheriting from mongoz.Document object and define the attributes using the mongoz Fields.

For each document defined you also need to set one mandatory field, the registry which is also an instance of Registry from Mongoz.

There are more parameters you can use and pass into the document such as tablename and a few more but more on this in this document.

Since Mongoz took inspiration from the interface of Mongoz, that also means that a Meta class should be declared.

Although this looks very simple, in fact Mongoz is doing a lot of work for you behind the scenes.

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Document):
    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    is_active: bool = mongoz.Boolean(default=True)

    class Meta:
        registry = registry
        database = "my_db"

Embedded Documents

There is a special section dedicated to explain what is this and how simple it is to use with the current documents.

The Meta class

When declaring a document, it is crucial having the Meta class declared. There is where you declare the metadata needed for your documents.

Currently the available parameters for the meta are:

  • registry - The registry instance for where the document will be generated. This field is mandatory and it will raise an ImproperlyConfigured error if no registry is found.

  • collection - The name of the table (collection) in the database, not the class name.

    Default: name of class pluralised

  • database - The name of the database where the document will be created.

  • abstract - If the document is abstract or not. If is abstract, then it won't generate the database table.

    Default: False

  • indexes - The extra custom indexes you want to add to the document.

  • autogenerate_index - If the indexes should be automatically generated by Mongoz.

    Default: False

Registry

Working with a registry is what makes Mongoz dynamic and very flexible with the familiar interface we all love. Without the registry, the document doesn't know where it should get the data from.

Imagine a registry like a bridge because it does exactly that.

Let us see some examples in how to use the registry with simple design and with some more complex approaches.

In a nutshell

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Model):
    name: str = mongoz.String(max_length=255)
    is_active: bool = mongoz.Boolean(default=True)

    class Meta:
        registry = registry
        database = "my_db"

As you can see, when declaring the registry and assigning it to registry, that same registry is then used in the Meta of the document.

With inheritance

Yes, you can also use the document inheritance to help you out with your documents and avoid repetition.

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class BaseDocument(mongoz.Document):
    """
    The base document for all documents using the `registry` registry.
    """

    class Meta:
        abstract = True
        registry = registry
        database = "my_db"


class User(BaseDocument):
    name: str = mongoz.String(max_length=255)
    is_active: bool = mongoz.Boolean(default=True)


class Product(BaseDocument):
    sku: str = mongoz.String(max_length=255, null=False)

As you can see, the User and Product tables are inheriting from the BaseDocument where the registry was already declared. This way you can avoid repeating yourself over and over again.

The reason for the abstract=True it is because we do not want to create a document of that specific class in the database.

This can be particularly useful if you have more than one registry in your system and you want to split the bases by responsabilities.

With abstract classes

What if your class is abstract? Can you inherit the registry anyway?

Of course! That doesn't change anything with the registry.

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class BaseDocument(mongoz.Document):
    """
    The base document for all documents using the `registry` registry.
    """

    class Meta:
        abstract = True
        registry = registry
        database = "my_db"


class User(BaseDocument):
    name: str = mongoz.String(max_length=255)
    is_active: bool = mongoz.Boolean(default=True)


class Product(BaseDocument):
    sku: str = mongoz.String(max_length=255, null=False)

Table name

This is actually very simple and also comes with defaults. When creating a document if a collection field in the Meta object is not declared, it will pluralise the python class.

Document without table name

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Document):
    """
    If the `tablename` is not declared in the `Meta`,
    edgy will pluralise the class name.

    This table will be called in the database `users`.
    """

    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    is_active: bool = mongoz.Boolean(default=True)

    class Meta:
        registry = registry
        database = "my_db"

As mentioned in the example, because a collection was not declared, Mongoz will pluralise the python class name User and it will become users in your database.

Document with a table name

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Document):
    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    is_active: bool = mongoz.Boolean(default=True)

    class Meta:
        registry = registry
        collection = "users"
        database = "my_db"

Here the collection is being explicitly declared as users. Although it matches with a puralisation of the python class name, this could also be something else.

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Document):
    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    is_active: bool = mongoz.Boolean(default=True)

    class Meta:
        registry = registry
        collection = "db_users"
        database = "my_db"

In this example, the User class will be represented by a db_users mapping into the database.

Tip

Calling collection with a different name than your class it doesn't change the behaviour in your codebase. You will still access the given table in your codebase via main class.

Abstract

As the name suggests, it is when you want to declare an abstract document.

Why do you need an abstract document in the first place? Well, for the same reason when you need to declare an abstract class in python but for this case you simply don't want to generate a table from that document declaration.

This can be useful if you want to hold common functionality across documents and don't want to repeat yourself.

The way of declaring an abstract document in Mongoz is by passing True to the abstract attribute in the meta class.

In a nutshell

In this document we already mentioned abstract documents and how to use them but let us use some more examples to be even clear.

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class BaseDocument(mongoz.Document):
    class Meta:
        abstract = True
        registry = registry
        database = "my_db"

This document itself does not do much alone. This simply creates a BaseDocument and declares the registry as well as declares the abstract as True.

Use abstract documents to hold common functionality

Taking advantage of the abstract documents to hold common functionality is usually the common use case for these to be use in the first place.

Let us see a more complex example and how to use it.

import uuid

import mongoz

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class BaseDocument(mongoz.Document):
    name: str = mongoz.String(max_length=255)

    class Meta:
        abstract = True
        registry = registry
        database = "my_db"

    def get_description(self):
        """
        Returns the description of a record
        """
        return getattr(self, "description", None)


class User(BaseDocument):
    """
    Inheriting the fields from the abstract class
    as well as the Meta data.
    """

    phone_number: str = mongoz.String(max_length=15)
    description: str = mongoz.String()

    def transform_phone_number(self):
        # logic here for the phone number
        ...


class Product(BaseDocument):
    """
    Inheriting the fields from the abstract class
    as well as the Meta data.
    """

    sku: str = mongoz.String(max_length=255)
    description: str = mongoz.String()

    def get_sku(self):
        # Logic to obtain the SKU
        ...

This is already quite a complex example where User and Product have both common functionality like the id and description as well the get_description() function.

Indexes

Sometimes you might want to add specific designed indexes to your documents. Database indexes also somes with costs and you should always be careful when creating one.

Mongoz provides an Index object that must be used when declaring documents indexes.

from mongoz import Index, IndexType, Order

The IndexType has the supported PyMongo index types:

  • GEO2D
  • GEOSPHERE
  • HASHED
  • TEXT

The Order is is the PyMongo order:

  • ASCENDING
  • DESCENDING

Parameters

The Index parameters are:

  • key - A string name of the field (key) for the index.
  • keys (Optional) - List of python tuples (string, order) for the index.
  • name - The index name.

The Index in Mongoz is an extension of the pymongo.IndexModel.

Creation of the indexes

When creating an index with mongoz you can do it in three different ways.

  1. Declare in the field directly by passing index=True.
  2. Declare in the Meta.
  3. Combine both field and metaclass.

When those are declared, Mongoz will automatically create those indexes for you if the autogenerate_index from the meta is True.

If the autogenerated_index is False, you will need to manually create the indexes.

Simple index

The simplest and cleanest way of declaring an index with Mongoz. You declare it directly in the document field.

import mongoz
from mongoz import Index

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Model):
    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    email: str = mongoz.Email(max_length=70, index=True, unique=True)
    is_active: bool = mongoz.Boolean(default=True)
    status: str = mongoz.String(max_length=255)

    class Meta:
        registry = registry
        database = "my_db"

Index via Meta

import mongoz
from mongoz import Index

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Model):
    name: str = mongoz.String(max_length=255)
    age: int = mongoz.Integer()
    email: str = mongoz.Email(max_length=70)
    is_active: bool = mongoz.Boolean(default=True)
    status: str = mongoz.String(max_length=255)

    class Meta:
        registry = registry
        database = "my_db"
        indexes = [Index("name", unique=True)]

Complex indexes

import mongoz
from mongoz import Database, Index, IndexType, Order, Registry

database_uri = "mongodb://localhost:27017"
registry = mongoz.Registry(database_uri)


class User(mongoz.Model):
    name: str = mongoz.String(max_length=255, index=True, unique=True)
    age: int = mongoz.Integer()
    email: str = mongoz.Email(max_length=70)
    is_active: bool = mongoz.Boolean(default=True)
    status: str = mongoz.String(max_length=255)

    class Meta:
        registry = registry
        database = "my_db"
        indexes = [
            Index(keys=[("age", Order.DESCENDING), ("email", IndexType.HASHED)]),
        ]

Index Operations

Working with indexes is pretty easy and there are some operations you can manually perform in your documents and others you need to manually perform in your documents.

Creating indexes

When indexes are declared in your documents, Mongoz will know what to do automatically for you if you have set the flag autogenerate_index=True in the metaclass.

The function create_indexes() is what is used to generate all the indexes declared.

If the flag is set to False, then you can simply create the indexes by calling:

await MyDocument.create_indexes()

For example, using the previous User document, it would be something like this:

await User.create_indexes()

Create individual indexes

What if you only want to manually create an index? Well that is also possible by calling create_index().

Tip

This is only necessary/needed if the autogenerate_index is set to False, otherwise this can be ignored.

The syntax is very clear ans simple:

await MyDocument.create_index(<INDEX-NAME>)

For example, using the previous User where the name was created to also be an index, it would be something like this:

await User.create_index("name")

Warning

If you try to create an index with a field name not declared in the document or not declared as index=True, at least, a InvalidKeyError is raised.

Dropping indexes

This is now the opposite of create indexes and this must be manually performed.

The function drop_indexes() is what is used to drop all the indexes declared in the document.

Note that this will only drop the indexes declared in the document only, so you can rest assured that no other index is affected.

For example, using the previous User document, it would be something like this:

await User.drop_indexes()

Danger

Running the drop_indexes() will drop all the indexes of the declared document, so be careful and be absolutely sure that you are running this operation with care.

Drop individual indexes

What if you only want to manually drop an index? Well that is also possible by calling drop_index().

The syntax is very clear ans simple:

await MyDocument.drop_index(<INDEX-NAME>)

For example, using the previous User where the name was an index, it would be something like this:

await User.drop_index("name")

Warning

If you try to drop an index with a field name not declared in the document or not declared as Index, at least, a InvalidKeyError is raised.