From 6cabfcfaf0ca1b9aa55df4c5fff1035ef6c7829e Mon Sep 17 00:00:00 2001 From: Matt Fulgo Date: Mon, 24 Oct 2022 14:40:19 -0400 Subject: [PATCH 1/2] Fix TypeError for GenericModel with Callable param Introduced in 1.10.2, a TypeError would be raised upon creation of a GenericModel class that used a Callable type parameter. This would happen when `typing.get_args` returned a list for the first type agruments in a Callable and pydantic would try to use the value as a dictionary key. To avoid the issue, we convert the list to a tuple before using it as a key. The possible approach of modifying pydantic's `get_args` function to return a tuple instead of a list didn't work out because the return values are used in more places, some of which expect the list and not a tuple. Fixes #4551 --- changes/4551-mfulgo.md | 1 + pydantic/generics.py | 6 +++++- tests/test_generics.py | 27 +++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changes/4551-mfulgo.md diff --git a/changes/4551-mfulgo.md b/changes/4551-mfulgo.md new file mode 100644 index 0000000000..ccd41c96e9 --- /dev/null +++ b/changes/4551-mfulgo.md @@ -0,0 +1 @@ +Fix GenericModel with Callable param raising a TypeError diff --git a/pydantic/generics.py b/pydantic/generics.py index a3f52bfee9..ece421e8ba 100644 --- a/pydantic/generics.py +++ b/pydantic/generics.py @@ -64,7 +64,11 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T """ def _cache_key(_params: Any) -> Tuple[Type[GenericModelT], Any, Tuple[Any, ...]]: - return cls, _params, get_args(_params) + args = get_args(_params) + # python returns a list for Callables, which is not hashable + if len(args) == 2 and isinstance(args[0], list): + args = (tuple(args[0]), args[1]) + return cls, _params, args cached = _generic_types_cache.get(_cache_key(params)) if cached is not None: diff --git a/tests/test_generics.py b/tests/test_generics.py index 39adc45f20..371272b1ca 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -7,6 +7,7 @@ ClassVar, Dict, Generic, + Iterable, List, Mapping, Optional, @@ -234,6 +235,32 @@ class Model(GenericModel, Generic[T]): assert len(_generic_types_cache) == cache_size + 2 +def test_cache_keys_are_hashable(): + cache_size = len(_generic_types_cache) + T = TypeVar('T') + C = Callable[[str, Dict[str, Any]], Iterable[str]] + + class MyGenericModel(GenericModel, Generic[T]): + t: T + + # Callable's first params get converted to a list, which is not hashable. + # Make sure we can handle that special case + Simple = MyGenericModel[Callable[[int], str]] + assert len(_generic_types_cache) == cache_size + 2 + # Nested Callables + MyGenericModel[Callable[[C], Iterable[str]]] + assert len(_generic_types_cache) == cache_size + 4 + MyGenericModel[Callable[[Simple], Iterable[int]]] + assert len(_generic_types_cache) == cache_size + 6 + MyGenericModel[Callable[[MyGenericModel[C]], Iterable[int]]] + assert len(_generic_types_cache) == cache_size + 10 + + class Model(BaseModel): + x: MyGenericModel[Callable[[C], Iterable[str]]] = Field(...) + + assert len(_generic_types_cache) == cache_size + 10 + + def test_generic_config(): data_type = TypeVar('data_type') From c24c5488cf1b95a367eb7d424cca27ab0e236e91 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Thu, 27 Oct 2022 16:19:00 +0100 Subject: [PATCH 2/2] change as markdown --- changes/4551-mfulgo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/4551-mfulgo.md b/changes/4551-mfulgo.md index ccd41c96e9..1d9871a41a 100644 --- a/changes/4551-mfulgo.md +++ b/changes/4551-mfulgo.md @@ -1 +1 @@ -Fix GenericModel with Callable param raising a TypeError +Fix `GenericModel` with `Callable` param raising a `TypeError`