Add attr.valid

This commit is contained in:
Hynek Schlawack 2015-02-02 12:13:11 +01:00
parent f7597c795f
commit 2cab907370
5 changed files with 71 additions and 0 deletions

View File

@ -7,6 +7,7 @@ from ._funcs import (
assoc,
fields,
has,
valid,
)
from ._make import (
Attribute,
@ -40,5 +41,6 @@ __all__ = [
"ib",
"make_class",
"s",
"valid",
"validators",
]

View File

@ -92,3 +92,15 @@ def assoc(inst, **changes):
)
setattr(new, k, v)
return new
def valid(inst):
"""
Validate all attributes on *inst* that have a validator.
Leaves all exceptions through.
:param inst: Instance of a class with ``attrs`` attributes.
"""
for a in fields(inst.__class__):
a.validator(a, getattr(inst, a.name))

View File

@ -150,6 +150,23 @@ Helpers
False
.. autofunction:: valid
For example:
.. doctest::
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> i = C(1)
>>> i.x = "1"
>>> attr.valid(i)
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '1' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, no_repr=False, no_cmp=False, no_hash=False, no_init=False), <type 'int'>, '1')
.. _api_validators:
Validators

View File

@ -120,6 +120,17 @@ If the value does not pass the validator's standards, it just raises an appropri
...
ValueError: 'x' has to be smaller than 5!
``attrs`` won't intercept your changes to those attributes but you can always call :func:``attr.valid`` on any instance to verify, that it's still valid:
.. doctest::
>>> i = C(4)
>>> i.x = 5 # works, no magic here
>>> attr.valid(i)
Traceback (most recent call last):
...
ValueError: 'x' has to be smaller than 5!
``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
.. doctest::

View File

@ -13,11 +13,13 @@ from attr._funcs import (
assoc,
fields,
has,
valid,
)
from attr._make import (
Attribute,
attr,
attributes,
make_class,
)
@ -167,3 +169,30 @@ class TestAssoc(object):
assert (
"y is not an attrs attribute on {cl!r}.".format(cl=C),
) == e.value.args
class TestValid(object):
"""
Tests for `valid`.
"""
def test_success(self):
"""
If the validator suceeds, nothing gets raised.
"""
C = make_class("C", {"x": attr(validator=lambda _, __: None)})
valid(C(1))
def test_propagates(self):
"""
The exception of the validator is handed through.
"""
def raiser(_, value):
if value == 42:
raise FloatingPointError
C = make_class("C", {"x": attr(validator=raiser)})
i = C(1)
i.x = 42
with pytest.raises(FloatingPointError):
valid(i)