Skip to content

Commit

Permalink
Move functions to validators module
Browse files Browse the repository at this point in the history
  • Loading branch information
sscherfke committed Nov 4, 2021
1 parent d37469c commit 17fc66b
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 45 deletions.
10 changes: 8 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,6 @@ Validators can be globally disabled if you want to run them only in development

.. autofunction:: get_run_validators

.. autofunction:: no_run_validators


.. _api_validators:

Expand Down Expand Up @@ -592,6 +590,14 @@ Validators
...
TypeError: ("'x' must be <class 'str'> (got 7 that is a <class 'int'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'str'>, 7)

Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact:

.. autofunction:: attr.validators.set_disabled

.. autofunction:: attr.validators.get_disabled

.. autofunction:: attr.validators.disabled


Converters
----------
Expand Down
6 changes: 3 additions & 3 deletions docs/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,18 +242,18 @@ If you define validators both ways for an attribute, they are both ran:

And finally you can disable validators globally:

>>> attr.set_run_validators(False)
>>> attr.validators.set_disabled(True)
>>> C("128")
C(x='128')
>>> attr.set_run_validators(True)
>>> attr.validators.set_disabled(True)
>>> C("128")
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')

You can achieve the same by using the context manager:

>>> with attr.no_run_validators():
>>> with attr.validators.disabled():
... C("128")
C(x='128')
>>> C("128")
Expand Down
3 changes: 1 addition & 2 deletions src/attr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from . import converters, exceptions, filters, setters, validators
from ._cmp import cmp_using
from ._config import get_run_validators, no_run_validators, set_run_validators
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
from ._make import (
NOTHING,
Expand Down Expand Up @@ -64,7 +64,6 @@
"has",
"ib",
"make_class",
"no_run_validators",
"resolve_types",
"s",
"set_run_validators",
Expand Down
22 changes: 6 additions & 16 deletions src/attr/_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import absolute_import, division, print_function

from contextlib import contextmanager


__all__ = ["set_run_validators", "get_run_validators"]

Expand All @@ -11,6 +9,9 @@
def set_run_validators(run):
"""
Set whether or not validators are run. By default, they are run.
.. deprecated:: 21.3.0 will not be moved to new ``attrs`` namespace.
Use :func:`attr.validators.set_disabled()` instead.
"""
if not isinstance(run, bool):
raise TypeError("'run' must be bool.")
Expand All @@ -21,19 +22,8 @@ def set_run_validators(run):
def get_run_validators():
"""
Return whether or not validators are run.
"""
return _run_validators

@contextmanager
def no_run_validators():
"""
Context manager that disables running validators within its context.
.. versionadded:: 21.3.0
.. deprecated:: 21.3.0 will not be moved to new ``attrs`` namespace.
Use :func:`attr.validators.get_disabled()` instead.
"""
set_run_validators(False)
try:
yield
finally:
set_run_validators(True)
return _run_validators
56 changes: 56 additions & 0 deletions src/attr/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import operator
import re

from contextlib import contextmanager

from ._config import get_run_validators, set_run_validators
from ._make import _AndValidator, and_, attrib, attrs
from .exceptions import NotCallableError

Expand All @@ -15,7 +18,9 @@
"and_",
"deep_iterable",
"deep_mapping",
"disabled",
"ge",
"get_disabled",
"gt",
"in_",
"instance_of",
Expand All @@ -26,9 +31,60 @@
"max_len",
"optional",
"provides",
"set_disabled",
]


def set_disabled(disabled):
"""
Globally disable or enable running validators.
By default, they are run.
:param disabled: If ``True``, disable running all validators.
:type disabled: bool
.. warning::
This function is not thread-safe!
.. versionadded:: 21.3.0
"""
if not isinstance(disabled, bool):
raise TypeError("'disabled' must be bool.")
set_run_validators(not disabled)


def get_disabled():
"""
Return a bool indicating whether validators are currently disabled or not.
:return: ``True`` if validators are currently disabled.
:rtype: bool
.. versionadded:: 21.3.0
"""
return not get_run_validators()


@contextmanager
def disabled():
"""
Context manager that disables running validators within its context.
.. warning::
This context manager is not thread-safe!
.. versionadded:: 21.3.0
"""
set_run_validators(False)
try:
yield
finally:
set_run_validators(True)


@attrs(repr=False, slots=True, hash=True)
class _InstanceOfValidator(object):
type = attrib()
Expand Down
21 changes: 0 additions & 21 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,3 @@ def test_wrong_type(self):
with pytest.raises(TypeError) as e:
_config.set_run_validators("False")
assert "'run' must be bool." == e.value.args[0]

def test_no_run_validators(self):
"""
The `no_run_validators` context manager disables running validators,
but only within its context.
"""
assert _config._run_validators is True
with _config.no_run_validators():
assert _config._run_validators is False
assert _config._run_validators is True

def test_no_run_validators_with_errors(self):
"""
Running validators is re-enabled even if an error is raised.
"""
assert _config._run_validators is True
with pytest.raises(ValueError):
with _config.no_run_validators():
assert _config._run_validators is False
raise ValueError("haha!")
assert _config._run_validators is True
62 changes: 61 additions & 1 deletion tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import attr

from attr import fields, has
from attr import _config, fields, has
from attr import validators as validator_module
from attr._compat import PY2, TYPE
from attr.validators import (
Expand Down Expand Up @@ -46,6 +46,66 @@ def zope_interface():
return zope.interface


class TestDisableValidators(object):
@pytest.fixture(autouse=True)
def reset_default(self):
"""
Make sure, validators are always enabled after a test!
"""
yield
_config._run_validators = True

def test_default(self):
"""
Run validators by default.
"""
assert _config._run_validators is True

@pytest.mark.parametrize("value, expected", [(True, False), (False, True)])
def test_set_validators_diabled(self, value, expected):
"""
Sets `_run_validators`.
"""
validator_module.set_disabled(value)
assert _config._run_validators is expected

@pytest.mark.parametrize("value, expected", [(True, False), (False, True)])
def test_get_run_validators(self, value, expected):
"""
Returns `_run_validators`.
"""
_config._run_validators = value
assert validator_module.get_disabled() is expected

def test_wrong_type(self):
"""
Passing anything else than a boolean raises TypeError.
"""
with pytest.raises(TypeError, match="'disabled' must be bool."):
validator_module.set_disabled("False")

def test_no_run_validators(self):
"""
The `no_run_validators` context manager disables running validators,
but only within its context.
"""
assert _config._run_validators is True
with validator_module.disabled():
assert _config._run_validators is False
assert _config._run_validators is True

def test_no_run_validators_with_errors(self):
"""
Running validators is re-enabled even if an error is raised.
"""
assert _config._run_validators is True
with pytest.raises(ValueError):
with validator_module.disabled():
assert _config._run_validators is False
raise ValueError("haha!")
assert _config._run_validators is True


class TestInstanceOf(object):
"""
Tests for `instance_of`.
Expand Down

0 comments on commit 17fc66b

Please sign in to comment.