Skip to content
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

Use TypeGuard for has in Python 3.10 and above #997

Merged
merged 8 commits into from Aug 16, 2022
Merged
10 changes: 9 additions & 1 deletion src/attr/__init__.pyi
Expand Up @@ -470,7 +470,15 @@ def astuple(
tuple_factory: Type[Sequence[Any]] = ...,
retain_collection_types: bool = ...,
) -> Tuple[Any, ...]: ...
def has(cls: type) -> bool: ...

if sys.version_info >= (3, 10):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be feasible to add a conditional import from typing-extensions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can only use one of a small number of constants like TYPE_CHECKING and sys.version_info to define types conditionally, try/except won't work, so I don't think so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my very limited testing this seems to work, both in Mypy and Pyright:

Screenshot 2022-08-15 at 13 37 05

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but typing_extensions is not a dependency of attrs, so that'll error if typing_extensions is not installed.

Copy link
Contributor Author

@layday layday Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, it seems typeshed ships typing_extensions as part of the stdlib, so I guess we can actually do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in fd2c819.

from typing import TypeGuard

def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ...

else:
def has(cls: type) -> bool: ...

def assoc(inst: _T, **changes: Any) -> _T: ...
def evolve(inst: _T, **changes: Any) -> _T: ...

Expand Down
12 changes: 12 additions & 0 deletions tests/test_mypy.yml
Expand Up @@ -1371,3 +1371,15 @@
b: str

fields(A) # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected "Type[AttrsInstance]"

- case: testHasTypeGuard
skip: sys.version_info < (3, 10)
main: |
from attrs import define, has

@define
class A:
pass

if has(A):
reveal_type(A) # N: Revealed type is "Type[attr.AttrsInstance]"