File: pydantic-fields.md

package info (click to toggle)
ormar 0.22.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,952 kB
  • sloc: python: 24,085; makefile: 34; sh: 14
file content (185 lines) | stat: -rw-r--r-- 5,083 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
# Pydantic only fields

Ormar allows you to declare normal `pydantic` fields in its model, so you have access to
all basic and custom pydantic fields like `str`, `int`, `HttpUrl`, `PaymentCardNumber` etc.

You can even declare fields leading to nested pydantic only Models, not only single fields.

Since those fields are not stored in database (that's the whole point of those fields),
you have to provide a meaningful value for them, either by setting a default one or 
providing one during model initialization.

If `ormar` cannot resolve the value for pydantic field it will fail during loading data from the database,
with missing required value for declared pydantic field.

Options to provide a value are described below.

Of course you can combine few or all of them in one model.

## Optional field

If you set a field as `Optional`, it defaults to `None` if not provided and that's 
exactly what's going to happen during loading from database.

```python
base_ormar_config = ormar.OrmarConfig(
    metadata=sqlalchemy.MetaData(),
    database=DatabaseConnection(DATABASE_URL),
)


class ModelTest(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=200)
    number: Optional[PaymentCardNumber]

test = ModelTest(name="Test")
assert test.name == "Test"
assert test.number is None
test.number = "123456789015"

await test.save()
test_check = await ModelTest.objects.get()

assert test_check.name == "Test"
# after load it's back to None
assert test_check.number is None
```

## Field with default value

By setting a default value, this value will be set on initialization and database load. 
Note that setting a default to `None` is the same as setting the field to `Optional`.

```python
base_ormar_config = ormar.OrmarConfig(
    metadata=sqlalchemy.MetaData(),
    database=DatabaseConnection(DATABASE_URL),
)


class ModelTest(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=200)
    url: HttpUrl = "https://www.example.com"

test = ModelTest(name="Test")
assert test.name == "Test"
assert test.url == "https://www.example.com"

test.url = "https://www.sdta.ada.pt"
assert test.url == "https://www.sdta.ada.pt"

await test.save()
test_check = await ModelTest.objects.get()

assert test_check.name == "Test"
# after load it's back to default
assert test_check.url == "https://www.example.com"
```

## Default factory function

By setting a `default_factory` function, this result of the function call will be set 
on initialization and each database load.

```python
from pydantic import Field, PaymentCardNumber
# ...


base_ormar_config = ormar.OrmarConfig(
    metadata=sqlalchemy.MetaData(),
    database=DatabaseConnection(DATABASE_URL),
)


CARD_NUMBERS = [
    "123456789007",
    "123456789015",
    "123456789023",
    "123456789031",
    "123456789049",
]


def get_number():
    return random.choice(CARD_NUMBERS)


class ModelTest2(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=200)
    # note that you do not call the function, just pass reference
    number: PaymentCardNumber = Field(default_factory=get_number)

# note that you still CAN provide a value 
test = ModelTest2(name="Test2", number="4000000000000002")
assert test.name == "Test2"
assert test.number == "4000000000000002"

await test.save()
test_check = await ModelTest2.objects.get()

assert test_check.name == "Test2"
# after load value is set to be one of the CARD_NUMBERS
assert test_check.number in CARD_NUMBERS
assert test_check.number != test.number
```

## Custom setup in `__init__`

You can provide a value for the field in your `__init__()` method before calling a `super()` init method.

```python
from pydantic import BaseModel
# ...


base_ormar_config = ormar.OrmarConfig(
    metadata=sqlalchemy.MetaData(),
    database=DatabaseConnection(DATABASE_URL),
)


class PydanticTest(BaseModel):
    aa: str
    bb: int


class ModelTest3(ormar.Model):
    ormar_config = base_ormar_config.copy()

    # provide your custom init function
    def __init__(self, **kwargs):
        # add value for required field without default value
        kwargs["pydantic_test"] = PydanticTest(aa="random", bb=42)
        # remember to call ormar.Model init!
        super().__init__(**kwargs)

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=200)
    pydantic_test: PydanticTest

test = ModelTest3(name="Test3")
assert test.name == "Test3"
assert test.pydantic_test.bb == 42
test.pydantic.aa = "new value"
assert test.pydantic.aa == "new value"

await test.save()
test_check = await ModelTest3.objects.get()

assert test_check.name == "Test3"
# after load it's back to value provided in init
assert test_check.pydantic_test.aa == "random"
```

!!!warning
    If you do not provide a value in one of the above ways `ValidationError` will be raised on load from database.