From 72fd1586268a88b4facd98639845ac327c8a4187 Mon Sep 17 00:00:00 2001 From: Ilya Konstantinov Date: Wed, 26 Apr 2023 16:11:17 +0200 Subject: [PATCH 1/3] Use attrs and @attrs.define in tests --- docs/source/additional_features.rst | 39 ++--- mypyc/test-data/run-attrs.test | 38 ++-- test-data/unit/check-flags.test | 20 +-- test-data/unit/check-incremental.test | 193 ++++++++++----------- test-data/unit/fine-grained-attr.test | 14 +- test-data/unit/lib-stub/attrs/__init__.pyi | 4 +- test-data/unit/stubgen.test | 6 +- 7 files changed, 157 insertions(+), 157 deletions(-) diff --git a/docs/source/additional_features.rst b/docs/source/additional_features.rst index 133310899b59e..10122e9b2fa9a 100644 --- a/docs/source/additional_features.rst +++ b/docs/source/additional_features.rst @@ -121,55 +121,54 @@ Type annotations can be added as follows: import attr - @attr.s + @attrs.define class A: - one: int = attr.ib() # Variable annotation (Python 3.6+) - two = attr.ib() # type: int # Type comment - three = attr.ib(type=int) # type= argument + one: int + two: int = 7 + three: int = attrs.field(8) -If you're using ``auto_attribs=True`` you must use variable annotations. +If you're using ``auto_attribs=False`` you must use ``attrs.field``: .. code-block:: python - import attr + import attrs - @attr.s(auto_attribs=True) + @attrs.define class A: - one: int - two: int = 7 - three: int = attr.ib(8) + one: int = attrs.field() # Variable annotation (Python 3.6+) + two = attrs.field() # type: int # Type comment + three = attrs.field(type=int) # type= argument Typeshed has a couple of "white lie" annotations to make type checking -easier. :py:func:`attr.ib` and :py:class:`attr.Factory` actually return objects, but the +easier. :py:func:`attrs.field` and :py:class:`attrs.Factory` actually return objects, but the annotation says these return the types that they expect to be assigned to. That enables this to work: .. code-block:: python - import attr - from typing import Dict + import attrs - @attr.s(auto_attribs=True) + @attrs.define class A: - one: int = attr.ib(8) - two: Dict[str, str] = attr.Factory(dict) - bad: str = attr.ib(16) # Error: can't assign int to str + one: int = attrs.field(8) + two: dict[str, str] = attrs.Factory(dict) + bad: str = attrs.field(16) # Error: can't assign int to str Caveats/Known Issues ==================== * The detection of attr classes and attributes works by function name only. This means that if you have your own helper functions that, for example, - ``return attr.ib()`` mypy will not see them. + ``return attrs.field()`` mypy will not see them. * All boolean arguments that mypy cares about must be literal ``True`` or ``False``. e.g the following will not work: .. code-block:: python - import attr + import attrs YES = True - @attr.s(init=YES) + @attrs.define(init=YES) class A: ... diff --git a/mypyc/test-data/run-attrs.test b/mypyc/test-data/run-attrs.test index 9c402a3eea7c5..b0ba4f7ff1761 100644 --- a/mypyc/test-data/run-attrs.test +++ b/mypyc/test-data/run-attrs.test @@ -1,10 +1,10 @@ -- Test cases for dataclasses based on the attrs library, where auto_attribs=True [case testRunAttrsclass] -import attr +import attrs from typing import Set, List, Callable, Any -@attr.s(auto_attribs=True) +@attrs.define class Person1: age : int name : str @@ -18,19 +18,19 @@ def testBool(p: Person1) -> bool: else: return False -@attr.s(auto_attribs=True) +@attrs.define class Person1b(Person1): id: str = '000' -@attr.s(auto_attribs=True) +@attrs.define class Person2: age : int - name : str = attr.ib(default='robot') + name : str = 'robot' -@attr.s(auto_attribs=True, order=True) +@attrs.define(order=True) class Person3: - age : int = attr.ib(default = 6) - friendIDs : List[int] = attr.ib(factory = list) + age : int = attrs.field(default=6) + friendIDs : List[int] = attrs.field(factory=list) def get_age(self) -> int: return (self.age) @@ -49,7 +49,7 @@ def get_next_age(g: Callable[[Any], int]) -> Callable[[Any], int]: return g(a) + 1 return f -@attr.s(auto_attribs=True) +@attrs.define class Person4: age : int _name : str = 'Bot' @@ -62,10 +62,10 @@ class Person4: def name(self) -> str: return self._name -@attr.s(auto_attribs=True) +@attrs.define class Point: - x : int = attr.ib(converter=int) - y : int = attr.ib(init=False) + x : int = attrs.field(converter=int) + y : int = attrs.field(init=False) def __attrs_post_init__(self): self.y = self.x + 1 @@ -165,10 +165,10 @@ assert isinstance(Person3().get_age, BuiltinMethodType) [case testRunAttrsclassNonAuto] -import attr +import attrs from typing import Set, List, Callable, Any -@attr.s +@attrs.define class Person1: age = attr.ib(type=int) name = attr.ib(type=str) @@ -182,16 +182,16 @@ def testBool(p: Person1) -> bool: else: return False -@attr.s +@attrs.define class Person1b(Person1): id = attr.ib(type=str, default='000') -@attr.s +@attrs.define class Person2: age = attr.ib(type=int) name = attr.ib(type=str, default='robot') -@attr.s(order=True) +@attrs.define(order=True) class Person3: age = attr.ib(type=int, default=6) friendIDs = attr.ib(factory=list, type=List[int]) @@ -213,7 +213,7 @@ def get_next_age(g: Callable[[Any], int]) -> Callable[[Any], int]: return g(a) + 1 return f -@attr.s +@attrs.define class Person4: age = attr.ib(type=int) _name = attr.ib(type=str, default='Bot') @@ -226,7 +226,7 @@ class Person4: def name(self) -> str: return self._name -@attr.s +@attrs.define class Point: x = attr.ib(type=int, converter=int) y = attr.ib(type=int, init=False) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 244e728f3ab6c..e4fcf5d090fae 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -1305,32 +1305,32 @@ main:3: error: Function is missing a type annotation for one or more arguments [case testDisallowIncompleteDefsAttrsNoAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class Unannotated: - foo = attr.ib() + foo = attrs.field() [builtins fixtures/plugin_attrs.pyi] [case testDisallowIncompleteDefsAttrsWithAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class Annotated: - bar: int = attr.ib() + bar: int = attrs.field() [builtins fixtures/plugin_attrs.pyi] [case testDisallowIncompleteDefsAttrsPartialAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class PartiallyAnnotated: # E: Function is missing a type annotation for one or more arguments - bar: int = attr.ib() - baz = attr.ib() + bar: int = attrs.field() + baz = attrs.field() [builtins fixtures/plugin_attrs.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ec48ff43cc642..661afca807f40 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2916,8 +2916,8 @@ main:3: note: Right operand is of type "Optional[int]" [case testAttrsIncrementalSubclassingCached] from a import A -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class B(A): e: str = 'e' a = B(5, [5], 'foo') @@ -2928,15 +2928,14 @@ a._d = 22 a.e = 'hi' [file a.py] -import attr -import attr +import attrs from typing import List, ClassVar -@attr.s(auto_attribs=True) +@attrs.define class A: a: int _b: List[int] c: str = '18' - _d: int = attr.ib(validator=None, default=18) + _d: int = attrs.field(validator=None, default=18) E = 7 F: ClassVar[int] = 22 @@ -2946,8 +2945,8 @@ class A: [case testAttrsIncrementalSubclassingCachedConverter] from a import A -import attr -@attr.s +import attrs +@attrs.define class B(A): pass reveal_type(B) @@ -2956,10 +2955,10 @@ reveal_type(B) def converter(s:int) -> str: return 'hello' -import attr -@attr.s +import attrs +@attrs.define class A: - x: str = attr.ib(converter=converter) + x: str = attrs.field(converter=converter) [builtins fixtures/list.pyi] [out1] @@ -2970,17 +2969,17 @@ main:6: note: Revealed type is "def (x: builtins.int) -> __main__.B" [case testAttrsIncrementalSubclassingCachedType] from a import A -import attr -@attr.s +import attrs +@attrs.define class B(A): pass reveal_type(B) [file a.py] -import attr -@attr.s +import attrs +@attrs.define class A: - x = attr.ib(type=int) + x: int [builtins fixtures/list.pyi] [out1] @@ -3006,16 +3005,16 @@ NoCmp(1) > NoCmp(2) NoCmp(1) >= NoCmp(2) [file a.py] -import attr -@attr.s(frozen=True) +import attrs +@attrs.frozen class Frozen: - x: int = attr.ib() -@attr.s(init=False) + x: int +@attrs.define(init=False) class NoInit: - x: int = attr.ib() -@attr.s(eq=False) + x: int +@attrs.define(eq=False) class NoCmp: - x: int = attr.ib() + x: int [builtins fixtures/list.pyi] [rechecked] @@ -3092,22 +3091,22 @@ from b import B B(5, 'foo') [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: x: int [file b.py] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: str [file b.py.2] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: int @@ -3121,22 +3120,22 @@ main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" from b import B B(5, 'foo') [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: x: int [file b.py] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: int [file b.py.2] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: str @@ -3152,24 +3151,24 @@ from c import C C(5, 'foo', True) [file a.py] -import attr -@attr.s +import attrs +@attrs.define class A: - a: int = attr.ib() + a: int [file b.py] -import attr -@attr.s +import attrs +@attrs.define class B: - b: str = attr.ib() + b: str [file c.py] from a import A from b import B -import attr -@attr.s +import attrs +@attrs.define class C(A, B): - c: bool = attr.ib() + c: bool [builtins fixtures/list.pyi] [out1] @@ -3184,10 +3183,10 @@ from typing import Optional def converter(s:Optional[int]) -> int: ... -import attr -@attr.s +import attrs +@attrs.define class A: - x: int = attr.ib(converter=converter) + x: int = attrs.field(converter=converter) [builtins fixtures/list.pyi] [out1] @@ -3254,80 +3253,80 @@ def maybe_bool(x: Optional[bool]) -> bool: ... [file base.py] from typing import Optional -import attr +import attrs import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class Base: - x: int = attr.ib(converter=maybe_int) - y: str = attr.ib(converter=maybe_str) - z: bool = attr.ib(converter=bar.maybe_bool) + x: int = attrs.field(converter=maybe_int) + y: str = attrs.field(converter=maybe_str) + z: bool = attrs.field(converter=bar.maybe_bool) [file subclass.py] from typing import Optional -import attr +import attrs from base import Base -@attr.s +@attrs.define class A(Base): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class B(Base): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [file submodule/__init__.py] [file submodule/base.py] from typing import Optional -import attr +import attrs import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class SubBase: - x: int = attr.ib(converter=maybe_int) - y: str = attr.ib(converter=maybe_str) - z: bool = attr.ib(converter=bar.maybe_bool) + x: int = attrs.field(converter=maybe_int) + y: str = attrs.field(converter=maybe_str) + z: bool = attrs.field(converter=bar.maybe_bool) [file submodule/subclass.py] from typing import Optional -import attr +import attrs from base import Base -@attr.s +@attrs.define class AA(Base): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class BB(Base): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [file submodule/subsubclass.py] from typing import Optional -import attr +import attrs from .base import SubBase -@attr.s +@attrs.define class SubAA(SubBase): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class SubBB(SubBase): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [builtins fixtures/list.pyi] [out1] [out2] @@ -3341,13 +3340,13 @@ tmp/a.py:17: error: Argument 2 to "SubAA" has incompatible type "int"; expected tmp/a.py:18: error: Argument 5 to "SubBB" has incompatible type "int"; expected "Optional[str]" [case testAttrsIncrementalConverterInFunction] -import attr +import attrs def foo() -> None: def foo(x: str) -> int: ... - @attr.s + @attrs.define class A: - x: int = attr.ib(converter=foo) + x: int = attrs.field(converter=foo) reveal_type(A) [builtins fixtures/list.pyi] [out1] @@ -3366,10 +3365,10 @@ from typing import List def converter(s:F) -> int: ... -import attr -@attr.s +import attrs +@attrs.define class A: - x: int = attr.ib(converter=converter) + x: int = attrs.field(converter=converter) F = List[int] @@ -3383,18 +3382,18 @@ main:3: note: Revealed type is "def (x: builtins.list[builtins.int]) -> a.a.A" [case testAttrsIncrementalConverterType-skip] from a import C -import attr +import attrs o = C("1", "2", "3", "4") o = C(1, 2, "3", 4) reveal_type(C) -@attr.s +@attrs.define class D(C): - x: str = attr.ib() + x: str reveal_type(D) [file a.py] from typing import overload -import attr -@attr.dataclass +import attrs +@attrs.define class A: x: str @overload @@ -3404,12 +3403,12 @@ def parse(x: int) -> int: def parse(x: str, y: str = '') -> int: ... def parse(x, y): ... -@attr.s +@attrs.define class C: - a: complex = attr.ib(converter=complex) - b: int = attr.ib(converter=int) - c: A = attr.ib(converter=A) - d: int = attr.ib(converter=parse) + a: complex = attrs.field(converter=complex) + b: int = attrs.field(converter=int) + c: A = attrs.field(converter=A) + d: int = attrs.field(converter=parse) [builtins fixtures/plugin_attrs.pyi] [out1] main:6: note: Revealed type is "def (a: Union[builtins.float, builtins.str], b: Union[builtins.str, builtins.bytes, builtins.int], c: builtins.str, d: Union[builtins.int, builtins.str]) -> a.C" @@ -3423,20 +3422,20 @@ from a import A A(5) [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: int [file a.py.2] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: str [file a.py.3] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: int = 6 diff --git a/test-data/unit/fine-grained-attr.test b/test-data/unit/fine-grained-attr.test index 145bfe57e4b28..8606fea15849d 100644 --- a/test-data/unit/fine-grained-attr.test +++ b/test-data/unit/fine-grained-attr.test @@ -1,18 +1,18 @@ [case updateMagicField] -from attr import Attribute +from attrs import Attribute import m def g() -> Attribute[int]: return m.A.__attrs_attrs__[0] [file m.py] -from attr import define +from attrs import define @define class A: a: int [file m.py.2] -from attr import define +from attrs import define @define class A: @@ -26,7 +26,7 @@ main:5: error: Incompatible return value type (got "Attribute[float]", expected import m [file c.py] -from attr import define +from attrs import define @define class A: @@ -49,11 +49,11 @@ A.__attrs_attrs__.b [case magicAttributeConsistency2-only_when_cache] [file c.py] -import attr +import attrs -@attr.s +@attrs.define class Entry: - var: int = attr.ib() + var: int [builtins fixtures/plugin_attrs.pyi] [file m.py] diff --git a/test-data/unit/lib-stub/attrs/__init__.pyi b/test-data/unit/lib-stub/attrs/__init__.pyi index 8e9aa1fdced57..bf31274b3eb99 100644 --- a/test-data/unit/lib-stub/attrs/__init__.pyi +++ b/test-data/unit/lib-stub/attrs/__init__.pyi @@ -1,4 +1,6 @@ -from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping +from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping, Generic + +from attr import Attribute as Attribute _T = TypeVar('_T') _C = TypeVar('_C', bound=type) diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 16584a1341473..1a89ec1acc75a 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -2361,11 +2361,11 @@ __version__ = '' [out] [case testAttrsClass_semanal] -import attr +import attrs -@attr.s +@attrs.define class C: - x = attr.ib() + x = attrs.field() [out] from _typeshed import Incomplete From 64b3d1a94b0d00a5224a78a57f6653698313de69 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Thu, 18 May 2023 10:57:03 -0400 Subject: [PATCH 2/3] revert mypyc test changes --- mypyc/test-data/run-attrs.test | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/mypyc/test-data/run-attrs.test b/mypyc/test-data/run-attrs.test index b0ba4f7ff1761..9c402a3eea7c5 100644 --- a/mypyc/test-data/run-attrs.test +++ b/mypyc/test-data/run-attrs.test @@ -1,10 +1,10 @@ -- Test cases for dataclasses based on the attrs library, where auto_attribs=True [case testRunAttrsclass] -import attrs +import attr from typing import Set, List, Callable, Any -@attrs.define +@attr.s(auto_attribs=True) class Person1: age : int name : str @@ -18,19 +18,19 @@ def testBool(p: Person1) -> bool: else: return False -@attrs.define +@attr.s(auto_attribs=True) class Person1b(Person1): id: str = '000' -@attrs.define +@attr.s(auto_attribs=True) class Person2: age : int - name : str = 'robot' + name : str = attr.ib(default='robot') -@attrs.define(order=True) +@attr.s(auto_attribs=True, order=True) class Person3: - age : int = attrs.field(default=6) - friendIDs : List[int] = attrs.field(factory=list) + age : int = attr.ib(default = 6) + friendIDs : List[int] = attr.ib(factory = list) def get_age(self) -> int: return (self.age) @@ -49,7 +49,7 @@ def get_next_age(g: Callable[[Any], int]) -> Callable[[Any], int]: return g(a) + 1 return f -@attrs.define +@attr.s(auto_attribs=True) class Person4: age : int _name : str = 'Bot' @@ -62,10 +62,10 @@ class Person4: def name(self) -> str: return self._name -@attrs.define +@attr.s(auto_attribs=True) class Point: - x : int = attrs.field(converter=int) - y : int = attrs.field(init=False) + x : int = attr.ib(converter=int) + y : int = attr.ib(init=False) def __attrs_post_init__(self): self.y = self.x + 1 @@ -165,10 +165,10 @@ assert isinstance(Person3().get_age, BuiltinMethodType) [case testRunAttrsclassNonAuto] -import attrs +import attr from typing import Set, List, Callable, Any -@attrs.define +@attr.s class Person1: age = attr.ib(type=int) name = attr.ib(type=str) @@ -182,16 +182,16 @@ def testBool(p: Person1) -> bool: else: return False -@attrs.define +@attr.s class Person1b(Person1): id = attr.ib(type=str, default='000') -@attrs.define +@attr.s class Person2: age = attr.ib(type=int) name = attr.ib(type=str, default='robot') -@attrs.define(order=True) +@attr.s(order=True) class Person3: age = attr.ib(type=int, default=6) friendIDs = attr.ib(factory=list, type=List[int]) @@ -213,7 +213,7 @@ def get_next_age(g: Callable[[Any], int]) -> Callable[[Any], int]: return g(a) + 1 return f -@attrs.define +@attr.s class Person4: age = attr.ib(type=int) _name = attr.ib(type=str, default='Bot') @@ -226,7 +226,7 @@ class Person4: def name(self) -> str: return self._name -@attrs.define +@attr.s class Point: x = attr.ib(type=int, converter=int) y = attr.ib(type=int, init=False) From e5d8da067c5f8599938a84ffd39725167e1fb564 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Thu, 18 May 2023 12:55:43 -0400 Subject: [PATCH 3/3] replace testAttrsNewPackage with testAttrsOldPackage --- test-data/unit/check-plugin-attrs.test | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index ce1d670431c78..6723271dc7f6a 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -400,20 +400,25 @@ reveal_type(D) # N: Revealed type is "def (b: Any) -> __main__.D" [builtins fixtures/bool.pyi] -[case testAttrsNewPackage] -import attrs -@attrs.define +[case testAttrsOldPackage] +import attr +@attr.s(auto_attribs=True) class A: - a: int = attrs.field() + a: int = attr.ib() b: bool -@attrs.frozen +@attr.s(auto_attribs=True, frozen=True) class B: a: bool b: int +@attr.s +class C: + a = attr.ib(type=int) + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -> __main__.A" reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B" +reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" [builtins fixtures/bool.pyi]