attrs
attrs copied to clipboard
Provide a builtin validator that takes a boolean function.
Currently, validators are required to raise an exception, if the value isn't appropriate. If I currently have a function for checking the property, but returns a boolean, instead of raising, to use it as a validator, I have to write a wrapper that converts the return to an exception. It would be nice if attrs provided such a wrapper itself.
+1, this would be a nice utility!
A basic implementation:
from __future__ import annotations
from typing import TYPE_CHECKING, Callable, TypeVar
import attr
_S = TypeVar("_S")
_T = TypeVar("_T")
if TYPE_CHECKING:
# TODO: why? attr.Attribute subclasses Generic[_T]...
# TypeError: 'type' object is not subscriptable
PredicateFunc = Callable[[_S, attr.Attribute[_T], _T], bool]
ValidatorFunc = Callable[[_S, attr.Attribute[_T], _T], None]
def as_validator(predicate: PredicateFunc[_S, _T]) -> ValidatorFunc[_S, _T]:
"""Convert a predicate to an Attrs validator function.
A "predicate" is a function (or other callable) that takes an
`attr.Attribute` and the value of that attribute, and returns a boolean.
"""
def validator(instance: _S, attribute: attr.Attribute[_T], value: _T) -> None:
if not predicate(instance, attribute, value):
raise ValueError(f"Invalid value for {instance.__class__.__name__}.{attribute.name}: {value!r}")
return validator
if __name__ == "__main__":
# Examples following https://www.attrs.org/en/stable/init.html#validators:
@attr.s()
class A(object):
x: int = attr.ib(validator=as_validator(lambda inst, _, val: val < inst.y))
y: int = attr.ib()
A(3, 5)
try:
A(5, 3)
except ValueError as exc:
print(exc)
@attr.s()
class B(object):
x: int = attr.ib()
y: int = attr.ib()
@x.validator
@as_validator
def _check_x(self, attribute, value):
return value < self.y
B(3, 5)
try:
B(5, 3)
except ValueError as exc:
print(exc)
If someone would like to cook up a PR, the chances are good for it to be merged!
instead of requiring a bool return value, i think it makes more ‘pythonic’ sense if this were to use bool() to see if the result is ‘truthy’?
this would make much more sense for various use cases:
Nonevs actual object, e.g. regular expression match functions return a typing.Match object orNonestr.split()or other list-producing functions can be used for ‘empty’ vs ‘non-empty’ checks
example usage based on the proof of concept code above:
RE_XYZ = re.compile(...)
@attr.define
class C:
a: ... = attr.field(validator=as_validator(lambda _, _, val: RE_XYZ.match(value)))
That's a great idea @wbolster. My example implementation above already uses general "truthiness" with the if not ... check, but the types could be updated to something like this:
class SupportsBool(Protocol):
def __bool__(self) -> bool: ...
_S = TypeVar("_S")
_T = TypeVar("_T")
if TYPE_CHECKING:
# TODO: why? attr.Attribute subclasses Generic[_T]...
# TypeError: 'type' object is not subscriptable
PredicateFunc = Callable[[_S, attr.Attribute[_T], _T], SupportsBool]
ValidatorFunc = Callable[[_S, attr.Attribute[_T], _T], None]
If anyone has suggestions of what the test cases should be, I will try to make the time for a PR.
i think an Any type (usually not the best choice) would do just fine here:
object.__bool__(self)Called to implement truth value testing and the built-in operation bool(); should return False or True. When this method is not defined,__len__()is called, if it is defined, and the object is considered true if its result is nonzero. If a class defines neither__len__()nor__bool__(), all its instances are considered true. – https://docs.python.org/3/reference/datamodel.html#object.bool
in other words: bool(obj) will do the right thing on any object, the right thing being the same thing as what if obj: ... would do
If someone would like to cook up a PR, the chances are good for it to be merged!
My draft pr is ready. i will raise in with next 4-5 days.Just adding some test cases.
What's the status of this issue/PR?
Reading through the comments I'd have thought a validators.is_true and validators.is_false would be more consistent with existing validator names?
I'm afraid the status is that the draft didn't make it to GitHub. 😅