From 6ef5e8bd99233aea4f63bb451c72e642ccc22462 Mon Sep 17 00:00:00 2001 From: Mason Barden Date: Sat, 3 Oct 2026 14:17:00 -0400 Subject: [PATCH] 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 --- src/attr/_make.py | 4 +++- tests/test_hooks.py | 7 ++++++- tests/test_make.py | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index e5b3760c1..631ca9c1f 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -483,13 +483,15 @@ def _transform_attrs( # 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: had_default = False + which_had_default = None for a in (a for a in attrs if a.init is not False and a.kw_only is False): if had_default is True and a.default is NOTHING: - msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" + 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}" raise ValueError(msg) if had_default is False and a.default is not NOTHING: had_default = True + which_had_default = a # Resolve default field alias for any new attributes that the # field_transformer may have added without setting an alias. diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 81f131c48..5c00c280e 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -179,7 +179,12 @@ class C: "eq=True, eq_key=None, order=True, order_key=None, " "hash=None, init=True, " "metadata=mappingproxy({'field_order': 1}), type='int', converter=None, " - "kw_only=False, inherited=False, on_setattr=None, alias='x')" + "kw_only=False, inherited=False, on_setattr=None, alias='x') " + "appears after Attribute(name='y', default=0, validator=None, " + "repr=True, eq=True, eq_key=None, order=True, order_key=None, " + "hash=None, init=True, metadata=mappingproxy({'field_order': 0}), " + "type='int', converter=None, kw_only=False, inherited=False, " + "on_setattr=None, alias='y')" ), ) == e.value.args diff --git a/tests/test_make.py b/tests/test_make.py index b32f1054e..bedd0812e 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -288,7 +288,12 @@ class C: "eq=True, eq_key=None, order=True, order_key=None, " "hash=None, init=True, " "metadata=mappingproxy({}), type=None, converter=None, " - "kw_only=False, inherited=False, on_setattr=None, alias='y')" + "kw_only=False, inherited=False, on_setattr=None, alias='y') " + "appears after Attribute(name='x', default=None, validator=None, " + "repr=True, eq=True, eq_key=None, order=True, order_key=None, " + "hash=None, init=True, metadata=mappingproxy({}), type=None, " + "converter=None, kw_only=False, inherited=False, on_setattr=None, " + "alias='x')" ), ) == e.value.args