New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Union Field: union_mode="smart" with default_factory and self reference ignores data that could be coerced #9101
Comments
To fix, one option is to specify class Points(BaseModel):
# This works
# points: Union[Point, "Points"] = Field(
# union_mode="smart",
# )
# This ignores the data
points: Union[Point, "Points"] = Field(
union_mode="left_to_right",
default_factory=lambda: Point(x=0, y=0),
) |
However, if you have a list of Unions, you can't use this trick from typing import Union
from pydantic import BaseModel, Field
class Point(BaseModel):
x: int
y: int
class Points(BaseModel):
# This works
# points: Union[Point, "Points"] = Field(
# union_mode="smart",
# )
# This ignores the data
points: list[Union[Point, "Points"]] = Field(
union_mode="left_to_right",
default_factory=lambda: Point(x=0, y=0),
)
points = Points(
points=[
{
"x": "1",
"y": "1",
}
],
)
print(points) # points=Points(points=Point(x=0, y=0))
points = Points(
points=[
{
"x": 1,
"y": 1,
}
],
)
print(points) # points=Point(x=1, y=1) which raises TypeError: The following constraints cannot be applied to list[typing.Union[__main__.Point, __main__.Points]]: 'union_mode' |
I found a way to achieve the desired behavior using a from typing import Annotated, Union
from pydantic import BaseModel, Discriminator, Field, Tag
class Point(BaseModel):
x: int
y: int
def points_discrimnator(v):
if isinstance(v, list):
return "Points"
return "Point"
class Points(BaseModel):
points: list[
Annotated[
Union[Annotated[Point, Tag("Point")], Annotated["Points", Tag("Points")]],
Discriminator(
points_discrimnator,
custom_error_type="invalid_union_member",
custom_error_message="Invalid union member",
custom_error_context={"discriminator": "str_or_model"},
),
]
] = Field(
default_factory=lambda: Point(x=0, y=0),
)
points = Points(
points=[
{
"x": "1",
"y": "1",
}
],
)
print(points) # points=Point(x=1, y=1)
points = Points(
points=[
{
"x": 1,
"y": 1,
}
],
)
print(points) # points=Point(x=1, y=1) But it seems really overkill, is there a better way to achieve this ? |
You can use Given that, I think this issue is resolved... |
@sydney-runkle thanks for the quick reply. Indeed this works class Points(BaseModel):
# This works
points: list[
Annotated[Union[Point, "Points"], Field(union_mode="left_to_right")]
] = Field(default_factory=list) |
I would argue that it's not quite resolved. There is a workaround though. Why ?
This caused major issues on our product FYI and was quite hard to track down... |
Ah yes you're right. I didn't understand the issue in its entirety! Thanks for following up. |
Initial Checks
Description
The
union_mode
defaultsmart
gets confused ifIn this situation, it ends up picking the default factory even if it should be capable of coercing the input.
NOTE: it seems that if you remove the default factory or don't use a self-referencing union, things work fine.
Example Code
Python, Pydantic & OS Version
Related
#2135
#2092
The text was updated successfully, but these errors were encountered: