-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
See #4482 (comment)
From @pepastach
We recently bumped Pydantic from 1.10.1 to 1.10.2 and our tests started failing. After some lengthy investigation I found this change to be the root cause. I'd like to ask you for your opinions.
We have a Pydantic model with a field such as
f: MyGenericModel[Callable[[Task, Dict[str, Any]], Iterable[Result]]] = Field(...)Just importing Python module with this code gives me:
cls = <class 'foo.MyGenericModel'> params = typing.Callable[[foo.Task, typing.Dict[str, typing.Any]], typing.Iterable[foo.Result]] def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]: """Instantiates a new class from a generic class `cls` and type variables `params`. :param params: Tuple of types the class . Given a generic class `Model` with 2 type variables and a concrete model `Model[str, int]`, the value `(str, int)` would be passed to `params`. :return: New model class inheriting from `cls` with instantiated types described by `params`. If no parameters are given, `cls` is returned as is. """ def _cache_key(_params: Any) -> Tuple[Type[GenericModelT], Any, Tuple[Any, ...]]: return cls, _params, get_args(_params) > cached = _generic_types_cache.get(_cache_key(params)) E TypeError: unhashable type: 'list'The problem seems to be that the function
GenericModel._cache_key()now callsget_args()which in turns calls Python'styping.get_args()-> and this function returns a tuple with a list in it. And that makes it unhashable.def get_args(tp): """Get type arguments with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. Examples:: get_args(Dict[str, int]) == (str, int) get_args(int) == () get_args(Union[int, Union[T, int], str][int]) == (int, str) get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) get_args(Callable[[], T][int]) == ([], int) """ if isinstance(tp, _GenericAlias) and not tp._special: res = tp.__args__ if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: res = (list(res[:-1]), res[-1]) # <======== this list is a problem return res return ()If I skip the callable typing and only define the field as
f: MyGenericModel[Callable] = Field(...)then it runs okay.
Thanks in advance!
Reply from @sveinugu
@pepastach Hmm... Seems we did not think of that. Surprised that typing uses lists and not tuples for Callable arguments, but there is probably a good reason for this (or it might be just that it would be too much a nuisance to switch between brackets and parentheses...). In any case, there might be other examples of unhashable return values from
get_params, so a simple solution could be to just catchTypeErrorand in that case default to the previous _cache_key generation.I am new as contributor to pydantic, but I assume it would be cleanest if you could create another issue for this.