From 1ce25335f1b121188fc8c95580406cecada56e9e Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Fri, 26 Jul 2019 18:31:04 -0300 Subject: [PATCH 01/10] Ability to disable the keyword-only order checks that forbid normal attrs after kw_only attrs --- src/attr/_make.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index 283aec667..5fb7e1b37 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -285,7 +285,7 @@ def _counter_getter(e): return e[1].counter -def _transform_attrs(cls, these, auto_attribs, kw_only): +def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_order_check): """ Transform all `_CountingAttr`s on a class into `Attribute`s. @@ -398,7 +398,12 @@ def _transform_attrs(cls, these, auto_attribs, kw_only): a.kw_only is False ): had_default = True - if was_kw_only is True and a.kw_only is False and a.init is True: + if ( + kw_only_order_check is True + and was_kw_only is True + and a.kw_only is False + and a.init is True + ): raise ValueError( "Non keyword-only attributes are not allowed after a " "keyword-only attribute (unless they are init=False). " @@ -456,9 +461,10 @@ def __init__( kw_only, cache_hash, is_exc, + kw_only_order_check, ): attrs, base_attrs, base_map = _transform_attrs( - cls, these, auto_attribs, kw_only + cls, these, auto_attribs, kw_only, kw_only_order_check ) self._cls = cls @@ -732,6 +738,7 @@ def attrs( kw_only=False, cache_hash=False, auto_exc=False, + kw_only_order_check=True, ): r""" A class decorator that adds `dunder @@ -854,6 +861,10 @@ def attrs( default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. + :param bool kw_only_order_check: Enable or disable the ordering check for keyword-only + attributes with respect to non keyword-only attributes. + - when True (Default): don't allow non keyword-only attrs after keyword-only attrs. + - when False: allow keyword-only attrs anywhere. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -874,6 +885,7 @@ def attrs( .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* + .. versionadded:: 19.2.0 *kw_only_order_check* """ def wrap(cls): @@ -893,6 +905,7 @@ def wrap(cls): kw_only, cache_hash, is_exc, + kw_only_order_check, ) if repr is True: From ac4e06f15097d614b6365a6342b14e5d2c9ca1b6 Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Fri, 26 Jul 2019 18:37:35 -0300 Subject: [PATCH 02/10] Updated typing info --- src/attr/__init__.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index 5ffa6dfe5..9d9b78e95 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -182,6 +182,7 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + kw_only_order_check: bool = ..., ) -> Callable[[_C], _C]: ... # TODO: add support for returning NamedTuple from the mypy plugin From 8e945a32f26ca4a90b350693b82967a32143a6e8 Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Fri, 26 Jul 2019 18:38:01 -0300 Subject: [PATCH 03/10] Updated tests, and added a new test for the new ordering feature --- tests/test_make.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tests/test_make.py b/tests/test_make.py index 43acd74e0..318fd41b7 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -163,7 +163,7 @@ def test_no_modifications(self): Does not attach __attrs_attrs__ to the class. """ C = make_tc() - _transform_attrs(C, None, False, False) + _transform_attrs(C, None, False, False, True) assert None is getattr(C, "__attrs_attrs__", None) @@ -172,7 +172,7 @@ def test_normal(self): Transforms every `_CountingAttr` and leaves others (a) be. """ C = make_tc() - attrs, _, _ = _transform_attrs(C, None, False, False) + attrs, _, _ = _transform_attrs(C, None, False, False, True) assert ["z", "y", "x"] == [a.name for a in attrs] @@ -186,7 +186,7 @@ class C(object): pass assert _Attributes(((), [], {})) == _transform_attrs( - C, None, False, False + C, None, False, False, True ) def test_transforms_to_attribute(self): @@ -194,7 +194,7 @@ def test_transforms_to_attribute(self): All `_CountingAttr`s are transformed into `Attribute`s. """ C = make_tc() - attrs, base_attrs, _ = _transform_attrs(C, None, False, False) + attrs, base_attrs, _ = _transform_attrs(C, None, False, False, True) assert [] == base_attrs assert 3 == len(attrs) @@ -211,7 +211,7 @@ class C(object): y = attr.ib() with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False) + _transform_attrs(C, None, False, False, True) assert ( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: Attribute" @@ -240,7 +240,7 @@ class C(B): x = attr.ib(default=None) y = attr.ib() - attrs, base_attrs, _ = _transform_attrs(C, None, False, True) + attrs, base_attrs, _ = _transform_attrs(C, None, False, True, True) assert len(attrs) == 3 assert len(base_attrs) == 1 @@ -263,7 +263,7 @@ class C(Base): y = attr.ib() attrs, base_attrs, _ = _transform_attrs( - C, {"x": attr.ib()}, False, False + C, {"x": attr.ib()}, False, False, True ) assert [] == base_attrs @@ -631,7 +631,7 @@ class C(object): y = attr.ib() with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False) + _transform_attrs(C, None, False, False, True) assert ( "Non keyword-only attributes are not allowed after a " @@ -642,6 +642,22 @@ class C(object): "type=None, converter=None, kw_only=False)", ) == e.value.args + def test_keyword_only_ordering_check_can_be_disabled(self): + """ + Raises `ValueError` if keyword-only attributes are followed by + regular (non keyword-only) attributes. + """ + + @attr.s(kw_only_order_check=False) + class C(object): + y = attr.ib(kw_only=True) + x = attr.ib() + + c = C(1, y=2) + + assert c.x == 1 + assert c.y == 2 + def test_keyword_only_attributes_allow_subclassing(self): """ Subclass can define keyword-only attributed without defaults, @@ -1314,7 +1330,7 @@ class C(object): pass b = _ClassBuilder( - C, None, True, True, False, False, False, False, False + C, None, True, True, False, False, False, False, False, True ) assert "<_ClassBuilder(cls=C)>" == repr(b) @@ -1328,7 +1344,7 @@ class C(object): x = attr.ib() b = _ClassBuilder( - C, None, True, True, False, False, False, False, False + C, None, True, True, False, False, False, False, False, True ) cls = ( @@ -1395,6 +1411,7 @@ class C(object): is_exc=False, kw_only=False, cache_hash=False, + kw_only_order_check=True, ) b._cls = {} # no __module__; no __qualname__ From b27b33282830a854cfd4bb2283c15a0eee338815 Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Fri, 26 Jul 2019 18:38:58 -0300 Subject: [PATCH 04/10] Pep8 --- src/attr/_make.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index 5fb7e1b37..164b91e9f 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -861,9 +861,11 @@ def attrs( default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. - :param bool kw_only_order_check: Enable or disable the ordering check for keyword-only - attributes with respect to non keyword-only attributes. - - when True (Default): don't allow non keyword-only attrs after keyword-only attrs. + :param bool kw_only_order_check: Enable or disable the ordering check for + keyword-only attributes with respect to non keyword-only attributes. + + - when True (Default): don't allow non keyword-only attrs after + keyword-only attrs. - when False: allow keyword-only attrs anywhere. .. versionadded:: 16.0.0 *slots* From 05f0b3a896547a48ff1fa262385305eff2a72ece Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Fri, 2 Aug 2019 16:04:09 -0300 Subject: [PATCH 05/10] Updated docstring --- tests/test_make.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_make.py b/tests/test_make.py index 318fd41b7..26bdf6aa6 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -644,8 +644,9 @@ class C(object): def test_keyword_only_ordering_check_can_be_disabled(self): """ - Raises `ValueError` if keyword-only attributes are followed by - regular (non keyword-only) attributes. + When this check is enable, a `ValueError` is raised if keyword-only + attributes are followed by regular (non keyword-only) attributes. + Setting the check flag to False, disables that check. """ @attr.s(kw_only_order_check=False) From 8dad19d4894a08bba12c9282a9d781b0ae08fcbe Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Tue, 13 Aug 2019 16:41:51 -0300 Subject: [PATCH 06/10] Rename to a name that reflects the usage, not the implementation --- src/attr/__init__.pyi | 2 +- src/attr/_make.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index 9d9b78e95..5d9b455b5 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -182,7 +182,7 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., - kw_only_order_check: bool = ..., + kw_only_anywhere: bool = ..., ) -> Callable[[_C], _C]: ... # TODO: add support for returning NamedTuple from the mypy plugin diff --git a/src/attr/_make.py b/src/attr/_make.py index 164b91e9f..d6e1ab6e7 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -285,7 +285,7 @@ def _counter_getter(e): return e[1].counter -def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_order_check): +def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_anywhere): """ Transform all `_CountingAttr`s on a class into `Attribute`s. @@ -399,7 +399,7 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_order_check): ): had_default = True if ( - kw_only_order_check is True + kw_only_anywhere is False and was_kw_only is True and a.kw_only is False and a.init is True @@ -461,10 +461,10 @@ def __init__( kw_only, cache_hash, is_exc, - kw_only_order_check, + kw_only_anywhere, ): attrs, base_attrs, base_map = _transform_attrs( - cls, these, auto_attribs, kw_only, kw_only_order_check + cls, these, auto_attribs, kw_only, kw_only_anywhere ) self._cls = cls @@ -738,7 +738,7 @@ def attrs( kw_only=False, cache_hash=False, auto_exc=False, - kw_only_order_check=True, + kw_only_anywhere=False, ): r""" A class decorator that adds `dunder @@ -861,12 +861,12 @@ def attrs( default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. - :param bool kw_only_order_check: Enable or disable the ordering check for - keyword-only attributes with respect to non keyword-only attributes. + :param bool kw_only_anywhere: Allow or disallow the definition of + keyword-only attributes attributes after non keyword-only attributes. - - when True (Default): don't allow non keyword-only attrs after + - when False (Default): don't allow non keyword-only attrs after keyword-only attrs. - - when False: allow keyword-only attrs anywhere. + - when True: allow keyword-only attrs anywhere. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -887,7 +887,7 @@ def attrs( .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* - .. versionadded:: 19.2.0 *kw_only_order_check* + .. versionadded:: 19.2.0 *kw_only_anywhere* """ def wrap(cls): @@ -907,7 +907,7 @@ def wrap(cls): kw_only, cache_hash, is_exc, - kw_only_order_check, + kw_only_anywhere, ) if repr is True: From 2fd384c5328af7aa545a5b60de78fcf6131242ce Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Tue, 13 Aug 2019 16:42:02 -0300 Subject: [PATCH 07/10] Update tests after the rename --- tests/test_make.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_make.py b/tests/test_make.py index 26bdf6aa6..7fe2aeaec 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -163,7 +163,7 @@ def test_no_modifications(self): Does not attach __attrs_attrs__ to the class. """ C = make_tc() - _transform_attrs(C, None, False, False, True) + _transform_attrs(C, None, False, False, False) assert None is getattr(C, "__attrs_attrs__", None) @@ -172,7 +172,7 @@ def test_normal(self): Transforms every `_CountingAttr` and leaves others (a) be. """ C = make_tc() - attrs, _, _ = _transform_attrs(C, None, False, False, True) + attrs, _, _ = _transform_attrs(C, None, False, False, False) assert ["z", "y", "x"] == [a.name for a in attrs] @@ -186,7 +186,7 @@ class C(object): pass assert _Attributes(((), [], {})) == _transform_attrs( - C, None, False, False, True + C, None, False, False, False ) def test_transforms_to_attribute(self): @@ -194,7 +194,7 @@ def test_transforms_to_attribute(self): All `_CountingAttr`s are transformed into `Attribute`s. """ C = make_tc() - attrs, base_attrs, _ = _transform_attrs(C, None, False, False, True) + attrs, base_attrs, _ = _transform_attrs(C, None, False, False, False) assert [] == base_attrs assert 3 == len(attrs) @@ -211,7 +211,7 @@ class C(object): y = attr.ib() with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False, True) + _transform_attrs(C, None, False, False, False) assert ( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: Attribute" @@ -240,7 +240,7 @@ class C(B): x = attr.ib(default=None) y = attr.ib() - attrs, base_attrs, _ = _transform_attrs(C, None, False, True, True) + attrs, base_attrs, _ = _transform_attrs(C, None, False, True, False) assert len(attrs) == 3 assert len(base_attrs) == 1 @@ -263,7 +263,7 @@ class C(Base): y = attr.ib() attrs, base_attrs, _ = _transform_attrs( - C, {"x": attr.ib()}, False, False, True + C, {"x": attr.ib()}, False, False, False ) assert [] == base_attrs @@ -631,7 +631,7 @@ class C(object): y = attr.ib() with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False, True) + _transform_attrs(C, None, False, False, False) assert ( "Non keyword-only attributes are not allowed after a " @@ -649,7 +649,7 @@ def test_keyword_only_ordering_check_can_be_disabled(self): Setting the check flag to False, disables that check. """ - @attr.s(kw_only_order_check=False) + @attr.s(kw_only_anywhere=True) class C(object): y = attr.ib(kw_only=True) x = attr.ib() @@ -1412,7 +1412,7 @@ class C(object): is_exc=False, kw_only=False, cache_hash=False, - kw_only_order_check=True, + kw_only_anywhere=False, ) b._cls = {} # no __module__; no __qualname__ From ba89d813171c636f89d0a9ddf309313267a67429 Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Wed, 21 Aug 2019 02:32:09 -0300 Subject: [PATCH 08/10] Huge simplification: never check kw_only and non-init attributes order --- src/attr/_make.py | 57 ++++++++++++----------------------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index 0098e2095..13fc8bc17 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -285,7 +285,7 @@ def _counter_getter(e): return e[1].counter -def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_anywhere): +def _transform_attrs(cls, these, auto_attribs, kw_only): """ Transform all `_CountingAttr`s on a class into `Attribute`s. @@ -374,43 +374,24 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, kw_only_anywhere): attrs = AttrsClass(base_attrs + own_attrs) + # mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + attrs_to_check = [ + a for a in attrs if a.init is not False and a.kw_only is False + ] + had_default = False - was_kw_only = False - for a in attrs: - if ( - was_kw_only is False - and had_default is True - and a.default is NOTHING - and a.init is True - and a.kw_only is False - ): + for a in attrs_to_check: + if had_default is True and a.default is NOTHING: raise ValueError( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: %r" % (a,) ) - elif ( - had_default is False - and a.default is not NOTHING - and a.init is not False - and - # Keyword-only attributes without defaults can be specified - # after keyword-only attributes with defaults. - a.kw_only is False - ): + + if had_default is False and a.default is not NOTHING: had_default = True - if ( - kw_only_anywhere is False - and was_kw_only is True - and a.kw_only is False - and a.init is True - ): - raise ValueError( - "Non keyword-only attributes are not allowed after a " - "keyword-only attribute (unless they are init=False). " - "Attribute in question: {a!r}".format(a=a) - ) - if was_kw_only is False and a.init is True and a.kw_only is True: - was_kw_only = True return _Attributes((attrs, base_attrs, base_attr_map)) @@ -461,10 +442,9 @@ def __init__( kw_only, cache_hash, is_exc, - kw_only_anywhere, ): attrs, base_attrs, base_map = _transform_attrs( - cls, these, auto_attribs, kw_only, kw_only_anywhere + cls, these, auto_attribs, kw_only ) self._cls = cls @@ -743,7 +723,6 @@ def attrs( kw_only=False, cache_hash=False, auto_exc=False, - kw_only_anywhere=False, ): r""" A class decorator that adds `dunder @@ -866,12 +845,6 @@ def attrs( default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. - :param bool kw_only_anywhere: Allow or disallow the definition of - keyword-only attributes attributes after non keyword-only attributes. - - - when False (Default): don't allow non keyword-only attrs after - keyword-only attrs. - - when True: allow keyword-only attrs anywhere. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -892,7 +865,6 @@ def attrs( .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* - .. versionadded:: 19.2.0 *kw_only_anywhere* """ def wrap(cls): @@ -912,7 +884,6 @@ def wrap(cls): kw_only, cache_hash, is_exc, - kw_only_anywhere, ) if repr is True: From 8790ec3415c3f0e68c89453b3821c1b5b2705da9 Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Wed, 21 Aug 2019 02:32:35 -0300 Subject: [PATCH 09/10] No more new parameter, remove from typing info --- src/attr/__init__.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index 5d9b455b5..5ffa6dfe5 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -182,7 +182,6 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., - kw_only_anywhere: bool = ..., ) -> Callable[[_C], _C]: ... # TODO: add support for returning NamedTuple from the mypy plugin From c7a0259300823857635a6754d17eac8ee6bb6b0d Mon Sep 17 00:00:00 2001 From: Juan Pedro Fisanotti Date: Wed, 21 Aug 2019 02:33:01 -0300 Subject: [PATCH 10/10] Update tests after the simplification, and add new test --- tests/test_make.py | 78 ++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/tests/test_make.py b/tests/test_make.py index 7fe2aeaec..51476a53a 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -163,7 +163,7 @@ def test_no_modifications(self): Does not attach __attrs_attrs__ to the class. """ C = make_tc() - _transform_attrs(C, None, False, False, False) + _transform_attrs(C, None, False, False) assert None is getattr(C, "__attrs_attrs__", None) @@ -172,7 +172,7 @@ def test_normal(self): Transforms every `_CountingAttr` and leaves others (a) be. """ C = make_tc() - attrs, _, _ = _transform_attrs(C, None, False, False, False) + attrs, _, _ = _transform_attrs(C, None, False, False) assert ["z", "y", "x"] == [a.name for a in attrs] @@ -186,7 +186,7 @@ class C(object): pass assert _Attributes(((), [], {})) == _transform_attrs( - C, None, False, False, False + C, None, False, False ) def test_transforms_to_attribute(self): @@ -194,7 +194,7 @@ def test_transforms_to_attribute(self): All `_CountingAttr`s are transformed into `Attribute`s. """ C = make_tc() - attrs, base_attrs, _ = _transform_attrs(C, None, False, False, False) + attrs, base_attrs, _ = _transform_attrs(C, None, False, False) assert [] == base_attrs assert 3 == len(attrs) @@ -211,7 +211,7 @@ class C(object): y = attr.ib() with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False, False) + _transform_attrs(C, None, False, False) assert ( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: Attribute" @@ -240,7 +240,7 @@ class C(B): x = attr.ib(default=None) y = attr.ib() - attrs, base_attrs, _ = _transform_attrs(C, None, False, True, False) + attrs, base_attrs, _ = _transform_attrs(C, None, False, True) assert len(attrs) == 3 assert len(base_attrs) == 1 @@ -263,7 +263,7 @@ class C(Base): y = attr.ib() attrs, base_attrs, _ = _transform_attrs( - C, {"x": attr.ib()}, False, False, False + C, {"x": attr.ib()}, False, False ) assert [] == base_attrs @@ -620,44 +620,35 @@ class C(object): "missing 1 required keyword-only argument: 'x'" ) in e.value.args[0] - def test_conflicting_keyword_only_attributes(self): + def test_keyword_only_attributes_can_come_in_any_order(self): """ - Raises `ValueError` if keyword-only attributes are followed by - regular (non keyword-only) attributes. + Mandatory vs non-mandatory attr order only matters when they are part + of the __init__ signature and when they aren't kw_only (which are + moved to the end and can be mandatory or non-mandatory in any order, + as they will be specified as keyword args anyway). """ + @attr.s class C(object): - x = attr.ib(kw_only=True) - y = attr.ib() - - with pytest.raises(ValueError) as e: - _transform_attrs(C, None, False, False, False) - - assert ( - "Non keyword-only attributes are not allowed after a " - "keyword-only attribute (unless they are init=False). " - "Attribute in question: Attribute" - "(name='y', default=NOTHING, validator=None, repr=True, " - "cmp=True, hash=None, init=True, metadata=mappingproxy({}), " - "type=None, converter=None, kw_only=False)", - ) == e.value.args - - def test_keyword_only_ordering_check_can_be_disabled(self): - """ - When this check is enable, a `ValueError` is raised if keyword-only - attributes are followed by regular (non keyword-only) attributes. - Setting the check flag to False, disables that check. - """ - - @attr.s(kw_only_anywhere=True) - class C(object): - y = attr.ib(kw_only=True) - x = attr.ib() - - c = C(1, y=2) - - assert c.x == 1 - assert c.y == 2 + a = attr.ib(kw_only=True) + b = attr.ib(kw_only=True, default="b") + c = attr.ib(kw_only=True) + d = attr.ib() + e = attr.ib(default="e") + f = attr.ib(kw_only=True) + g = attr.ib(kw_only=True, default="g") + h = attr.ib(kw_only=True) + + c = C("d", a="a", c="c", f="f", h="h") + + assert c.a == "a" + assert c.b == "b" + assert c.c == "c" + assert c.d == "d" + assert c.e == "e" + assert c.f == "f" + assert c.g == "g" + assert c.h == "h" def test_keyword_only_attributes_allow_subclassing(self): """ @@ -1331,7 +1322,7 @@ class C(object): pass b = _ClassBuilder( - C, None, True, True, False, False, False, False, False, True + C, None, True, True, False, False, False, False, False ) assert "<_ClassBuilder(cls=C)>" == repr(b) @@ -1345,7 +1336,7 @@ class C(object): x = attr.ib() b = _ClassBuilder( - C, None, True, True, False, False, False, False, False, True + C, None, True, True, False, False, False, False, False ) cls = ( @@ -1412,7 +1403,6 @@ class C(object): is_exc=False, kw_only=False, cache_hash=False, - kw_only_anywhere=False, ) b._cls = {} # no __module__; no __qualname__