Skip to content

Commit

Permalink
Merge branch 'main' into attrib_alias
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Jun 24, 2022
2 parents 6d4e1c1 + a7e82b5 commit 40a5161
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 25 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Expand Up @@ -9,7 +9,7 @@ repos:
- id: black

- repo: https://github.com/asottile/pyupgrade
rev: v2.31.1
rev: v2.32.1
hooks:
- id: pyupgrade
args: [--py3-plus, --keep-percent-format]
Expand Down Expand Up @@ -37,7 +37,7 @@ repos:
language_version: python3.10 # needed for match

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down
1 change: 1 addition & 0 deletions changelog.d/951.change.rst
@@ -0,0 +1 @@
``attrs.validators._in()``'s ``ValueError`` is not missing the attribute, expected options, and the value it got anymore.
38 changes: 19 additions & 19 deletions docs/api.rst
Expand Up @@ -9,7 +9,7 @@ If you're confused by the many names, please check out `names` for clarification

What follows is the API explanation, if you'd like a more hands-on introduction, have a look at `examples`.

As of version 21.3.0, ``attrs`` consists of **two** to-level package names:
As of version 21.3.0, ``attrs`` consists of **two** top-level package names:

- The classic ``attr`` that powered the venerable `attr.s` and `attr.ib`
- The modern ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes.
Expand Down Expand Up @@ -566,24 +566,24 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`

.. doctest::

>>> import enum
>>> class State(enum.Enum):
... ON = "on"
... OFF = "off"
>>> @attrs.define
... class C:
... state = attrs.field(validator=attrs.validators.in_(State))
... val = attrs.field(validator=attrs.validators.in_([1, 2, 3]))
>>> C(State.ON, 1)
C(state=<State.ON: 'on'>, val=1)
>>> C("on", 1)
Traceback (most recent call last):
...
ValueError: 'state' must be in <enum 'State'> (got 'on')
>>> C(State.ON, 4)
Traceback (most recent call last):
...
ValueError: 'val' must be in [1, 2, 3] (got 4)
>>> import enum
>>> class State(enum.Enum):
... ON = "on"
... OFF = "off"
>>> @attrs.define
... class C:
... state = attrs.field(validator=attrs.validators.in_(State))
... val = attrs.field(validator=attrs.validators.in_([1, 2, 3]))
>>> C(State.ON, 1)
C(state=<State.ON: 'on'>, val=1)
>>> C("on", 1)
Traceback (most recent call last):
...
ValueError: 'state' must be in <enum 'State'> (got 'on'), Attribute(name='state', default=NOTHING, validator=<in_ validator with options <enum 'State'>>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), <enum 'State'>, 'on')
>>> C(State.ON, 4)
Traceback (most recent call last):
...
ValueError: 'val' must be in [1, 2, 3] (got 4), Attribute(name='val', default=NOTHING, validator=<in_ validator with options [1, 2, 3]>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), [1, 2, 3], 4)

.. autofunction:: attrs.validators.provides

Expand Down
4 changes: 3 additions & 1 deletion docs/examples.rst
Expand Up @@ -329,6 +329,8 @@ The method receives the partially initialized instance which enables you to base
>>> C()
C(x=1, y=2, z=[])

Please keep in mind that the decorator approach *only* works if the attribute in question has a ``field`` assigned to it.
As a result, annotating an attribute with a type is *not* enough if you use ``@default``.

.. _examples_validators:

Expand Down Expand Up @@ -401,7 +403,7 @@ You can use a decorator:
ValueError: value out of bounds

Please note that the decorator approach only works if -- and only if! -- the attribute in question has a ``field`` assigned.
Therefore if you use ``@default``, it is *not* enough to annotate said attribute with a type.
Therefore if you use ``@validator``, it is *not* enough to annotate said attribute with a type.

``attrs`` ships with a bunch of validators, make sure to `check them out <api_validators>` before writing your own:

Expand Down
3 changes: 3 additions & 0 deletions docs/init.rst
Expand Up @@ -147,6 +147,8 @@ The method has to accept three arguments:
#. the *attribute* that it's validating, and finally
#. the *value* that is passed for it.

These values are passed as *positional arguments*, therefore their names don't matter.

If the value does not pass the validator's standards, it just raises an appropriate exception.

>>> @define
Expand All @@ -172,6 +174,7 @@ Callables
If you want to re-use your validators, you should have a look at the ``validator`` argument to `attrs.field`.

It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
Also as with the decorator approach, they are passed as *positional arguments* so you can name them however you want.

Since the validators run *after* the instance is initialized, you can refer to other attributes while validating:

Expand Down
9 changes: 8 additions & 1 deletion src/attr/validators.py
Expand Up @@ -299,7 +299,10 @@ def __call__(self, inst, attr, value):
raise ValueError(
"'{name}' must be in {options!r} (got {value!r})".format(
name=attr.name, options=self.options, value=value
)
),
attr,
self.options,
value,
)

def __repr__(self):
Expand All @@ -322,6 +325,10 @@ def in_(options):
got.
.. versionadded:: 17.1.0
.. versionchanged:: 22.1.0
The ValueError was incomplete until now and only contained the human
readable error message. Now it contains all the information that has
been promised since 17.1.0.
"""
return _InValidator(options)

Expand Down
16 changes: 14 additions & 2 deletions tests/test_validators.py
Expand Up @@ -469,9 +469,16 @@ def test_fail(self):
"""
v = in_([1, 2, 3])
a = simple_attr("test")

with pytest.raises(ValueError) as e:
v(None, a, None)
assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args

assert (
"'test' must be in [1, 2, 3] (got None)",
a,
[1, 2, 3],
None,
) == e.value.args

def test_fail_with_string(self):
"""
Expand All @@ -482,7 +489,12 @@ def test_fail_with_string(self):
a = simple_attr("test")
with pytest.raises(ValueError) as e:
v(None, a, None)
assert ("'test' must be in 'abc' (got None)",) == e.value.args
assert (
"'test' must be in 'abc' (got None)",
a,
"abc",
None,
) == e.value.args

def test_repr(self):
"""
Expand Down

0 comments on commit 40a5161

Please sign in to comment.