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 Edgy. 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 Edgy, 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.
- Declare in the field directly by passing
index=True
. - Declare in the Meta.
- 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.
Document checks¶
If you also want to make sure that you run the proper checks for the indexes, for example, an index was dropped from the document and you want to make sure that is reflected, you can also do it.
The syntax is very clear ans simple:
await MyDocument.check_indexes()
For example:
await User.check_indexes()
This can be useful if you want to make sure that for every registry, all the documents have the indexes checked before hand.
Read more about that in that section.
Create indexes for multiple databases¶
What if you have the same document in multiple databases (multi tenancy, for example) and you would like to reflect the indexes in all of them? Mongoz comes with that option as well.
The database names must be passed as a list or tuple of strings.
The syntax is as simlpe as this:
await MyDocument.create_indexes_for_multiple_databases(["db_one", "db_two", "db_three"])
For example:
await User.create_indexes_for_multiple_databases(["db_one", "db_two", "db_three"])
Drop indexes for multiple databases¶
What if you have the same document in multiple databases (multi tenancy, for example) and you would like to drop the indexes in all of them? Mongoz comes with that option as well.
The database names must be passed as a list or tuple of strings.
The syntax is as simlpe as this:
await MyDocument.drop_indexes_for_multiple_databases(["db_one", "db_two", "db_three"])
For example:
await User.drop_indexes_for_multiple_databases(["db_one", "db_two", "db_three"])