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
Pyright fails to recognize valid calls when ParamSpec, Concatenate, and factories are used #3559
Comments
This is an incorrect usage of ParamSpec. The problem is If your intent is to support any callable whose first parameter is an def make_n_companies() -> BulkFactory[...]:
... The |
This doesn't actually work at all though. If I make this change I get: ± python3.10 example.py
Traceback (most recent call last):
File "exmaple.py", line 32, in <module>
def make_n_companies() -> BulkFactory[...]:
File ".../Versions/3.10/lib/python3.10/typing.py", line 312, in inner
return func(*args, **kwds)
File ".../Versions/3.10/lib/python3.10/typing.py", line 1075, in __getitem__
arg = arg[subargs]
File ".../Versions/3.10/lib/python3.10/typing.py", line 312, in inner
return func(*args, **kwds)
File ".../Versions/3.10/lib/python3.10/typing.py", line 1081, in __getitem__
return self.copy_with(tuple(new_args))
File ".../Versions/3.10/lib/python3.10/typing.py", line 1294, in copy_with
raise TypeError("The last parameter to Concatenate should be a "
TypeError: The last parameter to Concatenate should be a ParamSpec variable. |
Hmm, this looks like a bug in the runtime handling of If I understand the problem you're trying to solve here, a ParamSpec isn't the right approach in the first place. A callback protocol would be much more appropriate. class BulkFactory(Protocol):
def __call__(
self, count: int, /, id: int, *args: Any, **kwargs: Any
) -> List[Company]:
... |
The runtime exception appears to be a bug in the |
@erictraut I tried using the Example Codefrom __future__ import annotations
from typing import Any, Callable, Generic, List, Optional, Protocol, TypeVar
from typing_extensions import ParamSpec, TypeAlias
def i_default() -> int:
return 4
def s_default() -> str:
return "foo"
class Company:
id: int
def __init__(
self,
id: Optional[int] = None,
# ... dozens more optional properties go here
):
self.id = id or i_default() # each property has its own default
print(self)
def __repr__(self) -> str:
return f"<company {self.id}>"
class User:
id: int
def __init__(
self,
uuid: Optional[str] = None,
# ... dozens more optional properties go here
):
self.uuid = uuid or s_default() # each property has its own default
print(self)
def __repr__(self) -> str:
return f"<user {self.uuid}>"
def make_company(*args: Any, **kwargs: Any) -> Company:
return Company(*args, **kwargs)
def make_user(*args: Any, **kwargs: Any) -> User:
return User(*args, **kwargs)
T = TypeVar("T")
P = ParamSpec("P")
class BulkFactory(Protocol, Generic[T]):
def __call__(
self,
n: int,
/,
*args: Any,
**kwargs: Any,
) -> List[T]:
...
def make_n(maker: Callable[P, T]) -> BulkFactory[T]:
def inner(n: int, /, *args: P.args, **kwargs: P.kwargs) -> List[T]:
return [maker(*args, **kwargs) for _ in range(n)]
return inner
# create our bulk factories for user and company:
make_n_users = make_n(make_user)
make_n_companies = make_n(make_company)
# create a bunch of users and companies:
make_n_users(5) # this should work, giving us all default values
make_n_users(5, uuid="whatever") # this should work, giving us some
# default values while others are set
make_n_companies(5)
make_n_companies(5, id=5) Pylance IssueOn the line
EDIT: T = TypeVar("T")
P = ParamSpec("P")
class BulkFactory(Protocol, Generic[T, P]):
def __call__(
self,
n: int,
/,
*args: P.args,
**kwargs: P.kwargs,
) -> List[T]:
...
def make_n(maker: Callable[P, T]) -> BulkFactory[T, P]:
def inner(n: int, /, *args: P.args, **kwargs: P.kwargs) -> List[T]:
return [maker(*args, **kwargs) for _ in range(n)]
return inner Thank you! |
Yeah, that looks like a bug. Could you please open a new issue in the pyright issue tracker so this doesn't get lost? Thanks! |
Oh it does? I figured I was just doing something wrong 😅 -- I'll open an issue now. |
Describe the bug
Contrived code but in the real version this makes sense as we're working with
pytest
and using the factories-as-fixtures pattern.See the inline comments on the three calls to
bulk_maker()
for thepyright
errors:The first two calls to
bulk_maker()
are fine. The last one is incorrect and will fail at runtime:Expected behavior
There should not be any
pyright
errors for the first two calls tobulk_maker()
.VS Code extension or command-line
The text was updated successfully, but these errors were encountered: