attrs icon indicating copy to clipboard operation
attrs copied to clipboard

"No mandatory attributes after..." error message

Open aleaverfay opened this issue 6 years ago • 2 comments

I was getting the

No mandatory attributes allowed after an attribute with a default value or factory.

error message related to issues #38 and #93. I had been stumped as to what the order of attributes was for my class and which ones had and which lacked defaults/factory methods. It would be super useful if the exception printed more about the offending attributes. In my case, I wanted to know:

"What is the first attribute that has a default that appears ahead of an attribute which lacks one?"

right now, the error message only tells me what attribute lacks a default.

E.g., I edited _make.py around line 331 (as of version 17.4) to this:

had_default = False
which_had_default = None
for a in attrs:
    if had_default is True and a.default is NOTHING and a.init is True:
        raise ValueError(
            "No mandatory attributes allowed after an attribute with a "
            "default value or factory.  Attribute in question: {a!r} appears after {before!r}"
            .format(a=a, before=which_had_default)
        )
    elif had_default is False and \
            a.default is not NOTHING and \
            a.init is not False:
        had_default = True
        which_had_default = a

(Printing only this message was not quite sufficient for my pretty funky case, and so I ended up printing out the full ordered list of attributes to see which were appearing out of reverse-MRO order and causing my problem. It might be even more helpful to print out the full attrs list in the even of an error.)

aleaverfay avatar Aug 27 '18 21:08 aleaverfay

It might also be good to better document / message that kw_only can be used to address this use case:

# kw default in parent
@attr.s(auto_attribs=True, hash=True, collect_by_mro=True)
class A:
    a: int = attr.field(default=1, kw_only=True)

@attr.s(auto_attribs=True, hash=True, collect_by_mro=True)
class B(A):
    b: int

B(1)
# kw required in subclass
@attr.s(auto_attribs=True, hash=True, collect_by_mro=True)
class A:
    a: int = 1

@attr.s(auto_attribs=True, hash=True, collect_by_mro=True)
class B(A):
    b: int = attr.field(kw_only=True)

B(b=1)

micimize avatar Nov 15 '22 19:11 micimize

Just leaving a note about this - it's a little more explicit now, like @aleaverfay had suggested. My error message (post stack trace):

ValueError: No mandatory attributes allowed after an attribute with a default value or factory.  Attribute in question: Attribute(name='input', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=typing.Tuple[pathlib.Path, pathlib.Path], converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)

four43 avatar Jul 19 '23 13:07 four43