diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/python/pytest/py3/_pytest/python_api.py | |
parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) | |
download | ydb-e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/python_api.py')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/python_api.py | 628 |
1 files changed, 314 insertions, 314 deletions
diff --git a/contrib/python/pytest/py3/_pytest/python_api.py b/contrib/python/pytest/py3/_pytest/python_api.py index 9cbf5584e8..81ce4f8953 100644 --- a/contrib/python/pytest/py3/_pytest/python_api.py +++ b/contrib/python/pytest/py3/_pytest/python_api.py @@ -1,36 +1,36 @@ import math import pprint -from collections.abc import Iterable -from collections.abc import Mapping -from collections.abc import Sized +from collections.abc import Iterable +from collections.abc import Mapping +from collections.abc import Sized from decimal import Decimal -from numbers import Complex -from types import TracebackType -from typing import Any -from typing import Callable -from typing import cast -from typing import Generic -from typing import Optional -from typing import overload -from typing import Pattern -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union - -if TYPE_CHECKING: - from numpy import ndarray - - +from numbers import Complex +from types import TracebackType +from typing import Any +from typing import Callable +from typing import cast +from typing import Generic +from typing import Optional +from typing import overload +from typing import Pattern +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +if TYPE_CHECKING: + from numpy import ndarray + + import _pytest._code -from _pytest.compat import final +from _pytest.compat import final from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail -def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: - at_str = f" at {at}" if at else "" +def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: + at_str = f" at {at}" if at else "" return TypeError( "cannot make approximate comparisons to non-numeric values: {!r} {}".format( value, at_str @@ -41,15 +41,15 @@ def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: # builtin pytest.approx helper -class ApproxBase: - """Provide shared utilities for making approximate comparisons between - numbers or sequences of numbers.""" +class ApproxBase: + """Provide shared utilities for making approximate comparisons between + numbers or sequences of numbers.""" # Tell numpy to use our `__eq__` operator instead of its. __array_ufunc__ = None __array_priority__ = 100 - def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: + def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: __tracebackhide__ = True self.expected = expected self.abs = abs @@ -57,32 +57,32 @@ class ApproxBase: self.nan_ok = nan_ok self._check_type() - def __repr__(self) -> str: + def __repr__(self) -> str: raise NotImplementedError - def __eq__(self, actual) -> bool: + def __eq__(self, actual) -> bool: return all( a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) ) - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore - def __ne__(self, actual) -> bool: + def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> "ApproxScalar": + def _approx_scalar(self, x) -> "ApproxScalar": return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) def _yield_comparisons(self, actual): - """Yield all the pairs of numbers to be compared. - - This is used to implement the `__eq__` method. + """Yield all the pairs of numbers to be compared. + + This is used to implement the `__eq__` method. """ raise NotImplementedError - def _check_type(self) -> None: - """Raise a TypeError if the expected value is not a valid type.""" + def _check_type(self) -> None: + """Raise a TypeError if the expected value is not a valid type.""" # This is only a concern if the expected value is a sequence. In every # other case, the approx() function ensures that the expected value has # a numeric type. For this reason, the default is to do nothing. The @@ -99,22 +99,22 @@ def _recursive_list_map(f, x): class ApproxNumpy(ApproxBase): - """Perform approximate comparisons where the expected value is numpy array.""" + """Perform approximate comparisons where the expected value is numpy array.""" - def __repr__(self) -> str: + def __repr__(self) -> str: list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) - return f"approx({list_scalars!r})" + return f"approx({list_scalars!r})" - def __eq__(self, actual) -> bool: + def __eq__(self, actual) -> bool: import numpy as np - # self.expected is supposed to always be an array here. + # self.expected is supposed to always be an array here. if not np.isscalar(actual): try: actual = np.asarray(actual) - except Exception as e: - raise TypeError(f"cannot compare '{actual}' to numpy.ndarray") from e + except Exception as e: + raise TypeError(f"cannot compare '{actual}' to numpy.ndarray") from e if not np.isscalar(actual) and actual.shape != self.expected.shape: return False @@ -130,26 +130,26 @@ class ApproxNumpy(ApproxBase): if np.isscalar(actual): for i in np.ndindex(self.expected.shape): - yield actual, self.expected[i].item() + yield actual, self.expected[i].item() else: for i in np.ndindex(self.expected.shape): - yield actual[i].item(), self.expected[i].item() + yield actual[i].item(), self.expected[i].item() class ApproxMapping(ApproxBase): - """Perform approximate comparisons where the expected value is a mapping - with numeric values (the keys can be anything).""" + """Perform approximate comparisons where the expected value is a mapping + with numeric values (the keys can be anything).""" - def __repr__(self) -> str: + def __repr__(self) -> str: return "approx({!r})".format( {k: self._approx_scalar(v) for k, v in self.expected.items()} ) - def __eq__(self, actual) -> bool: - try: - if set(actual.keys()) != set(self.expected.keys()): - return False - except AttributeError: + def __eq__(self, actual) -> bool: + try: + if set(actual.keys()) != set(self.expected.keys()): + return False + except AttributeError: return False return ApproxBase.__eq__(self, actual) @@ -158,7 +158,7 @@ class ApproxMapping(ApproxBase): for k in self.expected.keys(): yield actual[k], self.expected[k] - def _check_type(self) -> None: + def _check_type(self) -> None: __tracebackhide__ = True for key, value in self.expected.items(): if isinstance(value, type(self.expected)): @@ -166,10 +166,10 @@ class ApproxMapping(ApproxBase): raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) -class ApproxSequencelike(ApproxBase): - """Perform approximate comparisons where the expected value is a sequence of numbers.""" +class ApproxSequencelike(ApproxBase): + """Perform approximate comparisons where the expected value is a sequence of numbers.""" - def __repr__(self) -> str: + def __repr__(self) -> str: seq_type = type(self.expected) if seq_type not in (tuple, list, set): seq_type = list @@ -177,18 +177,18 @@ class ApproxSequencelike(ApproxBase): seq_type(self._approx_scalar(x) for x in self.expected) ) - def __eq__(self, actual) -> bool: - try: - if len(actual) != len(self.expected): - return False - except TypeError: + def __eq__(self, actual) -> bool: + try: + if len(actual) != len(self.expected): + return False + except TypeError: return False return ApproxBase.__eq__(self, actual) def _yield_comparisons(self, actual): return zip(actual, self.expected) - def _check_type(self) -> None: + def _check_type(self) -> None: __tracebackhide__ = True for index, x in enumerate(self.expected): if isinstance(x, type(self.expected)): @@ -197,70 +197,70 @@ class ApproxSequencelike(ApproxBase): class ApproxScalar(ApproxBase): - """Perform approximate comparisons where the expected value is a single number.""" - - # Using Real should be better than this Union, but not possible yet: - # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 - - def __repr__(self) -> str: - """Return a string communicating both the expected value and the - tolerance for the comparison being made. - - For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. + """Perform approximate comparisons where the expected value is a single number.""" + + # Using Real should be better than this Union, but not possible yet: + # https://github.com/python/typeshed/pull/3108 + DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 + + def __repr__(self) -> str: + """Return a string communicating both the expected value and the + tolerance for the comparison being made. + + For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. """ - # Don't show a tolerance for values that aren't compared using - # tolerances, i.e. non-numerics and infinities. Need to call abs to - # handle complex numbers, e.g. (inf + 1j). - if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( - abs(self.expected) # type: ignore[arg-type] - ): + # Don't show a tolerance for values that aren't compared using + # tolerances, i.e. non-numerics and infinities. Need to call abs to + # handle complex numbers, e.g. (inf + 1j). + if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( + abs(self.expected) # type: ignore[arg-type] + ): return str(self.expected) # If a sensible tolerance can't be calculated, self.tolerance will # raise a ValueError. In this case, display '???'. try: - vetted_tolerance = f"{self.tolerance:.1e}" - if ( - isinstance(self.expected, Complex) - and self.expected.imag - and not math.isinf(self.tolerance) - ): - vetted_tolerance += " ∠ ±180°" + vetted_tolerance = f"{self.tolerance:.1e}" + if ( + isinstance(self.expected, Complex) + and self.expected.imag + and not math.isinf(self.tolerance) + ): + vetted_tolerance += " ∠ ±180°" except ValueError: vetted_tolerance = "???" - return f"{self.expected} ± {vetted_tolerance}" + return f"{self.expected} ± {vetted_tolerance}" - def __eq__(self, actual) -> bool: - """Return whether the given value is equal to the expected value - within the pre-specified tolerance.""" - asarray = _as_numpy_array(actual) - if asarray is not None: + def __eq__(self, actual) -> bool: + """Return whether the given value is equal to the expected value + within the pre-specified tolerance.""" + asarray = _as_numpy_array(actual) + if asarray is not None: # Call ``__eq__()`` manually to prevent infinite-recursion with # numpy<1.13. See #3748. - return all(self.__eq__(a) for a in asarray.flat) + return all(self.__eq__(a) for a in asarray.flat) # Short-circuit exact equality. if actual == self.expected: return True - # If either type is non-numeric, fall back to strict equality. - # NB: we need Complex, rather than just Number, to ensure that __abs__, - # __sub__, and __float__ are defined. - if not ( - isinstance(self.expected, (Complex, Decimal)) - and isinstance(actual, (Complex, Decimal)) - ): - return False - + # If either type is non-numeric, fall back to strict equality. + # NB: we need Complex, rather than just Number, to ensure that __abs__, + # __sub__, and __float__ are defined. + if not ( + isinstance(self.expected, (Complex, Decimal)) + and isinstance(actual, (Complex, Decimal)) + ): + return False + # Allow the user to control whether NaNs are considered equal to each # other or not. The abs() calls are for compatibility with complex # numbers. - if math.isnan(abs(self.expected)): # type: ignore[arg-type] - return self.nan_ok and math.isnan(abs(actual)) # type: ignore[arg-type] + if math.isnan(abs(self.expected)): # type: ignore[arg-type] + return self.nan_ok and math.isnan(abs(actual)) # type: ignore[arg-type] # Infinity shouldn't be approximately equal to anything but itself, but # if there's a relative tolerance, it will be infinite and infinity @@ -268,22 +268,22 @@ class ApproxScalar(ApproxBase): # case would have been short circuited above, so here we can just # return false if the expected value is infinite. The abs() call is # for compatibility with complex numbers. - if math.isinf(abs(self.expected)): # type: ignore[arg-type] + if math.isinf(abs(self.expected)): # type: ignore[arg-type] return False # Return true if the two numbers are within the tolerance. - result: bool = abs(self.expected - actual) <= self.tolerance - return result + result: bool = abs(self.expected - actual) <= self.tolerance + return result - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore @property def tolerance(self): - """Return the tolerance for the comparison. - - This could be either an absolute tolerance or a relative tolerance, - depending on what the user specified or which would be larger. + """Return the tolerance for the comparison. + + This could be either an absolute tolerance or a relative tolerance, + depending on what the user specified or which would be larger. """ def set_default(x, default): @@ -295,7 +295,7 @@ class ApproxScalar(ApproxBase): if absolute_tolerance < 0: raise ValueError( - f"absolute tolerance can't be negative: {absolute_tolerance}" + f"absolute tolerance can't be negative: {absolute_tolerance}" ) if math.isnan(absolute_tolerance): raise ValueError("absolute tolerance can't be NaN.") @@ -317,7 +317,7 @@ class ApproxScalar(ApproxBase): if relative_tolerance < 0: raise ValueError( - f"relative tolerance can't be negative: {absolute_tolerance}" + f"relative tolerance can't be negative: {absolute_tolerance}" ) if math.isnan(relative_tolerance): raise ValueError("relative tolerance can't be NaN.") @@ -327,14 +327,14 @@ class ApproxScalar(ApproxBase): class ApproxDecimal(ApproxScalar): - """Perform approximate comparisons where the expected value is a Decimal.""" + """Perform approximate comparisons where the expected value is a Decimal.""" DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") -def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: - """Assert that two numbers (or two sets of numbers) are equal to each other +def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: + """Assert that two numbers (or two sets of numbers) are equal to each other within some tolerance. Due to the `intricacies of floating-point arithmetic`__, numbers that we @@ -426,18 +426,18 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) True - You can also use ``approx`` to compare nonnumeric types, or dicts and - sequences containing nonnumeric types, in which case it falls back to - strict equality. This can be useful for comparing dicts and sequences that - can contain optional values:: - - >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None}) - True - >>> [None, 1.0000005] == approx([None,1]) - True - >>> ["foo", 1.0000005] == approx([None,1]) - False - + You can also use ``approx`` to compare nonnumeric types, or dicts and + sequences containing nonnumeric types, in which case it falls back to + strict equality. This can be useful for comparing dicts and sequences that + can contain optional values:: + + >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None}) + True + >>> [None, 1.0000005] == approx([None,1]) + True + >>> ["foo", 1.0000005] == approx([None,1]) + False + If you're thinking about using ``approx``, then you might want to know how it compares to other good ways of comparing floating-point numbers. All of these algorithms are based on relative and absolute tolerances and should @@ -449,7 +449,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor ``b`` is a "reference value"). You have to specify an absolute tolerance if you want to compare to ``0.0`` because there is no tolerance by - default. `More information...`__ + default. `More information...`__ __ https://docs.python.org/3/library/math.html#math.isclose @@ -460,7 +460,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: think of ``b`` as the reference value. Support for comparing sequences is provided by ``numpy.allclose``. `More information...`__ - __ https://numpy.org/doc/stable/reference/generated/numpy.isclose.html + __ https://numpy.org/doc/stable/reference/generated/numpy.isclose.html - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b`` are within an absolute tolerance of ``1e-7``. No relative tolerance is @@ -495,14 +495,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: follows a fixed behavior. `More information...`__ __ https://docs.python.org/3/reference/datamodel.html#object.__ge__ - - .. versionchanged:: 3.7.1 - ``approx`` raises ``TypeError`` when it encounters a dict value or - sequence element of nonnumeric type. - - .. versionchanged:: 6.1.0 - ``approx`` falls back to strict equality for nonnumeric types instead - of raising ``TypeError``. + + .. versionchanged:: 3.7.1 + ``approx`` raises ``TypeError`` when it encounters a dict value or + sequence element of nonnumeric type. + + .. versionchanged:: 6.1.0 + ``approx`` falls back to strict equality for nonnumeric types instead + of raising ``TypeError``. """ # Delegate the comparison to a class that knows how to deal with the type @@ -523,125 +523,125 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: Type[ApproxBase] = ApproxDecimal + cls: Type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): - expected = _as_numpy_array(expected) + expected = _as_numpy_array(expected) cls = ApproxNumpy - elif ( - isinstance(expected, Iterable) - and isinstance(expected, Sized) - # Type ignored because the error is wrong -- not unreachable. - and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] - ): - cls = ApproxSequencelike + elif ( + isinstance(expected, Iterable) + and isinstance(expected, Sized) + # Type ignored because the error is wrong -- not unreachable. + and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] + ): + cls = ApproxSequencelike else: - cls = ApproxScalar + cls = ApproxScalar return cls(expected, rel, abs, nan_ok) -def _is_numpy_array(obj: object) -> bool: +def _is_numpy_array(obj: object) -> bool: """ - Return true if the given object is implicitly convertible to ndarray, - and numpy is already imported. + Return true if the given object is implicitly convertible to ndarray, + and numpy is already imported. + """ + return _as_numpy_array(obj) is not None + + +def _as_numpy_array(obj: object) -> Optional["ndarray"]: + """ + Return an ndarray if the given object is implicitly convertible to ndarray, + and numpy is already imported, otherwise None. """ - return _as_numpy_array(obj) is not None - - -def _as_numpy_array(obj: object) -> Optional["ndarray"]: - """ - Return an ndarray if the given object is implicitly convertible to ndarray, - and numpy is already imported, otherwise None. - """ import sys - np: Any = sys.modules.get("numpy") + np: Any = sys.modules.get("numpy") if np is not None: - # avoid infinite recursion on numpy scalars, which have __array__ - if np.isscalar(obj): - return None - elif isinstance(obj, np.ndarray): - return obj - elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"): - return np.asarray(obj) - return None + # avoid infinite recursion on numpy scalars, which have __array__ + if np.isscalar(obj): + return None + elif isinstance(obj, np.ndarray): + return obj + elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"): + return np.asarray(obj) + return None # builtin pytest.raises helper -_E = TypeVar("_E", bound=BaseException) - - -@overload -def raises( - expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], - *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[_E]": - ... - - -@overload -def raises( - expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], - func: Callable[..., Any], - *args: Any, - **kwargs: Any, -) -> _pytest._code.ExceptionInfo[_E]: - ... - - -def raises( - expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], *args: Any, **kwargs: Any -) -> Union["RaisesContext[_E]", _pytest._code.ExceptionInfo[_E]]: - r"""Assert that a code block/function call raises ``expected_exception`` - or raise a failure exception otherwise. - - :kwparam match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception using ``re.search``. To match a literal - string that may contain `special characters`__, the pattern can - first be escaped with ``re.escape``. - - (This is only used when ``pytest.raises`` is used as a context manager, - and passed through to the function otherwise. - When using ``pytest.raises`` as a function, you can use: - ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - - __ https://docs.python.org/3/library/re.html#regular-expression-syntax - - .. currentmodule:: _pytest._code - - Use ``pytest.raises`` as a context manager, which will capture the exception of the given - type:: - - >>> import pytest - >>> with pytest.raises(ZeroDivisionError): +_E = TypeVar("_E", bound=BaseException) + + +@overload +def raises( + expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], + *, + match: Optional[Union[str, Pattern[str]]] = ..., +) -> "RaisesContext[_E]": + ... + + +@overload +def raises( + expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], + func: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> _pytest._code.ExceptionInfo[_E]: + ... + + +def raises( + expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], *args: Any, **kwargs: Any +) -> Union["RaisesContext[_E]", _pytest._code.ExceptionInfo[_E]]: + r"""Assert that a code block/function call raises ``expected_exception`` + or raise a failure exception otherwise. + + :kwparam match: + If specified, a string containing a regular expression, + or a regular expression object, that is tested against the string + representation of the exception using ``re.search``. To match a literal + string that may contain `special characters`__, the pattern can + first be escaped with ``re.escape``. + + (This is only used when ``pytest.raises`` is used as a context manager, + and passed through to the function otherwise. + When using ``pytest.raises`` as a function, you can use: + ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) + + __ https://docs.python.org/3/library/re.html#regular-expression-syntax + + .. currentmodule:: _pytest._code + + Use ``pytest.raises`` as a context manager, which will capture the exception of the given + type:: + + >>> import pytest + >>> with pytest.raises(ZeroDivisionError): ... 1/0 - If the code block does not raise the expected exception (``ZeroDivisionError`` in the example - above), or no exception at all, the check will fail instead. - - You can also use the keyword argument ``match`` to assert that the - exception matches a text or regex:: - - >>> with pytest.raises(ValueError, match='must be 0 or None'): - ... raise ValueError("value must be 0 or None") - - >>> with pytest.raises(ValueError, match=r'must be \d+$'): - ... raise ValueError("value must be 42") - - The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the - details of the captured exception:: - - >>> with pytest.raises(ValueError) as exc_info: - ... raise ValueError("value must be 42") - >>> assert exc_info.type is ValueError - >>> assert exc_info.value.args[0] == "value must be 42" - + If the code block does not raise the expected exception (``ZeroDivisionError`` in the example + above), or no exception at all, the check will fail instead. + + You can also use the keyword argument ``match`` to assert that the + exception matches a text or regex:: + + >>> with pytest.raises(ValueError, match='must be 0 or None'): + ... raise ValueError("value must be 0 or None") + + >>> with pytest.raises(ValueError, match=r'must be \d+$'): + ... raise ValueError("value must be 42") + + The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the + details of the captured exception:: + + >>> with pytest.raises(ValueError) as exc_info: + ... raise ValueError("value must be 42") + >>> assert exc_info.type is ValueError + >>> assert exc_info.value.args[0] == "value must be 42" + .. note:: When using ``pytest.raises`` as a context manager, it's worthwhile to @@ -651,29 +651,29 @@ def raises( not be executed. For example:: >>> value = 15 - >>> with pytest.raises(ValueError) as exc_info: + >>> with pytest.raises(ValueError) as exc_info: ... if value > 10: ... raise ValueError("value must be <= 10") - ... assert exc_info.type is ValueError # this will not execute + ... assert exc_info.type is ValueError # this will not execute Instead, the following approach must be taken (note the difference in scope):: - >>> with pytest.raises(ValueError) as exc_info: + >>> with pytest.raises(ValueError) as exc_info: ... if value > 10: ... raise ValueError("value must be <= 10") ... - >>> assert exc_info.type is ValueError + >>> assert exc_info.type is ValueError - **Using with** ``pytest.mark.parametrize`` + **Using with** ``pytest.mark.parametrize`` - When using :ref:`pytest.mark.parametrize ref` - it is possible to parametrize tests such that - some runs raise an exception and others do not. + When using :ref:`pytest.mark.parametrize ref` + it is possible to parametrize tests such that + some runs raise an exception and others do not. - See :ref:`parametrizing_conditional_raising` for an example. + See :ref:`parametrizing_conditional_raising` for an example. - **Legacy form** + **Legacy form** It is possible to specify a callable by passing a to-be-called lambda:: @@ -689,8 +689,8 @@ def raises( >>> raises(ZeroDivisionError, f, x=0) <ExceptionInfo ...> - The form above is fully supported but discouraged for new code because the - context manager form is regarded as more readable and less error-prone. + The form above is fully supported but discouraged for new code because the + context manager form is regarded as more readable and less error-prone. .. note:: Similar to caught exception objects in Python, explicitly clearing @@ -702,85 +702,85 @@ def raises( the exception --> current frame stack --> local variables --> ``ExceptionInfo``) which makes Python keep all objects referenced from that cycle (including all local variables in the current - frame) alive until the next cyclic garbage collection run. - More detailed information can be found in the official Python - documentation for :ref:`the try statement <python:try>`. + frame) alive until the next cyclic garbage collection run. + More detailed information can be found in the official Python + documentation for :ref:`the try statement <python:try>`. """ __tracebackhide__ = True - if isinstance(expected_exception, type): - excepted_exceptions: Tuple[Type[_E], ...] = (expected_exception,) - else: - excepted_exceptions = expected_exception - for exc in excepted_exceptions: - if not isinstance(exc, type) or not issubclass(exc, BaseException): # type: ignore[unreachable] - msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] - not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ - raise TypeError(msg.format(not_a)) - - message = f"DID NOT RAISE {expected_exception}" - + if isinstance(expected_exception, type): + excepted_exceptions: Tuple[Type[_E], ...] = (expected_exception,) + else: + excepted_exceptions = expected_exception + for exc in excepted_exceptions: + if not isinstance(exc, type) or not issubclass(exc, BaseException): # type: ignore[unreachable] + msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] + not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ + raise TypeError(msg.format(not_a)) + + message = f"DID NOT RAISE {expected_exception}" + if not args: - match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) + match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " - msg += ", ".join(sorted(kwargs)) - msg += "\nUse context-manager form instead?" + msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" raise TypeError(msg) - return RaisesContext(expected_exception, message, match) + return RaisesContext(expected_exception, message, match) else: func = args[0] - if not callable(func): - raise TypeError( - "{!r} object (type: {}) must be callable".format(func, type(func)) - ) + if not callable(func): + raise TypeError( + "{!r} object (type: {}) must be callable".format(func, type(func)) + ) try: func(*args[1:], **kwargs) - except expected_exception as e: - # We just caught the exception - there is a traceback. - assert e.__traceback__ is not None - return _pytest._code.ExceptionInfo.from_exc_info( - (type(e), e, e.__traceback__) - ) + except expected_exception as e: + # We just caught the exception - there is a traceback. + assert e.__traceback__ is not None + return _pytest._code.ExceptionInfo.from_exc_info( + (type(e), e, e.__traceback__) + ) fail(message) -# This doesn't work with mypy for now. Use fail.Exception instead. -raises.Exception = fail.Exception # type: ignore +# This doesn't work with mypy for now. Use fail.Exception instead. +raises.Exception = fail.Exception # type: ignore -@final -class RaisesContext(Generic[_E]): - def __init__( - self, - expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], - message: str, - match_expr: Optional[Union[str, Pattern[str]]] = None, - ) -> None: +@final +class RaisesContext(Generic[_E]): + def __init__( + self, + expected_exception: Union[Type[_E], Tuple[Type[_E], ...]], + message: str, + match_expr: Optional[Union[str, Pattern[str]]] = None, + ) -> None: self.expected_exception = expected_exception self.message = message self.match_expr = match_expr - self.excinfo: Optional[_pytest._code.ExceptionInfo[_E]] = None + self.excinfo: Optional[_pytest._code.ExceptionInfo[_E]] = None - def __enter__(self) -> _pytest._code.ExceptionInfo[_E]: - self.excinfo = _pytest._code.ExceptionInfo.for_later() + def __enter__(self) -> _pytest._code.ExceptionInfo[_E]: + self.excinfo = _pytest._code.ExceptionInfo.for_later() return self.excinfo - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> bool: + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: __tracebackhide__ = True - if exc_type is None: + if exc_type is None: fail(self.message) - assert self.excinfo is not None - if not issubclass(exc_type, self.expected_exception): - return False - # Cast to narrow the exception type now that it's verified. - exc_info = cast(Tuple[Type[_E], _E, TracebackType], (exc_type, exc_val, exc_tb)) - self.excinfo.fill_unfilled(exc_info) - if self.match_expr is not None: + assert self.excinfo is not None + if not issubclass(exc_type, self.expected_exception): + return False + # Cast to narrow the exception type now that it's verified. + exc_info = cast(Tuple[Type[_E], _E, TracebackType], (exc_type, exc_val, exc_tb)) + self.excinfo.fill_unfilled(exc_info) + if self.match_expr is not None: self.excinfo.match(self.match_expr) - return True + return True |