Skip to content

Support factory= in attr plugin. #5005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ def _attribute_from_attrib_maker(ctx: 'mypy.plugin.ClassDefContext',
init = _get_bool_argument(ctx, rvalue, 'init', True)
# TODO: Check for attr.NOTHING
attr_has_default = bool(_get_argument(rvalue, 'default'))
attr_has_factory = bool(_get_argument(rvalue, 'factory'))

if attr_has_default and attr_has_factory:
ctx.api.fail("Can't pass both `default` and `factory`.", rvalue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a good idea to always add tests for any newly added error messages.

elif attr_has_factory:
attr_has_default = True

# If the type isn't set through annotation but is passed through `type=` use that.
type_arg = _get_argument(rvalue, 'type')
Expand Down
29 changes: 29 additions & 0 deletions test-data/unit/check-attr.test
Original file line number Diff line number Diff line change
Expand Up @@ -771,3 +771,32 @@ class FFrozen(F):
def bar(self) -> bool:
return self._cb(5, 6)
[builtins fixtures/callable.pyi]

[case testAttrsWithFactory]
from typing import List
import attr
def my_factory() -> int:
return 7
@attr.s
class A:
x: List[int] = attr.ib(factory=list)
y: int = attr.ib(factory=my_factory)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add also a test where this fails due to a wrong return type of the factory function.

A()
[builtins fixtures/list.pyi]

[case testAttrsFactoryAndDefault]
import attr
@attr.s
class A:
x: int = attr.ib(factory=int, default=7) # E: Can't pass both `default` and `factory`.
[builtins fixtures/bool.pyi]

[case testAttrsFactoryBadReturn]
import attr
def my_factory() -> int:
return 7
@attr.s
class A:
x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[T]", variable has type "int")
y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment (expression has type "int", variable has type "str")
[builtins fixtures/list.pyi]
130 changes: 85 additions & 45 deletions test-data/unit/lib-stub/attr.pyi
Original file line number Diff line number Diff line change
@@ -1,57 +1,97 @@
from typing import TypeVar, overload, Callable, Any, Type, Optional
from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping

_T = TypeVar('_T')
_C = TypeVar('_C', bound=type)

_ValidatorType = Callable[[Any, Any, _T], Any]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought validator should return bool, isn't this the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of validators is ignored. A validation error is a raised exception. However I don't want to force a "None"

_ConverterType = Callable[[Any], _T]
_FilterType = Callable[[Any, Any], bool]
_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]

# This form catches an explicit None or no default and infers the type from the other arguments.
@overload
def attrib(default: None = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: Optional[_ConverterType[_T]] = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: Optional[Type[_T]] = ...,
converter: Optional[_ConverterType[_T]] = ...,
factory: Optional[Callable[[], _T]] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@overload
def attr(default: Optional[_T] = ...,
validator: Optional[Any] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: Optional[Callable[[Any], _T]] = ...,
metadata: Any = ...,
type: Optional[Type[_T]] = ...,
converter: Optional[Callable[[Any], _T]] = ...) -> _T: ...
def attrib(default: _T,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: Optional[_ConverterType[_T]] = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: Optional[Type[_T]] = ...,
converter: Optional[_ConverterType[_T]] = ...,
factory: Optional[Callable[[], _T]] = ...,
) -> _T: ...
# This form catches explicit None or no default but with no other arguments returns Any.
@overload
def attr(default: None = ...,
validator: None = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: Optional[Callable[[Any], _T]] = ...,
metadata: Any = ...,
type: None = ...,
converter: None = ...) -> Any: ...
def attrib(default: None = ...,
validator: None = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: None = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: None = ...,
converter: None = ...,
factory: None = ...,
) -> Any: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@overload
def attrib(default: Optional[_T] = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
convert: Optional[_ConverterType[_T]] = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: object = ...,
converter: Optional[_ConverterType[_T]] = ...,
factory: Optional[Callable[[], _T]] = ...,
) -> Any: ...

@overload
def attributes(maybe_cls: _C,
these: Optional[Any] = ...,
repr_ns: Optional[str] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
str: bool = ...,
auto_attribs: bool = ...) -> _C: ...
def attrs(maybe_cls: _C,
these: Optional[Mapping[str, Any]] = ...,
repr_ns: Optional[str] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
str: bool = ...,
auto_attribs: bool = ...) -> _C: ...
@overload
def attributes(maybe_cls: None = ...,
these: Optional[Any] = ...,
repr_ns: Optional[str] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
str: bool = ...,
auto_attribs: bool = ...) -> Callable[[_C], _C]: ...
def attrs(maybe_cls: None = ...,
these: Optional[Mapping[str, Any]] = ...,
repr_ns: Optional[str] = ...,
repr: bool = ...,
cmp: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
str: bool = ...,
auto_attribs: bool = ...) -> Callable[[_C], _C]: ...


# aliases
s = attrs = attributes
ib = attrib = attr
s = attributes = attrs
ib = attr = attrib
dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;)