Use TypeGuard for has in Python 3.10 and above#997
Conversation
|
I don't see a direct use-case for this (since attrs classes should be statically known now) but I don't see the harm either. |
|
So what exactly does this do? |
|
It essentially makes Let's say your function takes a class (not an instance, but an actual class). You will type your argument as If we annotate Code example: def needs_attrs_class(cls: type[AttrsInstance]) -> None:
pass
def my_function(cls: type) -> None:
if has(cls):
needs_attrs_class(cls) # Mypy's gonna be unhappy, unless it knows `has` narrows the type of is argument |
|
It tells the type checker that if the condition is true then def is_dict_no_typeguard(value: object) -> bool:
return isinstance(value, dict)
def is_dict_typeguard(value: object) -> TypeGuard[dict]:
return isinstance(value, dict)
might_be_a_dict = ...
if is_dict_no_typeguard(might_be_a_dict):
"type of might_be_a_dict is object"
if is_dict_typeguard(might_be_a_dict):
"type of might_be_a_dict is dict"
|
|
Sounds cool! Any reason against it? If not: make it happen Tin. ;) |
src/attr/__init__.pyi
Outdated
| ) -> Tuple[Any, ...]: ... | ||
| def has(cls: type) -> bool: ... | ||
|
|
||
| if sys.version_info >= (3, 10): |
There was a problem hiding this comment.
Would it be feasible to add a conditional import from typing-extensions?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Right, but typing_extensions is not a dependency of attrs, so that'll error if typing_extensions is not installed.
There was a problem hiding this comment.
Okay, it seems typeshed ships typing_extensions as part of the stdlib, so I guess we can actually do that.
|
Would you mind coming up with a way to verify this works in https://github.com/python-attrs/attrs/blob/main/tests/typing_example.py ? |
|
There's a test in |
|
No, both would be great, since they work somewhat differently. Maybe |
|
Just so I understand this correctly: the import from typing_extensions only works, because we have the stubs in pyi files that are only touched by mypy which in turn brings in typing_extensions? IOW: if we'd use inline type hints, this would fall apart? |
|
That's right. The typeshed has import typing
if typing.TYPE_CHECKING:
from typing_extensions import TypeGuard
# Use TypeGuard either in quotes or with `from __future__ import annotations` |
|
Thanks! |
Since python-attrs/attrs#890 (≥ 22.1.0) `attrs.fields` is typed to accept a protocol. Since python-attrs/attrs#997 (≥ 22.2.0) `attrs.has` is a type-guard. Support both by removing the explicit error reporting and letting it fall through to the type stub. Fixes #15980.
Since python-attrs/attrs#890 (≥ 22.1.0) `attrs.fields` is typed to accept a protocol. Since python-attrs/attrs#997 (≥ 22.2.0) `attrs.has` is a type-guard. Support both by removing the explicit error reporting and letting it fall through to the type stub. Fixes #15980.

Summary
Conditionally defines
hasusingTypeGuardin Python 3.10+ in the stub file.xref: #987
Pull Request Check List
Our CI fails if coverage is not 100%.
.pyi).tests/typing_example.py.attr/__init__.pyi, they've also been re-imported inattrs/__init__.pyi.docs/api.rstby hand.@attr.s()have to be added by hand too.versionadded,versionchanged, ordeprecateddirectives.Find the appropriate next version in our
__init__.pyfile..rstfiles is written using semantic newlines.changelog.d.