Skip to content

Commit 6ef5e8b

Browse files
committed
Improve error message for mandatory attributes after defaults
When a mandatory attribute appears after an attribute with a default value or factory, the error message now includes which attribute has the default, making it easier to identify the problem. Fixes #437
1 parent a602f78 commit 6ef5e8b

3 files changed

Lines changed: 15 additions & 3 deletions

File tree

‎src/attr/_make.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,13 +483,15 @@ def _transform_attrs(
483483
# the end and can be mandatory or non-mandatory in any order, as they will
484484
# be specified as keyword args anyway). Check the order of those attrs:
485485
had_default = False
486+
which_had_default = None
486487
for a in (a for a in attrs if a.init is not False and a.kw_only is False):
487488
if had_default is True and a.default is NOTHING:
488-
msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
489+
msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r} appears after {which_had_default!r}"
489490
raise ValueError(msg)
490491

491492
if had_default is False and a.default is not NOTHING:
492493
had_default = True
494+
which_had_default = a
493495

494496
# Resolve default field alias for any new attributes that the
495497
# field_transformer may have added without setting an alias.

‎tests/test_hooks.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,12 @@ class C:
179179
"eq=True, eq_key=None, order=True, order_key=None, "
180180
"hash=None, init=True, "
181181
"metadata=mappingproxy({'field_order': 1}), type='int', converter=None, "
182-
"kw_only=False, inherited=False, on_setattr=None, alias='x')"
182+
"kw_only=False, inherited=False, on_setattr=None, alias='x') "
183+
"appears after Attribute(name='y', default=0, validator=None, "
184+
"repr=True, eq=True, eq_key=None, order=True, order_key=None, "
185+
"hash=None, init=True, metadata=mappingproxy({'field_order': 0}), "
186+
"type='int', converter=None, kw_only=False, inherited=False, "
187+
"on_setattr=None, alias='y')"
183188
),
184189
) == e.value.args
185190

‎tests/test_make.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,12 @@ class C:
288288
"eq=True, eq_key=None, order=True, order_key=None, "
289289
"hash=None, init=True, "
290290
"metadata=mappingproxy({}), type=None, converter=None, "
291-
"kw_only=False, inherited=False, on_setattr=None, alias='y')"
291+
"kw_only=False, inherited=False, on_setattr=None, alias='y') "
292+
"appears after Attribute(name='x', default=None, validator=None, "
293+
"repr=True, eq=True, eq_key=None, order=True, order_key=None, "
294+
"hash=None, init=True, metadata=mappingproxy({}), type=None, "
295+
"converter=None, kw_only=False, inherited=False, on_setattr=None, "
296+
"alias='x')"
292297
),
293298
) == e.value.args
294299

0 commit comments

Comments
 (0)