450 kwonly before init false (#459)
* Allow init=False attributes to follow kw_only attributes Closes #450 * Fix changelog typo * Update reference text for exception message * Remove type annotations from tests * Fix long docstring lines * Add test with literal default
This commit is contained in:
parent
7bfd0e4061
commit
e114561a06
|
@ -0,0 +1 @@
|
|||
Allow `init=False` arguments after `kw_only` arguments.
|
|
@ -409,12 +409,11 @@ 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:
|
||||
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. Attribute in question: {a!r}".format(
|
||||
a=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
|
||||
|
|
|
@ -705,7 +705,8 @@ class TestKeywordOnlyAttributes(object):
|
|||
|
||||
assert (
|
||||
"Non keyword-only attributes are not allowed after a "
|
||||
"keyword-only attribute. Attribute in question: Attribute"
|
||||
"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)",
|
||||
|
@ -771,6 +772,62 @@ class TestKeywordOnlyAttributes(object):
|
|||
assert c.x == 0
|
||||
assert c.y == 1
|
||||
|
||||
def test_init_false_attribute_after_keyword_attribute(self):
|
||||
"""
|
||||
A positional attribute cannot follow a `kw_only` attribute,
|
||||
but an `init=False` attribute can because it won't appear
|
||||
in `__init__`
|
||||
"""
|
||||
|
||||
@attr.s
|
||||
class KwArgBeforeInitFalse:
|
||||
kwarg = attr.ib(kw_only=True)
|
||||
non_init_function_default = attr.ib(init=False)
|
||||
non_init_keyword_default = attr.ib(
|
||||
init=False, default="default-by-keyword"
|
||||
)
|
||||
|
||||
@non_init_function_default.default
|
||||
def _init_to_init(self):
|
||||
return self.kwarg + "b"
|
||||
|
||||
c = KwArgBeforeInitFalse(kwarg="a")
|
||||
|
||||
assert c.kwarg == "a"
|
||||
assert c.non_init_function_default == "ab"
|
||||
assert c.non_init_keyword_default == "default-by-keyword"
|
||||
|
||||
def test_init_false_attribute_after_keyword_attribute_with_inheritance(
|
||||
self
|
||||
):
|
||||
"""
|
||||
A positional attribute cannot follow a `kw_only` attribute,
|
||||
but an `init=False` attribute can because it won't appear
|
||||
in `__init__`. This test checks that we allow this
|
||||
even when the `kw_only` attribute appears in a parent class
|
||||
"""
|
||||
|
||||
@attr.s
|
||||
class KwArgBeforeInitFalseParent:
|
||||
kwarg = attr.ib(kw_only=True)
|
||||
|
||||
@attr.s
|
||||
class KwArgBeforeInitFalseChild(KwArgBeforeInitFalseParent):
|
||||
non_init_function_default = attr.ib(init=False)
|
||||
non_init_keyword_default = attr.ib(
|
||||
init=False, default="default-by-keyword"
|
||||
)
|
||||
|
||||
@non_init_function_default.default
|
||||
def _init_to_init(self):
|
||||
return self.kwarg + "b"
|
||||
|
||||
c = KwArgBeforeInitFalseChild(kwarg="a")
|
||||
|
||||
assert c.kwarg == "a"
|
||||
assert c.non_init_function_default == "ab"
|
||||
assert c.non_init_keyword_default == "default-by-keyword"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not PY2, reason="PY2-specific keyword-only error behavior")
|
||||
class TestKeywordOnlyAttributesOnPy2(object):
|
||||
|
|
Loading…
Reference in New Issue