Search code examples
pythonpython-attrs

Set defaults for attrs subclass that uses validators from parent class?


Consider this minimal example:

from attrs import define, field, validators

@define(kw_only=True)
class Vehicle:
    num_wheels: int = field(validator=validators.instance_of(int))

    @num_wheels.validator
    def validate_num_wheels(self, attribute, value) -> None:
        if value <= 0:
            raise ValueError("Number of wheels must be greater than 0")

How can I create subclasses of this parent class which set their own default values of num_wheels but still run the validators defined in the parent class? For example:

@define
class Car(Vehicle):
    # num_wheels: int = 4  #! this doesn't work
    pass


@define
class Motorbike(Vehicle):
    # num_wheels: int = 2
    pass

I want to protect against Car(num_wheels=3.14) or Car(num_wheels=-1). But also want Car() or Motorbike() to initialise with default values.

I've seen this answer which might be relevant - https://stackoverflow.com/a/67129088/11432894. It suggests this might be a solution?

@define
class Car(Vehicle):
    def __init__(self, *, num_wheels: int = 4, **kwargs):
        self.__attrs_init__(num_wheels=num_wheels, **kwargs)

Solution

  • It seems like at the core you would like to re-use field definitions which is unfortunately an open issue: https://github.com/python-attrs/attrs/issues/637

    As mentioned in one of the comments, in this case the number of wheels should probably be part of the class – not an attribute. I guess you could also make your Vehicle class "abstract" and write a validator that reads the attribute from the class (i.e. it would check self.num_wheels with num_wheels being a ClassVar).