-
-
Notifications
You must be signed in to change notification settings - Fork 844
Expand file tree
/
Copy pathtest_model_fields_optional.py
More file actions
241 lines (174 loc) · 6.97 KB
/
test_model_fields_optional.py
File metadata and controls
241 lines (174 loc) · 6.97 KB
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
import pytest
from pydantic import ValidationError
from sqlmodel import Field, SQLModel
from sqlmodel._compat import SQLModelConfig
def test_model_fields_optional_basic(clear_sqlmodel):
"""Test that model_fields_optional='all' makes all inherited fields Optional
with a default of None."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
# All fields should be optional (not required)
for field_info in HeroUpdate.model_fields.values():
assert not field_info.is_required()
# Should be able to create with no arguments
hero = HeroUpdate()
assert hero.name is None
assert hero.secret_name is None
assert hero.age is None
def test_model_fields_optional_partial_data(clear_sqlmodel):
"""Test creating an instance with only some fields set."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
hero = HeroUpdate(name="Spider-Man")
assert hero.name == "Spider-Man"
assert hero.secret_name is None
assert hero.age is None
def test_model_fields_optional_exclude_unset(clear_sqlmodel):
"""Test that model_dump(exclude_unset=True) only includes explicitly set
fields."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
hero = HeroUpdate(name="Spider-Man")
dumped = hero.model_dump(exclude_unset=True)
assert dumped == {"name": "Spider-Man"}
def test_model_fields_optional_override_field(clear_sqlmodel):
"""Test that explicitly redefined fields in the child class are not
overridden by model_fields_optional."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
name: str # Keep name required
# name should still be required
assert HeroUpdate.model_fields["name"].is_required()
# Other fields should be optional
assert not HeroUpdate.model_fields["secret_name"].is_required()
assert not HeroUpdate.model_fields["age"].is_required()
with pytest.raises(ValidationError):
HeroUpdate() # name is required
hero = HeroUpdate(name="Batman")
assert hero.name == "Batman"
assert hero.secret_name is None
def test_model_fields_optional_preserves_constraints(clear_sqlmodel):
"""Test that field constraints (min_length, ge, etc.) are preserved when
making fields optional."""
class HeroBase(SQLModel):
name: str = Field(min_length=1)
age: int | None = Field(default=None, ge=0)
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
# None should be valid for all fields
hero = HeroUpdate(name=None, age=None)
assert hero.name is None
assert hero.age is None
# Non-None values should still be validated
with pytest.raises(ValidationError):
HeroUpdate(name="") # min_length=1 violated
with pytest.raises(ValidationError):
HeroUpdate(age=-1) # ge=0 violated
# Valid non-None values should work
hero = HeroUpdate(name="X", age=5)
assert hero.name == "X"
assert hero.age == 5
def test_model_fields_optional_multiple_inheritance(clear_sqlmodel):
"""Test model_fields_optional with multiple levels of inheritance."""
class PersonBase(SQLModel):
first_name: str
last_name: str
class EmployeeBase(PersonBase):
employee_id: int
department: str
class EmployeeUpdate(EmployeeBase, model_fields_optional="all"):
pass
# All fields from all base classes should be optional
for field_info in EmployeeUpdate.model_fields.values():
assert not field_info.is_required()
employee = EmployeeUpdate(department="Engineering")
assert employee.department == "Engineering"
assert employee.first_name is None
assert employee.last_name is None
assert employee.employee_id is None
def test_model_fields_optional_via_model_config(clear_sqlmodel):
"""Test model_fields_optional via model_config dict."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase):
model_config = SQLModelConfig(model_fields_optional="all")
# All fields should be optional
for field_info in HeroUpdate.model_fields.values():
assert not field_info.is_required()
hero = HeroUpdate()
assert hero.name is None
assert hero.secret_name is None
assert hero.age is None
def test_model_fields_optional_with_table_base(clear_sqlmodel):
"""Test that model_fields_optional works alongside table models."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class Hero(HeroBase, table=True):
id: int | None = Field(default=None, primary_key=True)
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
# Table model should still work normally
hero = Hero(name="Batman", secret_name="Bruce Wayne")
assert hero.name == "Batman"
# Update model should have all optional fields
update = HeroUpdate(name="Dark Knight")
assert update.name == "Dark Knight"
assert update.secret_name is None
def test_model_fields_optional_already_optional_fields(clear_sqlmodel):
"""Test that already-optional fields remain optional and keep their
defaults."""
class HeroBase(SQLModel):
name: str
nickname: str | None = "Unknown"
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
hero = HeroUpdate()
# name was required, should now be None
assert hero.name is None
# nickname had a default of "Unknown", should keep it
assert hero.nickname == "Unknown"
# age had a default of None, should stay None
assert hero.age is None
def test_model_fields_optional_model_validate(clear_sqlmodel):
"""Test that model_validate works correctly with model_fields_optional."""
class HeroBase(SQLModel):
name: str
secret_name: str
age: int | None = None
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
hero = HeroUpdate.model_validate({"name": "Spider-Man"})
assert hero.name == "Spider-Man"
assert hero.secret_name is None
hero2 = HeroUpdate.model_validate({})
assert hero2.name is None
def test_model_fields_optional_json_schema(clear_sqlmodel):
"""Test that JSON schema reflects optional fields."""
class HeroBase(SQLModel):
name: str
secret_name: str
class HeroUpdate(HeroBase, model_fields_optional="all"):
pass
schema = HeroUpdate.model_json_schema()
# No fields should be required in the schema
assert "required" not in schema or len(schema.get("required", [])) == 0