Skip to content

Commit 40a5161

Browse files
authored
Merge branch 'main' into attrib_alias
2 parents 6d4e1c1 + a7e82b5 commit 40a5161

7 files changed

Lines changed: 50 additions & 25 deletions

File tree

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: black
1010

1111
- repo: https://github.com/asottile/pyupgrade
12-
rev: v2.31.1
12+
rev: v2.32.1
1313
hooks:
1414
- id: pyupgrade
1515
args: [--py3-plus, --keep-percent-format]
@@ -37,7 +37,7 @@ repos:
3737
language_version: python3.10 # needed for match
3838

3939
- repo: https://github.com/pre-commit/pre-commit-hooks
40-
rev: v4.1.0
40+
rev: v4.2.0
4141
hooks:
4242
- id: trailing-whitespace
4343
- id: end-of-file-fixer

changelog.d/951.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``attrs.validators._in()``'s ``ValueError`` is not missing the attribute, expected options, and the value it got anymore.

docs/api.rst

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ If you're confused by the many names, please check out `names` for clarification
99

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

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

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

567567
.. doctest::
568568

569-
>>> import enum
570-
>>> class State(enum.Enum):
571-
... ON = "on"
572-
... OFF = "off"
573-
>>> @attrs.define
574-
... class C:
575-
... state = attrs.field(validator=attrs.validators.in_(State))
576-
... val = attrs.field(validator=attrs.validators.in_([1, 2, 3]))
577-
>>> C(State.ON, 1)
578-
C(state=<State.ON: 'on'>, val=1)
579-
>>> C("on", 1)
580-
Traceback (most recent call last):
581-
...
582-
ValueError: 'state' must be in <enum 'State'> (got 'on')
583-
>>> C(State.ON, 4)
584-
Traceback (most recent call last):
585-
...
586-
ValueError: 'val' must be in [1, 2, 3] (got 4)
569+
>>> import enum
570+
>>> class State(enum.Enum):
571+
... ON = "on"
572+
... OFF = "off"
573+
>>> @attrs.define
574+
... class C:
575+
... state = attrs.field(validator=attrs.validators.in_(State))
576+
... val = attrs.field(validator=attrs.validators.in_([1, 2, 3]))
577+
>>> C(State.ON, 1)
578+
C(state=<State.ON: 'on'>, val=1)
579+
>>> C("on", 1)
580+
Traceback (most recent call last):
581+
...
582+
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')
583+
>>> C(State.ON, 4)
584+
Traceback (most recent call last):
585+
...
586+
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)
587587

588588
.. autofunction:: attrs.validators.provides
589589

docs/examples.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ The method receives the partially initialized instance which enables you to base
329329
>>> C()
330330
C(x=1, y=2, z=[])
331331

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

333335
.. _examples_validators:
334336

@@ -401,7 +403,7 @@ You can use a decorator:
401403
ValueError: value out of bounds
402404

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

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

docs/init.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ The method has to accept three arguments:
147147
#. the *attribute* that it's validating, and finally
148148
#. the *value* that is passed for it.
149149

150+
These values are passed as *positional arguments*, therefore their names don't matter.
151+
150152
If the value does not pass the validator's standards, it just raises an appropriate exception.
151153

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

174176
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.
177+
Also as with the decorator approach, they are passed as *positional arguments* so you can name them however you want.
175178

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

src/attr/validators.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,10 @@ def __call__(self, inst, attr, value):
299299
raise ValueError(
300300
"'{name}' must be in {options!r} (got {value!r})".format(
301301
name=attr.name, options=self.options, value=value
302-
)
302+
),
303+
attr,
304+
self.options,
305+
value,
303306
)
304307

305308
def __repr__(self):
@@ -322,6 +325,10 @@ def in_(options):
322325
got.
323326
324327
.. versionadded:: 17.1.0
328+
.. versionchanged:: 22.1.0
329+
The ValueError was incomplete until now and only contained the human
330+
readable error message. Now it contains all the information that has
331+
been promised since 17.1.0.
325332
"""
326333
return _InValidator(options)
327334

tests/test_validators.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,9 +469,16 @@ def test_fail(self):
469469
"""
470470
v = in_([1, 2, 3])
471471
a = simple_attr("test")
472+
472473
with pytest.raises(ValueError) as e:
473474
v(None, a, None)
474-
assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args
475+
476+
assert (
477+
"'test' must be in [1, 2, 3] (got None)",
478+
a,
479+
[1, 2, 3],
480+
None,
481+
) == e.value.args
475482

476483
def test_fail_with_string(self):
477484
"""
@@ -482,7 +489,12 @@ def test_fail_with_string(self):
482489
a = simple_attr("test")
483490
with pytest.raises(ValueError) as e:
484491
v(None, a, None)
485-
assert ("'test' must be in 'abc' (got None)",) == e.value.args
492+
assert (
493+
"'test' must be in 'abc' (got None)",
494+
a,
495+
"abc",
496+
None,
497+
) == e.value.args
486498

487499
def test_repr(self):
488500
"""

0 commit comments

Comments
 (0)