Defining documents
Defining documents
As you probably already know, MongoDB databases have collections instead of tables. And each collection has documents instead of rows.
You can define your documents by inheriting from mongox.Model.
import asyncio
import mongox
client = mongox.Client("mongodb://localhost:27017")
db = client.get_database("test_db")
class Movie(mongox.Model, db=db):
name: str
year: int
First we create a mongox.Client instance with the MongoDB URI.
Then we get a database from client by calling .get_database().
If you want to have more control over the client event loop, you can specify a callable to get event loop at runtime:
client = mongox.Client(
"mongodb://localhost:27017", get_event_loop=asyncio.get_running_loop
)
By default, mongox.Client will use asyncio.get_running_loop.
Model attributes are defined the same way as Pydantic. The Movie class
is both a mongox Model and also a pydantic BaseModel.
Now we have a Movie collection with attributes name and year.
Field validation
You can add field-level validations by using mongox.Field.
This is actually just a short-cut to the Pydantic Field and accepts the same arguments.
Let's say we want to limit the year attribute of Movie to be more strict:
import mongox
class Movie(mongox.Model):
name: str
year: int = mongox.Field(gt=1800)
Now when creating a Movie instance, the year will be validated differently.
This will be ok:
await Movie(name="Forrest Gump", year=1994).insert()
But this will throw a Pydantic ValidationError:
await Movie(name="Golden Oldie", year=1790).insert()
# E pydantic.error_wrappers.ValidationError: 1 validation error for Movie
# E year
# E ensure this value is greater than 1800 (type=value_error.number.not_gt; limit_value=1800)
Some of the most common Field arguments include:
For numeric types like int, float and Decimal:
gtRquires the field to be greater thangeRquires the field to be greater than or equal toleRquires the field to be less than or equal toltRquires the field to be less thanmultiple_ofRequires the field to be a multiple of
For strings:
min_lengthRequires the field to have a minimum lengthmax_lengthRequires the field to have a maximum lengthregexRequires the field to match a regular expression
For a full list of Field arguments you can refer to
the Pydantic docs here.
Defining indexes
Mongox models accept indexes as a list of mongox.Index instances:
import mongox
indexes = [
mongox.Index("name", unique=True),
mongox.Index(keys=[("year", mongox.Order.DESCENDING), ("genre", mongox.IndexType.HASHED)]),
]
class Movie(mongox.Model, db=db, indexes=indexes):
name: str
genre: str
year: int
For creating Index objects we have two options, for simple cases we can do:
Index(key_name, **kwargs)
But to have more control over index definition we can do:
Index(keys=[(key_name, index_order)], **kwargs)
And then, you can then create the collection indexes with:
await Movie.create_indexes()
Or to drop the indexes:
await Movie.drop_indexes()
Note that this will only drop indexes defined in Movie model and
won't affect the ones manually created.
To drop all indexes, even those not defined here you can pass force=True:
await Movie.drop_indexes(force=True)
And finally if you need to drop a single index by name:
await Movie.drop_index("year_genre)
Index accepts the following arguments:
-
keyFor single key (simple indexes). -
keysList of keys and their types. -
nameCan be specified or automatically generated from keys. -
backgroundIf the index creation should happen in the background. -
uniqueIf the index should be a unique index.
For example:
Index(keys=[("year", Order.DESCENDING)], name="year_index", background=True)
To specify order of index you can use mongox.Order:
from mongox import Order
Index(keys=[("year", Order.ASCENDING)], name="year_index", background=True)
Order class has only two attributes ASCENDING and DESCENDING.
And to specify custom index type, you can pass mongox.IndexType instead of index order:
from mongox import IndexType
Index(keys=[("year", IndexType.HASHED)], name="year_index", background=True)
IndexType has the supported PyMongo index types:
-
GEO2D -
GEOSPHERE -
HASHED -
TEXT
For a full list of allowed arguments you can refer to the PyMongo docs here.
Embedded Models
Embedded Models are models to be embedded in mongox.Model.
The difference is that EmbeddedModels won't be inserted separately
and won't get their seaparate _id.
To define Embedded Models you should inherit from mongox.EmbeddedModel
and define the fields the same as Pydantic.
import mongox
class Genre(mongox.EmbeddedModel):
title: str
class Movie(mongox.Model):
name: str
genre: Genre
You can use EmbeddedModel to define multi-level nested objects in MongoDB.