diff --git a/changelog.d/669.change.rst b/changelog.d/669.change.rst new file mode 100644 index 00000000..9b83d907 --- /dev/null +++ b/changelog.d/669.change.rst @@ -0,0 +1,5 @@ +We have also provisionally added ``attr.field()`` that supplants ``attr.ib()``. +It also requires at least Python 3.6 and is keyword-only. +Other than that, it only dropped a few arguments, but changed no defaults. + +As with ``attr.s()``: ``attr.ib()`` is not going anywhere. diff --git a/docs/api.rst b/docs/api.rst index 7797e650..63640dc9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -622,10 +622,16 @@ Therefore your constructive feedback in the linked issues above is strongly enco Alias for `attr.define`. + .. versionadded:: 20.1.0 + .. function:: attr.frozen(same_as_define) Behaves the same as `attr.define` but sets *frozen=True* and *on_setattr=None*. + .. versionadded:: 20.1.0 + +.. autofunction:: attr.field + Deprecated APIs --------------- diff --git a/src/attr/__init__.py b/src/attr/__init__.py index 572daaff..1475976a 100644 --- a/src/attr/__init__.py +++ b/src/attr/__init__.py @@ -71,6 +71,6 @@ __all__ = [ ] if sys.version_info[:2] >= (3, 6): - from ._next_gen import define, frozen, mutable + from ._next_gen import define, field, frozen, mutable - __all__.extend((define, frozen, mutable)) + __all__.extend((define, field, frozen, mutable)) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index f3d6bb66..0869914c 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -182,6 +182,77 @@ def attrib( on_setattr: Optional[_OnSetAttrArgType] = ..., ) -> Any: ... @overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload def attrs( maybe_cls: _C, these: Optional[Dict[str, Any]] = ..., diff --git a/src/attr/_next_gen.py b/src/attr/_next_gen.py index 8312d215..b0aefe31 100644 --- a/src/attr/_next_gen.py +++ b/src/attr/_next_gen.py @@ -10,7 +10,7 @@ from functools import partial from attr.exceptions import UnannotatedAttributeError from . import setters -from ._make import attrs +from ._make import NOTHING, attrib, attrs def define( @@ -32,7 +32,7 @@ def define( order=False, auto_detect=True, getstate_setstate=None, - on_setattr=setters.validate + on_setattr=setters.validate, ): r""" The only behavioral difference is the handling of the *auto_attribs* @@ -84,3 +84,40 @@ def define( mutable = define frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Identical to `attr.ib`, except keyword-only and with some arguments + removed. + + .. versionadded:: 20.1.0 + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + ) diff --git a/tests/test_next_gen.py b/tests/test_next_gen.py index c1a85a92..e350498a 100644 --- a/tests/test_next_gen.py +++ b/tests/test_next_gen.py @@ -40,7 +40,7 @@ class TestNextGen: @attr.define class Validated: - x: int = attr.ib(validator=attr.validators.instance_of(int)) + x: int = attr.field(validator=attr.validators.instance_of(int)) v = Validated(1) @@ -67,13 +67,13 @@ class TestNextGen: """ Don't guess if auto_attrib is set explicitly. - Having an unannotated attr.ib fails. + Having an unannotated attr.ib/attr.field fails. """ with pytest.raises(attr.exceptions.UnannotatedAttributeError): @attr.define(auto_attribs=True) class ThisFails: - x = attr.ib() + x = attr.field() y: int def test_override_auto_attribs_false(self): @@ -97,7 +97,7 @@ class TestNextGen: @attr.define class OldSchool: - x = attr.ib() + x = attr.field() assert OldSchool(1) == OldSchool(1) diff --git a/tests/typing_example.py b/tests/typing_example.py index 1f7854a2..878837b7 100644 --- a/tests/typing_example.py +++ b/tests/typing_example.py @@ -211,7 +211,7 @@ class ValidatedSetter: # Provisional APIs @attr.define(order=True) class NGClass: - x: int + x: int = attr.field(default=42) # XXX: needs support in mypy