Skip to content

Commit 3fc4ea0

Browse files
authored
fix: pydantic v2 pydantic_model_creator nullable field not optional. (#1454) (#1465)
1 parent edde82e commit 3fc4ea0

File tree

9 files changed

+28
-9
lines changed

9 files changed

+28
-9
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Added
1616
- Add binary compression support for `UUIDField` in `MySQL`. (#1458)
1717
- Only `Model`, `Tortoise`, `BaseDBAsyncClient`, `__version__`, and `connections` are now exported from `tortoise`
1818

19+
Fixed
20+
^^^^^
21+
- Fix pydantic v2 pydantic_model_creator nullable field not optional. (#1454)
22+
1923
0.20.0
2024
------
2125
Added

examples/blacksheep/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import tortoise
12
from tortoise import fields, models
23
from tortoise.contrib.pydantic import pydantic_model_creator
34

5+
if tortoise.__version__ >= "0.20":
6+
raise RuntimeError("blacksheep not support pydantic V2, use tortoise-orm<0.20 instead!")
7+
48

59
class Users(models.Model):
610
id = fields.UUIDField(pk=True)

examples/fastapi/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def get_users():
2222

2323
@app.post("/users", response_model=User_Pydantic)
2424
async def create_user(user: UserIn_Pydantic):
25-
user_obj = await Users.create(**user.dict(exclude_unset=True))
25+
user_obj = await Users.create(**user.model_dump(exclude_unset=True))
2626
return await User_Pydantic.from_tortoise_orm(user_obj)
2727

2828

@@ -33,7 +33,7 @@ async def get_user(user_id: int):
3333

3434
@app.put("/user/{user_id}", response_model=User_Pydantic)
3535
async def update_user(user_id: int, user: UserIn_Pydantic):
36-
await Users.filter(id=user_id).update(**user.dict(exclude_unset=True))
36+
await Users.filter(id=user_id).update(**user.model_dump(exclude_unset=True))
3737
return await User_Pydantic.from_queryset_single(Users.get(id=user_id))
3838

3939

examples/pydantic/recursive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ async def run():
7575
await _2_1.gets_talked_to.add(_2_2, _1_1, loose)
7676

7777
p = await Employee_Pydantic.from_tortoise_orm(await Employee.get(name="Root"))
78-
print(p.json(indent=4))
78+
print(p.model_dump_json(indent=4))
7979

8080

8181
if __name__ == "__main__":

examples/pydantic/tutorial_1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def run():
4040
# As Python dict with Python objects (e.g. datetime)
4141
print(tourpy.model_dump())
4242
# As serialised JSON (e.g. datetime is ISO8601 string representation)
43-
print(tourpy.json(indent=4))
43+
print(tourpy.model_dump_json(indent=4))
4444

4545

4646
if __name__ == "__main__":

examples/pydantic/tutorial_2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def run():
4848
# Note that the root element is 'root' that contains the root element.
4949
print(tourpy.model_dump())
5050
# As serialised JSON (e.g. datetime is ISO8601 string representation)
51-
print(tourpy.json(indent=4))
51+
print(tourpy.model_dump_json(indent=4))
5252

5353

5454
if __name__ == "__main__":

examples/pydantic/tutorial_3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ async def run():
6868
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
6969

7070
# As serialised JSON
71-
print(tourpy.json(indent=4))
71+
print(tourpy.model_dump_json(indent=4))
7272

7373
# Serialise Event
7474
eventpy = await Event_Pydantic.from_tortoise_orm(event)
7575

7676
# As serialised JSON
77-
print(eventpy.json(indent=4))
77+
print(eventpy.model_dump_json(indent=4))
7878

7979

8080
if __name__ == "__main__":

examples/pydantic/tutorial_4.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ async def run():
9898
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
9999

100100
# As serialised JSON
101-
print(tourpy.json(indent=4))
101+
print(tourpy.model_dump_json(indent=4))
102102

103103

104104
if __name__ == "__main__":

tortoise/contrib/pydantic/creator.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from tortoise.contrib.pydantic.base import PydanticListModel, PydanticModel
1010
from tortoise.contrib.pydantic.utils import get_annotations
11-
from tortoise.fields import JSONField, relational
11+
from tortoise.fields import IntField, JSONField, TextField, relational
1212

1313
if TYPE_CHECKING: # pragma: nocoverage
1414
from tortoise.models import Model
@@ -348,11 +348,13 @@ def get_submodel(_model: "Type[Model]") -> Optional[Type[PydanticModel]]:
348348
return pmodel
349349

350350
# Foreign keys and OneToOne fields are embedded schemas
351+
is_to_one_relation = False
351352
if (
352353
field_type is relational.ForeignKeyFieldInstance
353354
or field_type is relational.OneToOneFieldInstance
354355
or field_type is relational.BackwardOneToOneRelation
355356
):
357+
is_to_one_relation = True
356358
model = get_submodel(fdesc["python_type"])
357359
if model:
358360
if fdesc.get("nullable"):
@@ -410,6 +412,15 @@ def get_submodel(_model: "Type[Model]") -> Optional[Type[PydanticModel]]:
410412
if field_default is not None and not callable(field_default):
411413
properties[fname] = (ftype, Field(default=field_default, **fconfig))
412414
else:
415+
if (j := fconfig.get("json_schema_extra")) and (
416+
(
417+
j.get("nullable")
418+
and not is_to_one_relation
419+
and field_type not in (IntField, TextField)
420+
)
421+
or (exclude_readonly and j.get("readOnly"))
422+
):
423+
fconfig["default_factory"] = lambda: None
413424
properties[fname] = (ftype, Field(**fconfig))
414425

415426
# Here we endure that the name is unique, but complete objects are still labeled verbatim

0 commit comments

Comments
 (0)