File: defining-a-document.md

package info (click to toggle)
python-beanie 2.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,480 kB
  • sloc: python: 14,427; makefile: 7; sh: 6
file content (262 lines) | stat: -rw-r--r-- 6,478 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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.

```python
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](../api-documentation/fields.md/#pydanticobjectid).

```python
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:

```python
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:

```python
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:

```python
class Sample(Document):
    description: Indexed(str, index_type=pymongo.TEXT)
```

The `Indexed` function also supports pymongo `IndexModel` kwargs arguments ([PyMongo Documentation](https://pymongo.readthedocs.io/en/stable/api/pymongo/operations.html#pymongo.operations.IndexModel)). 
 
For example, to create a `unique` index:

```python
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.

```python
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](https://pymongo.readthedocs.io/en/stable/api/pymongo/operations.html#pymongo.operations.IndexModel)

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:
```python
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:
```python
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](/tutorial/relations/#nested-links).