Skip to content

Commit 6c89173

Browse files
authored
Fix type annotations for converter types, considering the Converter class (#1373)
* Fix type annotations for converter types, considering the Converter class Also consider that field() or attr.ib() takes a list or tuple of converters as an implicit pipe, add type annotations for that syntax * Fix expected mypy output with bad converters now that typing information has been expanded
1 parent 13105a6 commit 6c89173

File tree

5 files changed

+62
-32
lines changed

5 files changed

+62
-32
lines changed

src/attr/__init__.pyi

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class Attribute(Generic[_T]):
130130
order: _EqOrderType
131131
hash: bool | None
132132
init: bool
133-
converter: _ConverterType | Converter[Any, _T] | None
133+
converter: Converter | None
134134
metadata: dict[Any, Any]
135135
type: type[_T] | None
136136
kw_only: bool
@@ -194,7 +194,10 @@ def attrib(
194194
init: bool = ...,
195195
metadata: Mapping[Any, Any] | None = ...,
196196
type: type[_T] | None = ...,
197-
converter: _ConverterType | Converter[Any, _T] | None = ...,
197+
converter: _ConverterType
198+
| list[_ConverterType]
199+
| tuple[_ConverterType]
200+
| None = ...,
198201
factory: Callable[[], _T] | None = ...,
199202
kw_only: bool = ...,
200203
eq: _EqOrderType | None = ...,
@@ -214,7 +217,10 @@ def attrib(
214217
init: bool = ...,
215218
metadata: Mapping[Any, Any] | None = ...,
216219
type: type[_T] | None = ...,
217-
converter: _ConverterType | Converter[Any, _T] | None = ...,
220+
converter: _ConverterType
221+
| list[_ConverterType]
222+
| tuple[_ConverterType]
223+
| None = ...,
218224
factory: Callable[[], _T] | None = ...,
219225
kw_only: bool = ...,
220226
eq: _EqOrderType | None = ...,
@@ -234,7 +240,10 @@ def attrib(
234240
init: bool = ...,
235241
metadata: Mapping[Any, Any] | None = ...,
236242
type: object = ...,
237-
converter: _ConverterType | Converter[Any, _T] | None = ...,
243+
converter: _ConverterType
244+
| list[_ConverterType]
245+
| tuple[_ConverterType]
246+
| None = ...,
238247
factory: Callable[[], _T] | None = ...,
239248
kw_only: bool = ...,
240249
eq: _EqOrderType | None = ...,

src/attr/converters.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ def optional(converter: _ConverterType) -> _ConverterType: ...
1010
def default_if_none(default: _T) -> _ConverterType: ...
1111
@overload
1212
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ...
13-
def to_bool(val: str) -> bool: ...
13+
def to_bool(val: str | int | bool) -> bool: ...

src/attrs/__init__.pyi

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ _C = TypeVar("_C", bound=type)
5151

5252
_EqOrderType = bool | Callable[[Any], Any]
5353
_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any]
54-
_ConverterType = Callable[[Any], Any]
54+
_ConverterType = Callable[[Any], Any] | Converter[Any, _T]
5555
_ReprType = Callable[[Any], str]
5656
_ReprArgType = bool | _ReprType
5757
_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any]
@@ -94,7 +94,10 @@ def field(
9494
hash: bool | None = ...,
9595
init: bool = ...,
9696
metadata: Mapping[Any, Any] | None = ...,
97-
converter: _ConverterType | Converter[Any, _T] | None = ...,
97+
converter: _ConverterType
98+
| list[_ConverterType]
99+
| tuple[_ConverterType]
100+
| None = ...,
98101
factory: Callable[[], _T] | None = ...,
99102
kw_only: bool = ...,
100103
eq: _EqOrderType | None = ...,
@@ -114,7 +117,10 @@ def field(
114117
hash: bool | None = ...,
115118
init: bool = ...,
116119
metadata: Mapping[Any, Any] | None = ...,
117-
converter: _ConverterType | Converter[Any, _T] | None = ...,
120+
converter: _ConverterType
121+
| list[_ConverterType]
122+
| tuple[_ConverterType]
123+
| None = ...,
118124
factory: Callable[[], _T] | None = ...,
119125
kw_only: bool = ...,
120126
eq: _EqOrderType | None = ...,
@@ -134,7 +140,10 @@ def field(
134140
hash: bool | None = ...,
135141
init: bool = ...,
136142
metadata: Mapping[Any, Any] | None = ...,
137-
converter: _ConverterType | Converter[Any, _T] | None = ...,
143+
converter: _ConverterType
144+
| list[_ConverterType]
145+
| tuple[_ConverterType]
146+
| None = ...,
138147
factory: Callable[[], _T] | None = ...,
139148
kw_only: bool = ...,
140149
eq: _EqOrderType | None = ...,

tests/test_mypy.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -788,9 +788,9 @@
788788
reveal_type(A)
789789
out: |
790790
main:15: error: Cannot determine __init__ type from converter [misc]
791-
main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
791+
main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type]
792792
main:16: error: Cannot determine __init__ type from converter [misc]
793-
main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
793+
main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type]
794794
main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A"
795795
796796
- case: testAttrsUsingBadConverterReprocess
@@ -816,9 +816,9 @@
816816
reveal_type(A)
817817
out: |
818818
main:16: error: Cannot determine __init__ type from converter [misc]
819-
main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
819+
main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type]
820820
main:17: error: Cannot determine __init__ type from converter [misc]
821-
main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
821+
main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type]
822822
main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A"
823823
824824
- case: testAttrsUsingUnsupportedConverter

tests/typing_example.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,40 +133,52 @@ class AliasExample:
133133
attr.fields(AliasExample).without_alias.alias
134134
attr.fields(AliasExample)._with_alias.alias
135135

136+
136137
# Converters
137-
# XXX: Currently converters can only be functions so none of this works
138-
# although the stubs should be correct.
139138

140-
# @attr.s
141-
# class ConvCOptional:
142-
# x: Optional[int] = attr.ib(converter=attr.converters.optional(int))
143139

140+
@attr.s
141+
class ConvCOptional:
142+
x: int | None = attr.ib(converter=attr.converters.optional(int))
144143

145-
# ConvCOptional(1)
146-
# ConvCOptional(None)
147144

145+
ConvCOptional(1)
146+
ConvCOptional(None)
148147

148+
149+
# XXX: Fails with E: Unsupported converter, only named functions, types and lambdas are currently supported [misc]
150+
# See https://github.com/python/mypy/issues/15736
151+
#
152+
# @attr.s
153+
# class ConvCPipe:
154+
# x: str = attr.ib(converter=attr.converters.pipe(int, str))
155+
#
156+
#
157+
# ConvCPipe(3.4)
158+
# ConvCPipe("09")
159+
#
160+
#
149161
# @attr.s
150162
# class ConvCDefaultIfNone:
151163
# x: int = attr.ib(converter=attr.converters.default_if_none(42))
152-
153-
164+
#
165+
#
154166
# ConvCDefaultIfNone(1)
155167
# ConvCDefaultIfNone(None)
156168

157169

158-
# @attr.s
159-
# class ConvCToBool:
160-
# x: int = attr.ib(converter=attr.converters.to_bool)
170+
@attr.s
171+
class ConvCToBool:
172+
x: int = attr.ib(converter=attr.converters.to_bool)
161173

162174

163-
# ConvCToBool(1)
164-
# ConvCToBool(True)
165-
# ConvCToBool("on")
166-
# ConvCToBool("yes")
167-
# ConvCToBool(0)
168-
# ConvCToBool(False)
169-
# ConvCToBool("n")
175+
ConvCToBool(1)
176+
ConvCToBool(True)
177+
ConvCToBool("on")
178+
ConvCToBool("yes")
179+
ConvCToBool(0)
180+
ConvCToBool(False)
181+
ConvCToBool("n")
170182

171183

172184
# Validators

0 commit comments

Comments
 (0)