Ability to have `kw_only` attributes defined anywhere, as an optional feature (#559)
* Ability to disable the keyword-only order checks that forbid normal attrs after kw_only attrs * Updated typing info * Updated tests, and added a new test for the new ordering feature * Pep8 * Updated docstring * Rename to a name that reflects the usage, not the implementation * Update tests after the rename * Huge simplification: never check kw_only and non-init attributes order * No more new parameter, remove from typing info * Update tests after the simplification, and add new test
This commit is contained in:
parent
55f71b9ec3
commit
94ee269438
|
@ -374,38 +374,24 @@ def _transform_attrs(cls, these, auto_attribs, kw_only):
|
|||
|
||||
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 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))
|
||||
|
||||
|
|
|
@ -620,27 +620,35 @@ class TestKeywordOnlyAttributes(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()
|
||||
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)
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
_transform_attrs(C, None, False, False)
|
||||
c = C("d", a="a", c="c", f="f", h="h")
|
||||
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue