Bug: `pydantic_model_creator` incorrectly marks fields as `Optional` when default is not `None`
Hi! I believe I’ve found a small issue in the pydantic_model_creator helper function.
Describe the bug Source: https://github.com/tortoise/tortoise-orm/blob/4b4e4a5a016cf327d795e1097d907526aa9d74f7/tortoise/contrib/pydantic/creator.py#L527-L530
In line 528, when generating field annotations, the logic currently considers a field optional if its default is anything other than None (field.default is not None). However, this can be misleading—for example, an integer field might have a default value of 0, but None is still not a valid input.
In such cases, the generated Pydantic model uses Optional[int], so the schema accepts None, but that later causes an ValueError at the ORM level bypassing schema validation at the HTTP level.
Let me know if this makes sense, or if there's another reason for this behavior I might have missed.
Stack versions:
- pydantic:
2.10.4 - tortoise-orm:
0.21.3(also reproduced in the latest version)
To Reproduce
class Altitude(models.Model):
altitude = fields.FloatField(default=0, null=False)
AltitudeSchema = pydantic_model_creator(
Altitude,
name="AltitudeSchema",
)
# Generated field annotation
$ AltitudeSchema.__fields__["altitude"]
> FieldInfo(annotation=Union[float, NoneType], required=False, default=0, title='Altitude', json_schema_extra={})
Later, when using this schema with a None value, it will be accepted by the schema but will fail when inserting into the database:
payload = AltitudeSchema(id=1, altitude=None)
await Altitude.create(**payload.model_dump())
Traceback (most recent call last):
...
File "<string>", line 2, in <module>
File "packages/tortoise/models.py", line 697, in _set_kwargs
raise ValueError(f"{key} is non nullable field, but null was passed")
ValueError: altitude is non nullable field, but null was passed
Expected behavior
I expected the Pydantic schema generated by the pydantic_model_creator helper function to type as Optional only the fields explicitly passed to the optional parameter or those declared at the ORM level with null=True.
Additional context
If I'm not missing anything, I think this issue could be fixed by changing the current if block to:
if not field.pk and (
field_name in self._optional or field.default is None or field.null
):
Or even simpler:
if not field.pk and (
field_name in self._optional or field.null
)
The generated schema:
AltitudeSchema.__fields__["altitude"]
# FieldInfo(annotation=float, required=False, default=0, title='Altitude', json_schema_extra={})
AltitudeSchema(id=1, altitude=None)
# Traceback (most recent call last):
# File "<string>", line 1, in <module>
# /pydantic/main.py", line 214, in __init__
# validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# pydantic_core._pydantic_core.ValidationError: 1 validation error for AltitudeSchema
# altitude
# Input should be a valid number [type=float_type, input_value=None, input_type=NoneType]
# For further information visit https://errors.pydantic.dev/2.10/v/float_type
+1
From my perspective, a field should be considered optional (accepting None values) only if
- it is explicitly marked as
self._optional - or if it is nullable
(field.null)
Whether the field has a default value should not influence whether None is accepted as a valid value. Or at least it should ensure that has a default AND the field can be nullable:
if not field.pk and (
field_name in self._optional or field.null or (field.default is not None and field.null)
)
Which can be finally simplified as
if not field.pk and (
field_name in self._optional or field.null
)
What do you think @henadzit @abondar @waketzheng @long2ice