Skip to content

Commit

Permalink
Fix type annotations for converter types, considering the Converter c…
Browse files Browse the repository at this point in the history
…lass (#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
  • Loading branch information
filbranden authored Nov 27, 2024
1 parent 13105a6 commit 6c89173
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 32 deletions.
17 changes: 13 additions & 4 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class Attribute(Generic[_T]):
order: _EqOrderType
hash: bool | None
init: bool
converter: _ConverterType | Converter[Any, _T] | None
converter: Converter | None
metadata: dict[Any, Any]
type: type[_T] | None
kw_only: bool
Expand Down Expand Up @@ -194,7 +194,10 @@ def attrib(
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
type: type[_T] | None = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand All @@ -214,7 +217,10 @@ def attrib(
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
type: type[_T] | None = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand All @@ -234,7 +240,10 @@ def attrib(
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
type: object = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand Down
2 changes: 1 addition & 1 deletion src/attr/converters.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ def optional(converter: _ConverterType) -> _ConverterType: ...
def default_if_none(default: _T) -> _ConverterType: ...
@overload
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ...
def to_bool(val: str) -> bool: ...
def to_bool(val: str | int | bool) -> bool: ...
17 changes: 13 additions & 4 deletions src/attrs/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _C = TypeVar("_C", bound=type)

_EqOrderType = bool | Callable[[Any], Any]
_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any]
_ConverterType = Callable[[Any], Any]
_ConverterType = Callable[[Any], Any] | Converter[Any, _T]
_ReprType = Callable[[Any], str]
_ReprArgType = bool | _ReprType
_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any]
Expand Down Expand Up @@ -94,7 +94,10 @@ def field(
hash: bool | None = ...,
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand All @@ -114,7 +117,10 @@ def field(
hash: bool | None = ...,
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand All @@ -134,7 +140,10 @@ def field(
hash: bool | None = ...,
init: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
converter: _ConverterType | Converter[Any, _T] | None = ...,
converter: _ConverterType
| list[_ConverterType]
| tuple[_ConverterType]
| None = ...,
factory: Callable[[], _T] | None = ...,
kw_only: bool = ...,
eq: _EqOrderType | None = ...,
Expand Down
8 changes: 4 additions & 4 deletions tests/test_mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,9 @@
reveal_type(A)
out: |
main:15: error: Cannot determine __init__ type from converter [misc]
main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
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]
main:16: error: Cannot determine __init__ type from converter [misc]
main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
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]
main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A"
- case: testAttrsUsingBadConverterReprocess
Expand All @@ -816,9 +816,9 @@
reveal_type(A)
out: |
main:16: error: Cannot determine __init__ type from converter [misc]
main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
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]
main:17: error: Cannot determine __init__ type from converter [misc]
main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Never] | None" [arg-type]
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]
main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A"
- case: testAttrsUsingUnsupportedConverter
Expand Down
50 changes: 31 additions & 19 deletions tests/typing_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,40 +133,52 @@ class AliasExample:
attr.fields(AliasExample).without_alias.alias
attr.fields(AliasExample)._with_alias.alias


# Converters
# XXX: Currently converters can only be functions so none of this works
# although the stubs should be correct.

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

@attr.s
class ConvCOptional:
x: int | None = attr.ib(converter=attr.converters.optional(int))

# ConvCOptional(1)
# ConvCOptional(None)

ConvCOptional(1)
ConvCOptional(None)


# XXX: Fails with E: Unsupported converter, only named functions, types and lambdas are currently supported [misc]
# See https://github.com/python/mypy/issues/15736
#
# @attr.s
# class ConvCPipe:
# x: str = attr.ib(converter=attr.converters.pipe(int, str))
#
#
# ConvCPipe(3.4)
# ConvCPipe("09")
#
#
# @attr.s
# class ConvCDefaultIfNone:
# x: int = attr.ib(converter=attr.converters.default_if_none(42))


#
#
# ConvCDefaultIfNone(1)
# ConvCDefaultIfNone(None)


# @attr.s
# class ConvCToBool:
# x: int = attr.ib(converter=attr.converters.to_bool)
@attr.s
class ConvCToBool:
x: int = attr.ib(converter=attr.converters.to_bool)


# ConvCToBool(1)
# ConvCToBool(True)
# ConvCToBool("on")
# ConvCToBool("yes")
# ConvCToBool(0)
# ConvCToBool(False)
# ConvCToBool("n")
ConvCToBool(1)
ConvCToBool(True)
ConvCToBool("on")
ConvCToBool("yes")
ConvCToBool(0)
ConvCToBool(False)
ConvCToBool("n")


# Validators
Expand Down

0 comments on commit 6c89173

Please sign in to comment.