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
|
## Inheritance for multi-model use case
Beanie `Documents` support inheritance as any other Python classes. But there are additional features available if you mark the root model with the parameter `is_root = True` in the inner Settings class.
This behavior is similar to `UnionDoc`, but you don't need an additional entity.
Parent `Document` act like a "controller", that handles proper storing and fetches different `Document` types.
Also, parent `Document` can have some shared attributes which are propagated to all children.
All classes in the inheritance chain can be used as `Link` in foreign `Documents`.
Depending on the business logic, parent `Document` can be like an "abstract" class that is not used to store objects of its type (like in the example below), as well as can be a full-fledged entity, like its children.
### Defining models
To set the root model you have to set `is_root = True` in the inner Settings class. All the inherited documents (on any level) will be stored in the same collection.
```py hl_lines="20 20"
from typing import List, Optional
from pydantic import BaseModel
from pymongo import AsyncMongoClient
from beanie import Document, Link, init_beanie
class Vehicle(Document):
"""Inheritance scheme bellow"""
# Vehicle
# / | \
# / | \
# Bicycle Bike Car
# \
# \
# Bus
# shared attribute for all children
color: str
class Settings:
is_root = True
class Fuelled(BaseModel):
"""Just a mixin"""
fuel: Optional[str]
class Bicycle(Vehicle):
"""Derived from Vehicle, will use its collection"""
frame: int
wheels: int
class Bike(Vehicle, Fuelled):
...
class Car(Vehicle, Fuelled):
body: str
class Bus(Car, Fuelled):
"""Inheritance chain is Vehicle -> Car -> Bus, it is also stored in Vehicle collection"""
seats: int
class Owner(Document):
vehicles: Optional[List[Link[Vehicle]]]
```
### Inserts
Inserts work the same way as usual
```python
client = AsyncMongoClient()
await init_beanie(client.test_db, document_models=[Vehicle, Bicycle, Bike, Car, Bus, Owner])
bike_1 = await Bike(color='black', fuel='gasoline').insert()
car_1 = await Car(color='grey', body='sedan', fuel='gasoline').insert()
car_2 = await Car(color='white', body='crossover', fuel='diesel').insert()
bus_1 = await Bus(color='white', seats=80, body='bus', fuel='diesel').insert()
bus_2 = await Bus(color='yellow', seats=26, body='minibus', fuel='diesel').insert()
owner = await Owner(name='John', vehicles=[car_1, car_2, bus_1]).insert()
```
### Find operations
With parameter `with_children = True` the find query results will contain all the children classes' objects.
```python
# this query returns vehicles of all types that have white color, because `with_children` is True
white_vehicles = await Vehicle.find(Vehicle.color == 'white', with_children=True).to_list()
# [
# Bicycle(..., color='white', frame=54, wheels=29),
# Car(fuel='diesel', ..., color='white', body='crossover'),
# Bus(fuel='diesel', ..., color='white', body='bus', seats=80)
# ]
```
If the search is based on a child, the query returns this child type and all sub-children (with parameter `with_children=True`)
```python
cars_and_buses = await Car.find(Car.fuel == 'diesel', with_children=True).to_list()
# [
# Car(fuel='diesel', ..., color='white', body='crossover'),
# Bus(fuel='diesel', ..., color='white', body='bus', seats=80),
# Bus(fuel='diesel', ..., color='yellow', body='minibus', seats=26)
# ]
```
If you need to return objects of the specific class only, you can use this class for finding:
```python
# however it is possible to limit by Vehicle type
cars_only = await Car.find().to_list()
# [
# Car(fuel='gasoline', ..., color='grey', body='sedan'),
# Car(fuel='diesel', ..., color='white', body='crossover')
# ]
```
To get a single Document it is not necessary to know the type. You can query using the parent class
```python
await Vehicle.get(bus_2.id, with_children=True)
# returns Bus instance:
# Bus(fuel='diesel', ..., color='yellow', body='minibus', seats=26)
```
### Relations
Linked documents will be resolved into the respective classes
```python
owner = await Owner.get(owner.id, fetch_links=True)
print(owner.vehicles)
# [
# Car(fuel='diesel', ..., color='white', body='crossover'),
# Bus(fuel='diesel', ..., color='white', body='bus', seats=80),
# Car(fuel='gasoline', ..., color='grey', body='sedan')
# ]
```
The same result will be if the owner gets objects without fetching the links, and they will be fetched manually later
### Other
All other operations work the same way as for simple Documents
```python
await Bike.find().update({"$set": {Bike.color: 'yellow'}})
await Car.find_one(Car.body == 'sedan')
```
|