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
First instantiation of generic and nested Union model defines type coercion of models instantiated later #4519
Comments
Thanks 👍. Obviously not ideal, but let us know if you think there's anything can be easily be fixed here, or if we just need to wait for v2? |
Some immediate thoughts, given my limited knowledge of the I believe one workaround could be to define a variant of Alternatively, you might want to reconsider the type coercion logic, however I don't have any ideas at this moment of good alternatives. I do not have enough overview of v2 to see if this would fix the issue in itself. As the |
Come to think of it now, the
However, the Python issue happens independently of the usage of |
An illustration on how the Python issue might affect from pydantic import BaseModel
from typing import List, Union
# import something_else
JSON_List = List[Union[int, float, bool, str]]
class JSON(BaseModel):
json_list: JSON_List
assert JSON(json_list=('abc', '1', '2.0', 'True')).json() == '{"json_list": ["abc", 1, 2.0, true]}' Here, the types are ordered at least relatively sensibly. But let's say the contents of from typing import List, Union
SomethingElse = List[Union[bool, int, float, str]] Then removing the comment on the import line in the first file makes the assertion fail ( |
We’re no longer actively developing Pydantic V1 (although it will continue to receive security fixes for the next year or so), so we’re closing issues solely related to V1. If you really think this shouldn’t be closed, please comment or create a new issue 🚀. |
@sydney-runkle I don't think this should be closed, as it is still relevant for Pydantic v2. Perhaps we should even open another issue, as there are really two issues at play here. The first issue is really a core Python issue that has consequences for pydantic. The problem is that type caching in the from typing import List, Optional, Union, get_args
print(get_args(List[Union[float, int]]))
print(get_args(List[Union[int, float]]))
print(get_args(List[float | int]))
print(get_args(List[int | float]))
print(get_args(Optional[Union[float, int]]))
print(get_args(Optional[Union[int, float]]))
print(get_args(Optional[float | int]))
print(get_args(Optional[int | float])) Prints:
However, newer syntax variations do not have this issue, e.g.: from typing import Union, get_args
print(get_args(list[Union[float, int]]))
print(get_args(list[Union[int, float]]))
print(get_args(list[float | int]))
print(get_args(list[int | float]))
print(get_args(float | int | None))
print(get_args(int | float | None)) Prints: (typing.Union[float, int],)
(typing.Union[int, float],)
(float | int,)
(int | float,)
(<class 'float'>, <class 'int'>, <class 'NoneType'>)
(<class 'int'>, <class 'float'>, <class 'NoneType'>) There are a couple of related threads in the cpython repo:
AFAIK, there is no issue in the from typing import List
from pydantic import BaseModel
class A(BaseModel):
list_of_numbers: List[int | float]
class B(BaseModel):
list_of_numbers: List[float | int]
print(A(list_of_numbers=['1']), B(list_of_numbers=['1'])) Prints: list_of_numbers=[1] list_of_numbers=[1] While moving B before A in the above code, and with no other syntax change, prints: list_of_numbers=[1.0] list_of_numbers=[1.0] The issue is only related to the order in which the Python process reads the code, also across files, potentially giving rise to very strange errors. Hence, I think the issue should be documented in the pydantic docs with a recommendation to make use of new type notation if allowed by the Python version ( |
The other issue is a pydantic one, which is still a bug in pydantic v2 (and even a regression). If we now focus on the new type notation (so that the broken behaviour in Python documented above in #4519 (comment) does not interfere) there are still issues with the pydantic caching of generic types. For example: from typing import Generic, TypeVar, List, Union
from pydantic import BaseModel
NumberT = TypeVar('NumberT')
class NumberModel(BaseModel, Generic[NumberT]):
data: NumberT
NumberModel[list[float | int]](data=['1']), NumberModel[list[int | float]](data=['1']) produces (interactively): (NumberModel[list[Union[float, int]]](data=[1.0]),
NumberModel[list[Union[float, int]]](data=[1.0])) While switching the order of the items in the last line produces (in a new process): (NumberModel[list[Union[int, float]]](data=[1]),
NumberModel[list[Union[int, float]]](data=[1]))
Even worse is that it seems there is a regression (still in v2) of the simpler variant of the bug that was fixed for pydantic v1 in #4474. For example: NumberModel[int | float](data='1'), NumberModel[float | int](data='1') produces: (NumberModel[Union[int, float]](data=1),
NumberModel[Union[int, float]](data=1)) While, strangely, NumberModel[Union[int, float]](data='1'), NumberModel[Union[float, int]](data='1') gives: (NumberModel[Union[int, float]](data=1),
NumberModel[Union[float, int]](data=1.0)) Both of these latter two variants worked in pydantic v1. |
I can also add that the issue IS less problematic in v2, as smart_union=True is now default behaviour. Still, the above failing code examples do not include any config changes, so the order of unions is still important also in a default setup. |
@sydney-runkle I did not receive a reply on my comments objecting to the closing of this issue. This just caused a bug in my code due to the fact that pydantic did not differentiate between two models like below: from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class MyModel(BaseModel, Generic[T]):
data: T
print(MyModel[tuple[str | bytes, str]])
print(MyModel[tuple[bytes | str, str]]) This prints:
which caused a hard-to-debug issue in my code. |
Initial Checks
Description
Reopening of nested variant of bug described in issue #4474, for now mostly as a reference point for other repos. The basic variant of the bug was fixed in version 1.10.2.
Example code:
Still prints:
While changing the order of the models prints:
See this #4474 (comment) for more info on the remaining issue, which is not trivial to fix and possibly depends on changes in the
typing
package.A skipped test documenting the issue was also added to the
pydantic
source code in version 1.10.2.Example Code
No response
Python, Pydantic & OS Version
Affected Components
.dict()
and.json()
construct()
, pickling, private attributes, ORM modeThe text was updated successfully, but these errors were encountered: