File: 1.8.0.md

package info (click to toggle)
python-beanie 1.29.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 1,544 kB
  • sloc: python: 14,413; makefile: 7; sh: 6
file content (345 lines) | stat: -rw-r--r-- 8,517 bytes parent folder | download
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
I'm happy to introduce to you the new version of Beanie and a lot of new features, that come with it.

Here is the feature list:

- Relations
- Event-based actions
- Cache
- Revision

## Relations

This feature is perhaps the most anticipated of all. It took some time, but finally, it is here. Relations.

The document can contain links to other documents in their fields.

*Only top-level fields are fully supported for now.*

Direct link to the document:

```python
from beanie import Document, Link

class Door(Document):
    height: int = 2
    width: int = 1


class House(Document):
    name: str
    door: Link[Door]  # This is the link
```

List of the links:

```python
from typing import List

from beanie import Document, Link

class Window(Document):
    x: int = 10
    y: int = 10


class House(Document):
    name: str
    door: Link[Door]
    windows: List[Link[Window]]  # This is the list of the links
```

Other link patterns are not supported for now. If you need something more specific for your use-case, please leave an issue on the GitHub page - <https://github.com/roman-right/beanie>

### Write

The next write methods support relations:

- `insert(...)`
- `replace(...)`
- `save(...)`

To apply the writing method to the linked documents, you should set the respective `link_rule` parameter

```python
house.windows = [Window(x=100, y=100)]
house.name = "NEW NAME"

# The next call will insert a new window object 
# and replace the house instance with updated data
await house.save(link_rule=WriteRules.WRITE)

# `insert` and `replace` methods will work the same way
```

Or Beanie can ignore internal links with the `link_rule` parameter `WriteRules.DO_NOTHING`

```python
house.door.height = 3
house.name = "NEW NAME"

# The next call will just replace the house instance 
# with new data, but the linked door object will not be synced
await house.replace(link_rule=WriteRules.DO_NOTHING)

# `insert` and `save` methods will work the same way
```

### Fetch

#### Prefetch

You can fetch linked documents on the find query step, using the parameter `fetch_links`

```python
houses = await House.find(
    House.name == "test", 
    fetch_links=True
).to_list()
```

All the find methods supported:
- find
- find_one
- get

Beanie uses a single aggregation query under the hood to fetch all the linked documents. This operation is very effective.

#### On-demand fetch

If you don't use prefetching, linked documents will be presented as objects of the `Link` class. You can fetch them manually then.
To fetch all the linked documents you can use the `fetch_all_links` method

```python
await house.fetch_all_links()
```

It will fetch all the linked documents and replace `Link` objects with them.

Or you can fetch a single field:

```python
await house.fetch_link(House.door)
```

This will fetch the Door object and put it in the `door` field of the `house` object.

### Delete

Delete method works the same way as write operations, but it uses other rules:

To delete all the links on the document deletion you should use the `DeleteRules.DELETE_LINKS` value for the `link_rule` parameter

```python
await house.delete(link_rule=DeleteRules.DELETE_LINKS)
```

To keep linked documents you can use the `DO_NOTHING` rule

```python
await house.delete(link_rule=DeleteRules.DO_NOTHING)
```

## Event-based actions

You can register methods as pre- or post- actions for document events like `insert`, `replace` and etc.

Currently supported events:

- Insert
- Replace
- SaveChanges
- ValidateOnSave

To register an action you can use `@before_event` and `@after_event` decorators respectively.

```python
from beanie import Insert, Replace

class Sample(Document):
    num: int
    name: str

    @before_event(Insert)
    def capitalize_name(self):
        self.name = self.name.capitalize()

    @after_event(Replace)
    def num_change(self):
        self.num -= 1
```

It is possible to register action for a list of events:

```python
from beanie import Insert, Replace

class Sample(Document):
    num: int
    name: str

    @before_event([Insert, Replace])
    def capitalize_name(self):
        self.name = self.name.capitalize()
```

This will capitalize the `name` field value before each document insert and replace

And sync and async methods could work as actions.

```python
from beanie import Insert, Replace

class Sample(Document):
    num: int
    name: str

    @after_event([Insert, Replace])
    async def send_callback(self):
        await client.send(self.id)
```

## Cache

All the query results could be locally cached.

This feature must be turned on in the `Settings` inner class explicitly.

```python
class Sample(Document):
    num: int
    name: str

    class Settings:
        use_cache = True
```

Beanie uses LRU cache with expiration time. You can set `capacity` (the maximum number of the cached queries) and expiration time in the `Settings` inner class.

```python
class Sample(Document):
    num: int
    name: str

    class Settings:
        use_cache = True
        cache_expiration_time = datetime.timedelta(seconds=10)
        cache_capacity = 5
```

Any query will be cached for this document class.

```python
# on the first call it will go to the database
samples = await Sample.find(num>10).to_list()

# on the second - it will use cache instead
samples = await Sample.find(num>10).to_list()

await asyncio.sleep(15)

# if the expiration time was reached 
# it will go to the database again
samples = await Sample.find(num>10).to_list()
```

## Revision

This feature helps with concurrent operations. 

It stores `revision_id` together with the document and changes it on each document update. If the application with the old local copy of the document will try to change it, an exception will be raised. Only when the local copy will be synced with the database, the application will be allowed to change the data. It helps to avoid losses of data.

This feature must be turned on in the `Settings` inner class explicitly too.

```python
class Sample(Document):
    num: int
    name: str

    class Settings:
        use_revision = True
```

Any changing operation will check if the local copy of the document has the actual `revision_id` value:

```python
s = await Sample.find_one(Sample.name="TestName")
s.num = 10

# If a concurrent process already changed the doc, 
# the next operation will raise an error
await s.replace()
```

If you want to ignore revision and apply all the changes even if the local copy is outdated, you can use the parameter `ignore_revision`

```python
await s.replace(ignore_revision=True)
```

## Other

There is a bunch of smaller features, presented in this release. I would like to mention a couple of them here.

### Save changes

Beanie can keep the document state, that synced with the database, to find local changes and save only them.

This feature must be turned on in the `Settings` inner class explicitly.

```python
class Sample(Document):
    num: int
    name: str

    class Settings:
        use_state_management = True
```

To save only changed values the `save_changes()` method should be used.

```python
s = await Sample.find_one(Sample.name == "Test")
s.num = 100
await s.save_changes()
```

The `save_changes()` method can be used only with already inserted documents.

### On save validation

Pydantic has very useful config to validate values on assignment - `validate_assignment = True`. But unfortunately, this is a heavy operation and doesn't fit some use cases.

You can validate all the values before saving the document (insert, replace, save, save_changes) with beanie config `validate_on_save` instead.

This feature must be turned on in the `Settings` inner class explicitly.

```python
class Sample(Document):
    num: int
    name: str

    class Settings:
        validate_on_save = True
```

If any field has a wrong value, it will raise an error on write operations (insert, replace, save, save_changes)

```python
sample = await Sample.find_one(Sample.name == "Test")
sample.num = "wrong value type"

# Next call will raise an error
await sample.replace()
```

## Conclusion

Thank you for reading. I hope you'll find these features useful.

If you would like to help with development - there are some issues at the GitHub page of the project - <https://github.com/roman-right/beanie>

## Links

- [Beanie Project](https://github.com/roman-right/beanie)
- [Documentation](https://roman-right.github.io/beanie/)
- [Discord Server](https://discord.gg/ZTTnM7rMaz)