From 4bb731398f2fb8c690c65ac18ba193be03c127fe Mon Sep 17 00:00:00 2001 From: David Montague <35119617+dmontagu@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:22:47 -0600 Subject: [PATCH 1/3] Fix issue with recursion error caused by ParamSpec --- pydantic/_internal/_generics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py index e8dc6988bc..876b5e7be5 100644 --- a/pydantic/_internal/_generics.py +++ b/pydantic/_internal/_generics.py @@ -359,7 +359,7 @@ def has_instance_in_type(type_: Any, isinstance_target: Any) -> bool: # Handle special case for typehints that can have lists as arguments. # `typing.Callable[[int, str], int]` is an example for this. - if isinstance(type_, (List, list)): + if isinstance(type_, (List, list)) and not isinstance(type_, typing_extensions.ParamSpec): if any(has_instance_in_type(element, isinstance_target) for element in type_): return True From 1338aba9b728c2680695ecac47924f6c379a3661 Mon Sep 17 00:00:00 2001 From: David Montague <35119617+dmontagu@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:26:19 -0600 Subject: [PATCH 2/3] Add test --- tests/test_generics.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_generics.py b/tests/test_generics.py index 1a761f5518..e80d3724a4 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -32,7 +32,7 @@ import pytest from dirty_equals import HasRepr, IsStr from pydantic_core import CoreSchema, core_schema -from typing_extensions import Annotated, Literal, OrderedDict, TypeVarTuple, Unpack, get_args +from typing_extensions import Annotated, Literal, OrderedDict, ParamSpec, TypeVarTuple, Unpack, get_args from pydantic import ( BaseModel, @@ -2534,3 +2534,19 @@ class Container(BaseModel, Generic[T]): assert Container[type(None)](value=None).value is None assert Container[None](value=None).value is None + + +def test_paramspec_is_usable(): + # This used to cause a recursion error due to `P in P is True` + # This test doesn't actually test that ParamSpec works properly for validation or anything. + + P = ParamSpec('P') + + class MyGenericParamSpecClass(Generic[P]): + def __init__(self, func: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: + super().__init__() + + class ParamSpecGenericModel(BaseModel, Generic[P]): + my_generic: MyGenericParamSpecClass[P] + + model_config = dict(arbitrary_types_allowed=True) From b2a20b85840bd8cb59592ce335926e6c5fe29ce7 Mon Sep 17 00:00:00 2001 From: David Montague <35119617+dmontagu@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:56:58 -0600 Subject: [PATCH 3/3] Ignore ParamSpec for PyPy --- tests/test_generics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_generics.py b/tests/test_generics.py index e80d3724a4..512b411cb1 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -2536,6 +2536,7 @@ class Container(BaseModel, Generic[T]): assert Container[None](value=None).value is None +@pytest.mark.skipif(platform.python_implementation() == 'PyPy', reason='PyPy does not allow ParamSpec in generics') def test_paramspec_is_usable(): # This used to cause a recursion error due to `P in P is True` # This test doesn't actually test that ParamSpec works properly for validation or anything.