attrs icon indicating copy to clipboard operation
attrs copied to clipboard

Create validators not tied to attributes

Open shashankchaudhry opened this issue 5 years ago • 5 comments

Currently if I need to add a validator to check a condition that affects two attributes, I have to make it a validator of one of them. For example:

@attr.s
class C(object):
     x = attr.ib()
     y = attr.ib()

     @x.validator
     def x_smaller_than_y(self, _ , value):
          if value >= self.y:
          raise ValueError("'x' has to be smaller than 'y'!")

This feels a little unintuitive because this validator is not really a validator on just 'x'. It should be a validator on 'x' and 'y'. It made me wonder if the order of writing x and y in the class matters, in case the validator is run right after an attribute is set.

I wonder what folks think about a generic validator of this form:

@attr.s
class C(object):
     x = attr.ib()
     y = attr.ib()

     @validator
     def x_smaller_than_y(self):
          if self.x >= self.y:
          raise ValueError("'x' has to be smaller than 'y'!")

shashankchaudhry avatar May 22 '20 05:05 shashankchaudhry

This seems like something for __attrs_post_init__ and not a validator?

hynek avatar May 24 '20 09:05 hynek

This may be something to think about in the context of the __setattr__ conversation; presumably this validation should happen any time x or y are assigned to. In which case, it should be separate from __attrs_post_init__.

wsanchez avatar May 25 '20 16:05 wsanchez

@wsanchez : What is the __setattr__ conversation?

@hynek : __attrs_post_init__ to me feels like a place to mutate the attrs after initialization, rather than a place to validate them. Since you explicitly have validators, and it is pretty common to want to do multi-attr validation, it seems more natural if you could natively support that. An alternate approach I can think of is some syntax like:

@attr.s
class C(object):
     x = attr.ib()
     y = attr.ib()

     @multi_validator(['x', 'y'])
     def x_smaller_than_y(self, x, y):
          if x >= y:
              raise ValueError("'x' has to be smaller than 'y'!")

shashankchaudhry avatar May 28 '20 18:05 shashankchaudhry

@shashankchaudhry Sorry I should have included a pointer. See #645.

Since we are considering validation when attributes are assigned to, then even a validator not specific to a given attribute would probably need to be triggered when you set either any or relevant-to-the-validator attributes. So a solution that only work in the context of the init cycle is probably going to be unsatisfactory.

wsanchez avatar May 28 '20 18:05 wsanchez

I’m starting to remember that at some point we had a “validate the whole instance” conversation but it got turned down?

hynek avatar Jun 07 '20 07:06 hynek