Skip to content

Commit

Permalink
Allow attrs kw_only arguments at any position (#8803)
Browse files Browse the repository at this point in the history
Fixes #7489.
  • Loading branch information
markusschmaus authored May 19, 2020
1 parent 8748e69 commit aaf9828
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 35 deletions.
20 changes: 12 additions & 8 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,13 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext',
# Check the init args for correct default-ness. Note: This has to be done after all the
# attributes for all classes have been read, because subclasses can override parents.
last_default = False
last_kw_only = False

for i, attribute in enumerate(attributes):
if not attribute.init:
continue

if attribute.kw_only:
# Keyword-only attributes don't care whether they are default or not.
last_kw_only = True
continue

# If the issue comes from merging different classes, report it
Expand All @@ -377,11 +375,6 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext',
ctx.api.fail(
"Non-default attributes not allowed after default attributes.",
context)
if last_kw_only:
ctx.api.fail(
"Non keyword-only attributes are not allowed after a keyword-only attribute.",
context
)
last_default |= attribute.has_default

return attributes
Expand Down Expand Up @@ -626,7 +619,18 @@ def _make_frozen(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute]
def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute],
adder: 'MethodAdder') -> None:
"""Generate an __init__ method for the attributes and add it to the class."""
args = [attribute.argument(ctx) for attribute in attributes if attribute.init]
# Convert attributes to arguments with kw_only arguments at the end of
# the argument list
pos_args = []
kw_only_args = []
for attribute in attributes:
if not attribute.init:
continue
if attribute.kw_only:
kw_only_args.append(attribute.argument(ctx))
else:
pos_args.append(attribute.argument(ctx))
args = pos_args + kw_only_args
if all(
# We use getattr rather than instance checks because the variable.type
# might be wrapped into a Union or some other type, but even non-Any
Expand Down
5 changes: 3 additions & 2 deletions test-data/unit/check-attr.test
Original file line number Diff line number Diff line change
Expand Up @@ -1070,11 +1070,12 @@ class A:
a = attr.ib(default=0)
@attr.s
class B(A):
b = attr.ib() # E: Non keyword-only attributes are not allowed after a keyword-only attribute.
b = attr.ib()
@attr.s
class C:
a = attr.ib(kw_only=True)
b = attr.ib(15) # E: Non keyword-only attributes are not allowed after a keyword-only attribute.
b = attr.ib(15)

[builtins fixtures/attr.pyi]

[case testAttrsKwOnlyPy2]
Expand Down
25 changes: 0 additions & 25 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -1023,31 +1023,6 @@ class A:
==
main:2: error: Unsupported left operand type for < ("B")

[case testAttrsUpdateKwOnly]
[file a.py]
import attr
@attr.s(kw_only=True)
class A:
a = attr.ib(15) # type: int
[file b.py]
from a import A
import attr
@attr.s(kw_only=True)
class B(A):
b = attr.ib("16") # type: str

[file b.py.2]
from a import A
import attr
@attr.s()
class B(A):
b = attr.ib("16") # type: str
B(b="foo", a=7)
[builtins fixtures/attr.pyi]
[out]
==
b.py:5: error: Non keyword-only attributes are not allowed after a keyword-only attribute.

[case testAttrsUpdateBaseKwOnly]
from b import B
B(5)
Expand Down

0 comments on commit aaf9828

Please sign in to comment.