Defining a document
The Document
class in Beanie is responsible for mapping and handling the data
from the collection. It is inherited from the BaseModel
Pydantic class, so it
follows the same data typing and parsing behavior.
from typing import Optional
import pymongo
from pydantic import BaseModel
from beanie import Document, Indexed
class Category(BaseModel):
name: str
description: str
class Product(Document): # This is the model
name: str
description: Optional[str] = None
price: Indexed(float, pymongo.DESCENDING)
category: Category
class Settings:
name = "products"
indexes = [
[
("name", pymongo.TEXT),
("description", pymongo.TEXT),
],
]
Fields
As it was mentioned before, the Document
class is inherited from the Pydantic BaseModel
class.
It uses all the same patterns of BaseModel
. But also it has special types of fields:
- id
- Indexed
id
id
field of the Document
class reflects the unique _id
field of the MongoDB document.
Each object of the Document
type has this field.
The default type of this is PydanticObjectId.
class Sample(Document):
num: int
description: str
foo = await Sample.find_one(Sample.num > 5)
print(foo.id) # This will print id
bar = await Sample.get(foo.id) # get by id
If you prefer another type, you can set it up too. For example, UUID:
from uuid import UUID, uuid4
from pydantic import Field
class Sample(Document):
id: UUID = Field(default_factory=uuid4)
num: int
description: str
Indexed
To set up an index over a single field, the Indexed
function can be used to wrap the type:
from beanie import Indexed
class Sample(Document):
num: Indexed(int)
description: str
The Indexed
function takes an optional argument index_type
, which may be set to a pymongo index type:
class Sample(Document):
description: Indexed(str, index_type=pymongo.TEXT)
The Indexed
function also supports pymongo IndexModel
kwargs arguments (PyMongo Documentation).
For example, to create a unique
index:
class Sample(Document):
name: Indexed(str, unique=True)
Settings
The inner class Settings
is used to configure:
- MongoDB collection name
- Indexes
- Encoders
- Use of
revision_id
- Use of cache
- Use of state management
- Validation on save
- Configure if nulls should be saved to the database
- Configure nesting depth for linked documents on the fetch operation
Collection name
To set MongoDB collection name, you can use the name
field of the Settings
inner class.
class Sample(Document):
num: int
description: str
class Settings:
name = "samples"
Indexes
The indexes
field of the inner Settings
class is responsible for the indexes' setup.
It is a list where items can be:
- Single key. Name of the document's field (this is equivalent to using the Indexed function described above)
- List of (key, direction) pairs. Key - string, name of the document's field. Direction - pymongo direction (
example:
pymongo.ASCENDING
) pymongo.IndexModel
instance - the most flexible option. PyMongo Documentation
class DocumentTestModelWithIndex(Document):
test_int: int
test_list: List[SubDocument]
test_str: str
class Settings:
indexes = [
"test_int",
[
("test_int", pymongo.ASCENDING),
("test_str", pymongo.DESCENDING),
],
IndexModel(
[("test_str", pymongo.DESCENDING)],
name="test_string_index_DESCENDING",
),
]
Encoders
The bson_encoders
field of the inner Settings
class defines how the Python types are going to be represented
when saved in the database. The default conversions can be overridden with this.
The ip
field in the following example is converted to String by default:
from ipaddress import IPv4Address
class Sample(Document):
ip: IPv4Address
Note: Default conversions are defined in
beanie.odm.utils.bson.ENCODERS_BY_TYPE
.
However, if you want the ip
field to be represented as Integer in the database,
you need to override the default encoders like this:
from ipaddress import IPv4Address
class Sample(Document):
ip: IPv4Address
class Settings:
bson_encoders = {
IPv4Address: int
}
You can also define your own function for the encoding:
from ipaddress import IPv4Address
def ipv4address_to_int(v: IPv4Address):
return int(v)
class Sample(Document):
ip: IPv4Address
class Settings:
bson_encoders = {
IPv4Address: ipv4address_to_int
}
Keep nulls
By default, Beanie saves fields with None
value as null
in the database.
But if you don't want to save null
values, you can set keep_nulls
to False
in the Settings
class:
class Sample(Document):
num: int
description: Optional[str] = None
class Settings:
keep_nulls = False
Nested Documents Depth
It is possible to define nested linked documents with Beanie. Sometimes this can lead to infinite recursion. To prevent this, or to decrease the database load, you can limit the maximum nesting depth. By default, it is set to 3, which means it will fetch up to 3 levels of nested documents.
You can configure:
- maximum depth for all linked documents
- depth for a specific linked document
Maximum:
class Sample(Document):
num: int
category: Link[Category]
class Settings:
max_nesting_depth = 2
# Maximum nesting depth for all linked documents of this model
Specific:
class Sample(Document):
num: int
category: Link[Category]
class Settings:
max_nesting_depths_per_field = {
"category": 1 # Nesting depth for a specific field
}
Also, you can limit the nesting depth during find operations. You can read more about this here.