Skip to content

Commit c4ded79

Browse files
Tinchehynek
andauthored
Mypy improvements (#890)
* Mypy improvements * Change protocol, add a test * Relax fields return type * Test fixes Co-authored-by: Hynek Schlawack <[email protected]>
1 parent fc827cf commit c4ded79

File tree

2 files changed

+47
-30
lines changed

2 files changed

+47
-30
lines changed

src/attr/__init__.pyi

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import sys
33
from typing import (
44
Any,
55
Callable,
6+
ClassVar,
67
Dict,
78
Generic,
89
List,
910
Mapping,
1011
Optional,
12+
Protocol,
1113
Sequence,
1214
Tuple,
1315
Type,
@@ -22,8 +24,8 @@ from . import exceptions as exceptions
2224
from . import filters as filters
2325
from . import setters as setters
2426
from . import validators as validators
25-
from ._version_info import VersionInfo
2627
from ._cmp import cmp_using as cmp_using
28+
from ._version_info import VersionInfo
2729

2830
__version__: str
2931
__version_info__: VersionInfo
@@ -57,6 +59,10 @@ _FieldTransformer = Callable[
5759
# _ValidatorType from working when passed in a list or tuple.
5860
_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
5961

62+
# A protocol to be able to statically accept an attrs class.
63+
class AttrsInstance(Protocol):
64+
__attrs_attrs__: ClassVar[Any]
65+
6066
# _make --
6167

6268
NOTHING: object
@@ -399,13 +405,9 @@ def define(
399405
mutable = define
400406
frozen = define # they differ only in their defaults
401407

402-
# TODO: add support for returning NamedTuple from the mypy plugin
403-
class _Fields(Tuple[Attribute[Any], ...]):
404-
def __getattr__(self, name: str) -> Attribute[Any]: ...
405-
406-
def fields(cls: type) -> _Fields: ...
407-
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
408-
def validate(inst: Any) -> None: ...
408+
def fields(cls: Type[AttrsInstance]) -> Any: ...
409+
def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ...
410+
def validate(inst: AttrsInstance) -> None: ...
409411
def resolve_types(
410412
cls: _C,
411413
globalns: Optional[Dict[str, Any]] = ...,
@@ -449,7 +451,7 @@ def make_class(
449451
# https://github.com/python/typing/issues/253
450452
# XXX: remember to fix attrs.asdict/astuple too!
451453
def asdict(
452-
inst: Any,
454+
inst: AttrsInstance,
453455
recurse: bool = ...,
454456
filter: Optional[_FilterType[Any]] = ...,
455457
dict_factory: Type[Mapping[Any, Any]] = ...,
@@ -462,7 +464,7 @@ def asdict(
462464

463465
# TODO: add support for returning NamedTuple from the mypy plugin
464466
def astuple(
465-
inst: Any,
467+
inst: AttrsInstance,
466468
recurse: bool = ...,
467469
filter: Optional[_FilterType[Any]] = ...,
468470
tuple_factory: Type[Sequence[Any]] = ...,

tests/test_mypy.yml

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
- case: attr_s_with_type_argument
22
parametrized:
3-
- val: 'a = attr.ib(type=int)'
4-
- val: 'a: int = attr.ib()'
3+
- val: "a = attr.ib(type=int)"
4+
- val: "a: int = attr.ib()"
55
main: |
66
import attr
77
@attr.s
@@ -12,7 +12,7 @@
1212
C(a=1)
1313
C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int"
1414
- case: attr_s_with_type_annotations
15-
main : |
15+
main: |
1616
import attr
1717
@attr.s
1818
class C:
@@ -97,7 +97,7 @@
9797
9898
- case: testAttrsUntypedNoUntypedDefs
9999
mypy_config: |
100-
disallow_untyped_defs = True
100+
disallow_untyped_defs = True
101101
main: |
102102
import attr
103103
@attr.s
@@ -316,7 +316,6 @@
316316
class Confused:
317317
...
318318
319-
320319
- case: testAttrsInheritance
321320
main: |
322321
import attr
@@ -469,14 +468,13 @@
469468
return self.x # E: Incompatible return value type (got "List[T]", expected "T")
470469
reveal_type(A) # N: Revealed type is "def [T] (x: builtins.list[T`1], y: T`1) -> main.A[T`1]"
471470
a = A([1], 2)
472-
reveal_type(a) # N: Revealed type is "main.A[builtins.int*]"
473-
reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int*]"
474-
reveal_type(a.y) # N: Revealed type is "builtins.int*"
471+
reveal_type(a) # N: Revealed type is "main.A[builtins.int]"
472+
reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int]"
473+
reveal_type(a.y) # N: Revealed type is "builtins.int"
475474
476475
A(['str'], 7) # E: Cannot infer type argument 1 of "A"
477476
A([1], '2') # E: Cannot infer type argument 1 of "A"
478477
479-
480478
- case: testAttrsUntypedGenericInheritance
481479
main: |
482480
from typing import Generic, TypeVar
@@ -514,12 +512,12 @@
514512
pass
515513
516514
sub_int = Sub[int](attr=1)
517-
reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int*]"
518-
reveal_type(sub_int.attr) # N: Revealed type is "builtins.int*"
515+
reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int]"
516+
reveal_type(sub_int.attr) # N: Revealed type is "builtins.int"
519517
520518
sub_str = Sub[str](attr='ok')
521-
reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str*]"
522-
reveal_type(sub_str.attr) # N: Revealed type is "builtins.str*"
519+
reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str]"
520+
reveal_type(sub_str.attr) # N: Revealed type is "builtins.str"
523521
524522
- case: testAttrsGenericInheritance2
525523
main: |
@@ -764,8 +762,7 @@
764762
return 'hello'
765763
766764
- case: testAttrsUsingBadConverter
767-
mypy_config:
768-
strict_optional = False
765+
mypy_config: strict_optional = False
769766
main: |
770767
import attr
771768
from typing import overload
@@ -792,8 +789,7 @@
792789
main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A"
793790
794791
- case: testAttrsUsingBadConverterReprocess
795-
mypy_config:
796-
strict_optional = False
792+
mypy_config: strict_optional = False
797793
main: |
798794
import attr
799795
from typing import overload
@@ -1169,7 +1165,6 @@
11691165
c = attr.ib(15)
11701166
D(b=17)
11711167
1172-
11731168
- case: testAttrsKwOnlySubclass
11741169
main: |
11751170
import attr
@@ -1211,8 +1206,7 @@
12111206
reveal_type(B) # N: Revealed type is "def (x: main.C) -> main.B"
12121207
12131208
- case: testDisallowUntypedWorksForwardBad
1214-
mypy_config:
1215-
disallow_untyped_defs = True
1209+
mypy_config: disallow_untyped_defs = True
12161210
main: |
12171211
import attr
12181212
@@ -1357,3 +1351,24 @@
13571351
foo = x
13581352
13591353
reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> main.B"
1354+
1355+
- case: testFields
1356+
main: |
1357+
from attrs import define, fields
1358+
1359+
@define
1360+
class A:
1361+
a: int
1362+
b: str
1363+
1364+
reveal_type(fields(A)) # N: Revealed type is "Any"
1365+
1366+
- case: testFieldsError
1367+
main: |
1368+
from attrs import fields
1369+
1370+
class A:
1371+
a: int
1372+
b: str
1373+
1374+
fields(A) # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected "Type[AttrsInstance]"

0 commit comments

Comments
 (0)