From ee009465bdf123ee464137440afffb3e8f26b6aa Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:26:12 +0200
Subject: [PATCH 1/5] undo deprecation
---
.coveragerc | 2 +-
doc/en/conf.py | 4 +
doc/en/how-to/assert.rst | 5 +-
doc/en/how-to/capture-warnings.rst | 7 --
src/_pytest/python_api.py | 3 +
src/_pytest/raises.py | 14 +--
src/_pytest/recwarn.py | 36 +++---
testing/_py/test_local.py | 15 ++-
testing/code/test_code.py | 4 +-
testing/code/test_excinfo.py | 114 ++++++++++++------
testing/code/test_source.py | 9 +-
.../sub2/conftest.py | 3 +-
testing/python/collect.py | 12 +-
testing/python/raises.py | 22 ++--
testing/test_cacheprovider.py | 6 +-
testing/test_capture.py | 59 +++++----
testing/test_config.py | 15 ++-
testing/test_debugging.py | 5 +-
testing/test_legacypath.py | 3 +-
testing/test_monkeypatch.py | 12 +-
testing/test_parseopt.py | 3 +-
testing/test_pluginmanager.py | 23 ++--
testing/test_pytester.py | 6 +-
testing/test_recwarn.py | 53 ++++----
testing/test_runner.py | 6 +-
testing/test_session.py | 3 +-
26 files changed, 276 insertions(+), 168 deletions(-)
diff --git a/.coveragerc b/.coveragerc
index d39d3d5f02b..4e13efa8066 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -27,7 +27,7 @@ exclude_lines =
^\s*assert False(,|$)
^\s*assert_never\(
- ^\s*if TYPE_CHECKING:
+ ^\s*(el)?if TYPE_CHECKING:
^\s*@overload( |$)
^\s*def .+: \.\.\.$
diff --git a/doc/en/conf.py b/doc/en/conf.py
index c89e14d07fa..9deee5230bd 100644
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -97,6 +97,10 @@
# TypeVars
("py:class", "_pytest._code.code.E"),
("py:class", "E"), # due to delayed annotation
+ ("py:class", "T"),
+ ("py:class", "P"),
+ ("py:class", "P.args"),
+ ("py:class", "P.kwargs"),
("py:class", "_pytest.fixtures.FixtureFunction"),
("py:class", "_pytest.nodes._NodeType"),
("py:class", "_NodeType"), # due to delayed annotation
diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index 6bc8f6fed33..8f8208b77ee 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -282,6 +282,10 @@ exception at a specific level; exceptions contained directly in the top
Alternate `pytest.raises` form (legacy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. warning::
+ This will be deprecated and removed in a future release.
+
+
There is an alternate form of :func:`pytest.raises` where you pass
a function that will be executed, along with ``*args`` and ``**kwargs``. :func:`pytest.raises`
will then execute the function with those arguments and assert that the given exception is raised:
@@ -301,7 +305,6 @@ exception* or *wrong exception*.
This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
being considered more readable.
-Nonetheless, this form is fully supported and not deprecated in any way.
xfail mark and pytest.raises
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst
index a9bd894b6fd..efbc4ac49ec 100644
--- a/doc/en/how-to/capture-warnings.rst
+++ b/doc/en/how-to/capture-warnings.rst
@@ -338,13 +338,6 @@ Some examples:
... warnings.warn("issue with foo() func")
...
-You can also call :func:`pytest.warns` on a function or code string:
-
-.. code-block:: python
-
- pytest.warns(expected_warning, func, *args, **kwargs)
- pytest.warns(expected_warning, "func(*args, **kwargs)")
-
The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information:
diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py
index 07794abea95..026c2ea851f 100644
--- a/src/_pytest/python_api.py
+++ b/src/_pytest/python_api.py
@@ -16,6 +16,9 @@
if TYPE_CHECKING:
from numpy import ndarray
+ from typing_extensions import ParamSpec
+
+ P = ParamSpec("P")
def _compare_approx(
diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py
index 480ae33647f..fd9b77a15af 100644
--- a/src/_pytest/raises.py
+++ b/src/_pytest/raises.py
@@ -95,14 +95,15 @@ def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException
@overload
def raises(
expected_exception: type[E] | tuple[type[E], ...],
- func: Callable[..., Any],
- *args: Any,
- **kwargs: Any,
+ func: Callable[P, object],
+ *args: P.args,
+ **kwargs: P.kwargs,
) -> ExceptionInfo[E]: ...
def raises(
expected_exception: type[E] | tuple[type[E], ...] | None = None,
+ func: Callable[P, object] | None = None,
*args: Any,
**kwargs: Any,
) -> RaisesExc[BaseException] | ExceptionInfo[E]:
@@ -253,7 +254,7 @@ def raises(
>>> raises(ZeroDivisionError, f, x=0)
- The form above is fully supported but discouraged for new code because the
+ The form above is going to be deprecated in a future pytest release as the
context manager form is regarded as more readable and less error-prone.
.. note::
@@ -272,7 +273,7 @@ def raises(
"""
__tracebackhide__ = True
- if not args:
+ if func is None and not args:
if set(kwargs) - {"match", "check", "expected_exception"}:
msg = "Unexpected keyword arguments passed to pytest.raises: "
msg += ", ".join(sorted(kwargs))
@@ -289,11 +290,10 @@ def raises(
f"Raising exceptions is already understood as failing the test, so you don't need "
f"any special code to say 'this should never raise an exception'."
)
- func = args[0]
if not callable(func):
raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
with RaisesExc(expected_exception) as excinfo:
- func(*args[1:], **kwargs)
+ func(*args, **kwargs)
try:
return excinfo
finally:
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index e3db717bfe4..804ff6399d0 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -17,8 +17,11 @@
if TYPE_CHECKING:
+ from typing_extensions import ParamSpec
from typing_extensions import Self
+ P = ParamSpec("P")
+
import warnings
from _pytest.deprecated import check_ispytest
@@ -49,7 +52,7 @@ def deprecated_call(
@overload
-def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ...
+def deprecated_call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ...
def deprecated_call(
@@ -67,6 +70,8 @@ def deprecated_call(
>>> import pytest
>>> with pytest.deprecated_call():
... assert api_call_v2() == 200
+ >>> with pytest.deprecated_call(match="^use v3 of this api$") as warning_messages:
+ ... assert api_call_v2() == 200
It can also be used by passing a function and ``*args`` and ``**kwargs``,
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of
@@ -76,14 +81,17 @@ def deprecated_call(
that the warning matches a text or regex.
The context manager produces a list of :class:`warnings.WarningMessage` objects,
- one for each warning raised.
+ one for each warning emitted
+ (regardless of whether it is an ``expected_warning`` or not).
"""
__tracebackhide__ = True
- if func is not None:
- args = (func, *args)
- return warns(
- (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs
- )
+ # Potential QoL: allow `with deprecated_call:` - i.e. no parens
+ dep_warnings = (DeprecationWarning, PendingDeprecationWarning, FutureWarning)
+ if func is None:
+ return warns(dep_warnings, *args, **kwargs)
+
+ with warns(dep_warnings):
+ return func(*args, **kwargs)
@overload
@@ -97,16 +105,16 @@ def warns(
@overload
def warns(
expected_warning: type[Warning] | tuple[type[Warning], ...],
- func: Callable[..., T],
- *args: Any,
- **kwargs: Any,
+ func: Callable[P, T],
+ *args: P.args,
+ **kwargs: P.kwargs,
) -> T: ...
def warns(
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
+ func: Callable[..., object] | None = None,
*args: Any,
- match: str | re.Pattern[str] | None = None,
**kwargs: Any,
) -> WarningsChecker | Any:
r"""Assert that code raises a particular class of warning.
@@ -151,7 +159,8 @@ def warns(
"""
__tracebackhide__ = True
- if not args:
+ if func is None and not args:
+ match: str | re.Pattern[str] | None = kwargs.pop("match", None)
if kwargs:
argnames = ", ".join(sorted(kwargs))
raise TypeError(
@@ -160,11 +169,10 @@ def warns(
)
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
else:
- func = args[0]
if not callable(func):
raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
with WarningsChecker(expected_warning, _ispytest=True):
- return func(*args[1:], **kwargs)
+ return func(*args, **kwargs)
class WarningsRecorder(warnings.catch_warnings):
diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py
index 7064d1daa9b..94a0403c1be 100644
--- a/testing/_py/test_local.py
+++ b/testing/_py/test_local.py
@@ -625,7 +625,8 @@ def test_chdir_gone(self, path1):
p = path1.ensure("dir_to_be_removed", dir=1)
p.chdir()
p.remove()
- pytest.raises(error.ENOENT, local)
+ with pytest.raises(error.ENOENT):
+ local()
assert path1.chdir() is None
assert os.getcwd() == str(path1)
@@ -998,8 +999,10 @@ def test_locked_make_numbered_dir(self, tmpdir):
assert numdir.new(ext=str(j)).check()
def test_error_preservation(self, path1):
- pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime)
- pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read)
+ with pytest.raises(EnvironmentError):
+ path1.join("qwoeqiwe").mtime()
+ with pytest.raises(EnvironmentError):
+ path1.join("qwoeqiwe").read()
# def test_parentdirmatch(self):
# local.parentdirmatch('std', startmodule=__name__)
@@ -1099,7 +1102,8 @@ def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
pseudopath = tmpdir.ensure(name + "123.py")
mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod)
- excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport)
+ with pytest.raises(pseudopath.ImportMismatchError) as excinfo:
+ p.pyimport()
modname, modfile, orig = excinfo.value.args
assert modname == name
assert modfile == pseudopath
@@ -1397,7 +1401,8 @@ def test_stat_helpers(self, tmpdir, monkeypatch):
def test_stat_non_raising(self, tmpdir):
path1 = tmpdir.join("file")
- pytest.raises(error.ENOENT, lambda: path1.stat())
+ with pytest.raises(error.ENOENT):
+ path1.stat()
res = path1.stat(raising=False)
assert res is None
diff --git a/testing/code/test_code.py b/testing/code/test_code.py
index 7ae5ad46100..d1e7efdc678 100644
--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -85,10 +85,8 @@ def test_code_from_func() -> None:
def test_unicode_handling() -> None:
value = "ąć".encode()
- def f() -> None:
+ with pytest.raises(Exception) as excinfo:
raise Exception(value)
-
- excinfo = pytest.raises(Exception, f)
str(excinfo)
diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py
index 555645030fc..a3b15296e4d 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -222,17 +222,18 @@ def h():
g()
#
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
traceback = excinfo.traceback
ntraceback = traceback.filter(excinfo)
print(f"old: {traceback!r}")
print(f"new: {ntraceback!r}")
if matching:
- assert len(ntraceback) == len(traceback) - 2
- else:
# -1 because of the __tracebackhide__ in pytest.raises
assert len(ntraceback) == len(traceback) - 1
+ else:
+ assert len(ntraceback) == len(traceback)
def test_traceback_recursion_index(self):
def f(n):
@@ -240,7 +241,8 @@ def f(n):
n += 1
f(n)
- excinfo = pytest.raises(RecursionError, f, 8)
+ with pytest.raises(RecursionError) as excinfo:
+ f(8)
traceback = excinfo.traceback
recindex = traceback.recursionindex()
assert recindex == 3
@@ -251,7 +253,8 @@ def f(n):
raise RuntimeError("hello")
f(n - 1)
- excinfo = pytest.raises(RuntimeError, f, 25)
+ with pytest.raises(RuntimeError) as excinfo:
+ f(25)
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
repr = excinfo.getrepr()
assert "RuntimeError: hello" in str(repr.reprcrash)
@@ -273,8 +276,8 @@ def f(n: int) -> None:
except BaseException:
reraise_me()
- excinfo = pytest.raises(RuntimeError, f, 8)
- assert excinfo is not None
+ with pytest.raises(RuntimeError) as excinfo:
+ f(8)
traceback = excinfo.traceback
recindex = traceback.recursionindex()
assert recindex is None
@@ -294,7 +297,8 @@ def fail():
fail = log(log(fail))
- excinfo = pytest.raises(ValueError, fail)
+ with pytest.raises(ValueError) as excinfo:
+ fail()
assert excinfo.traceback.recursionindex() is None
def test_getreprcrash(self):
@@ -312,7 +316,8 @@ def g():
def f():
g()
- excinfo = pytest.raises(ValueError, f)
+ with pytest.raises(ValueError) as excinfo:
+ f()
reprcrash = excinfo._getreprcrash()
assert reprcrash is not None
co = _pytest._code.Code.from_function(h)
@@ -328,12 +333,14 @@ def f():
__tracebackhide__ = True
g()
- excinfo = pytest.raises(ValueError, f)
+ with pytest.raises(ValueError) as excinfo:
+ f()
assert excinfo._getreprcrash() is None
def test_excinfo_exconly():
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
assert excinfo.exconly().startswith("ValueError")
with pytest.raises(ValueError) as excinfo:
raise ValueError("hello\nworld")
@@ -343,7 +350,8 @@ def test_excinfo_exconly():
def test_excinfo_repr_str() -> None:
- excinfo1 = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo1:
+ h()
assert repr(excinfo1) == ""
assert str(excinfo1) == ""
@@ -354,7 +362,8 @@ def __repr__(self):
def raises() -> None:
raise CustomException()
- excinfo2 = pytest.raises(CustomException, raises)
+ with pytest.raises(CustomException) as excinfo2:
+ raises()
assert repr(excinfo2) == ""
assert str(excinfo2) == ""
@@ -366,7 +375,8 @@ def test_excinfo_for_later() -> None:
def test_excinfo_errisinstance():
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
assert excinfo.errisinstance(ValueError)
@@ -390,7 +400,8 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
loader = jinja2.FileSystemLoader(str(tmp_path))
env = jinja2.Environment(loader=loader)
template = env.get_template("test.txt")
- excinfo = pytest.raises(ValueError, template.render, h=h)
+ with pytest.raises(ValueError) as excinfo:
+ template.render(h=h)
for item in excinfo.traceback:
print(item) # XXX: for some reason jinja.Template.render is printed in full
_ = item.source # shouldn't fail
@@ -754,7 +765,8 @@ def func1(m):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "m" * 500)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("m" * 500)
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True, truncate_args=True)
@@ -777,7 +789,8 @@ def func1():
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1()
excinfo.traceback = excinfo.traceback.filter(excinfo)
p = FormattedExcinfo()
reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
@@ -810,7 +823,8 @@ def func1(m, x, y, z):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("m" * 90, 5, 13, "z" * 120)
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True)
@@ -837,7 +851,8 @@ def func1(x, *y, **z):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("a", "b", c="d")
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True)
@@ -863,7 +878,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
lines = reprtb.lines
@@ -898,7 +914,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ZeroDivisionError, mod.entry)
+ with pytest.raises(ZeroDivisionError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-3])
assert len(reprtb.lines) == 1
@@ -923,7 +940,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="no")
p.repr_traceback_entry(excinfo.traceback[-2])
@@ -942,7 +960,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(tbfilter=True)
reprtb = p.repr_traceback(excinfo)
assert len(reprtb.reprentries) == 2
@@ -963,7 +982,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
from _pytest._code.code import Code
with monkeypatch.context() as mp:
@@ -988,7 +1008,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
styles: tuple[TracebackStyle, ...] = ("long", "short")
for style in styles:
@@ -1016,7 +1037,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(abspath=False)
@@ -1065,7 +1087,8 @@ def entry():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
repr = excinfo.getrepr()
repr.addsection("title", "content")
repr.toterminal(tw_mock)
@@ -1079,7 +1102,8 @@ def entry():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
repr = excinfo.getrepr()
assert repr.reprcrash is not None
assert repr.reprcrash.path.endswith("mod.py")
@@ -1098,7 +1122,8 @@ def entry():
rec1(42)
"""
)
- excinfo = pytest.raises(RuntimeError, mod.entry)
+ with pytest.raises(RuntimeError) as excinfo:
+ mod.entry()
for style in ("short", "long", "no"):
p = FormattedExcinfo(style="short")
@@ -1115,7 +1140,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
styles: tuple[TracebackStyle, ...] = ("short", "long", "no")
for style in styles:
@@ -1146,7 +1172,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
repr.toterminal(tw_mock)
@@ -1179,7 +1206,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
tmp_path.joinpath("mod.py").unlink()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
@@ -1211,7 +1239,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8")
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
@@ -1235,13 +1264,15 @@ def f():
def test_toterminal_long_filenames(
self, importasmod, tw_mock, monkeypatch: MonkeyPatch
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
path = Path(mod.__file__)
monkeypatch.chdir(path.parent)
repr = excinfo.getrepr(abspath=False)
@@ -1268,7 +1299,8 @@ def f():
g('some_value')
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr(style="value")
repr.toterminal(tw_mock)
@@ -1324,7 +1356,8 @@ def i():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
excinfo.traceback = _pytest._code.Traceback(
entry if i not in (1, 2) else entry.with_repr_style("short")
@@ -1377,7 +1410,8 @@ def h():
if True: raise AttributeError()
"""
)
- excinfo = pytest.raises(AttributeError, mod.f)
+ with pytest.raises(AttributeError) as excinfo:
+ mod.f()
r = excinfo.getrepr(style="long")
r.toterminal(tw_mock)
for line in tw_mock.lines:
@@ -1470,7 +1504,8 @@ def g():
raise ValueError()
"""
)
- excinfo = pytest.raises(AttributeError, mod.f)
+ with pytest.raises(AttributeError) as excinfo:
+ mod.f()
r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
r.toterminal(tw_mock)
for line in tw_mock.lines:
@@ -1565,7 +1600,8 @@ def unreraise():
raise e.__cause__
"""
)
- excinfo = pytest.raises(ZeroDivisionError, mod.unreraise)
+ with pytest.raises(ZeroDivisionError) as excinfo:
+ mod.unreraise()
r = excinfo.getrepr(style="short")
r.toterminal(tw_mock)
out = "\n".join(line for line in tw_mock.lines if isinstance(line, str))
diff --git a/testing/code/test_source.py b/testing/code/test_source.py
index 321372d4b59..bf7ed38cefe 100644
--- a/testing/code/test_source.py
+++ b/testing/code/test_source.py
@@ -210,7 +210,8 @@ def test_getstatementrange_out_of_bounds_py3(self) -> None:
def test_getstatementrange_with_syntaxerror_issue7(self) -> None:
source = Source(":")
- pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
+ with pytest.raises(SyntaxError):
+ source.getstatementrange(0)
def test_getstartingblock_singleline() -> None:
@@ -379,7 +380,8 @@ def test_code_of_object_instance_with_call() -> None:
class A:
pass
- pytest.raises(TypeError, lambda: Source(A()))
+ with pytest.raises(TypeError):
+ Source(A())
class WithCall:
def __call__(self) -> None:
@@ -392,7 +394,8 @@ class Hello:
def __call__(self) -> None:
pass
- pytest.raises(TypeError, lambda: Code.from_function(Hello))
+ with pytest.raises(TypeError):
+ Code.from_function(Hello)
def getstatement(lineno: int, source) -> Source:
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
index 112d1e05f27..678dd06d907 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
@@ -6,4 +6,5 @@
@pytest.fixture
def arg2(request):
- pytest.raises(Exception, request.getfixturevalue, "arg1")
+ with pytest.raises(Exception): # noqa: B017 # too general exception
+ request.getfixturevalue("arg1")
diff --git a/testing/python/collect.py b/testing/python/collect.py
index 530f1c340ff..0834204be84 100644
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -20,7 +20,8 @@
class TestModule:
def test_failing_import(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("import alksdjalskdjalkjals")
- pytest.raises(Collector.CollectError, modcol.collect)
+ with pytest.raises(Collector.CollectError):
+ modcol.collect()
def test_import_duplicate(self, pytester: Pytester) -> None:
a = pytester.mkdir("a")
@@ -72,12 +73,15 @@ def test():
def test_syntax_error_in_module(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("this is a syntax error")
- pytest.raises(modcol.CollectError, modcol.collect)
- pytest.raises(modcol.CollectError, modcol.collect)
+ with pytest.raises(modcol.CollectError):
+ modcol.collect()
+ with pytest.raises(modcol.CollectError):
+ modcol.collect()
def test_module_considers_pluginmanager_at_import(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("pytest_plugins='xasdlkj',")
- pytest.raises(ImportError, lambda: modcol.obj)
+ with pytest.raises(ImportError):
+ modcol.obj()
def test_invalid_test_module_name(self, pytester: Pytester) -> None:
a = pytester.mkdir("a")
diff --git a/testing/python/raises.py b/testing/python/raises.py
index 40f9afea3ba..66b48465307 100644
--- a/testing/python/raises.py
+++ b/testing/python/raises.py
@@ -21,11 +21,13 @@ def test_check_callable(self) -> None:
pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload]
def test_raises(self):
- excinfo = pytest.raises(ValueError, int, "qwe")
+ with pytest.raises(ValueError) as excinfo:
+ int("qwe")
assert "invalid literal" in str(excinfo.value)
def test_raises_function(self):
- excinfo = pytest.raises(ValueError, int, "hello")
+ with pytest.raises(ValueError) as excinfo:
+ int("hello")
assert "invalid literal" in str(excinfo.value)
def test_raises_does_not_allow_none(self):
@@ -179,7 +181,8 @@ def test_invalid_regex():
def test_noclass(self) -> None:
with pytest.raises(TypeError):
- pytest.raises("wrong", lambda: None) # type: ignore[call-overload]
+ with pytest.raises("wrong"): # type: ignore[call-overload]
+ ... # pragma: no cover
def test_invalid_arguments_to_raises(self) -> None:
with pytest.raises(TypeError, match="unknown"):
@@ -192,7 +195,8 @@ def test_tuple(self):
def test_no_raise_message(self) -> None:
try:
- pytest.raises(ValueError, int, "0")
+ with pytest.raises(ValueError):
+ int("0")
except pytest.fail.Exception as e:
assert e.msg == f"DID NOT RAISE {ValueError!r}"
else:
@@ -266,7 +270,8 @@ def test_raises_match(self) -> None:
pytest.raises(ValueError, int, "asdf").match(msg)
assert str(excinfo.value) == expr
- pytest.raises(TypeError, int, match="invalid")
+ with pytest.raises(TypeError, match="invalid"):
+ int() # noqa: UP018
def tfunc(match):
raise ValueError(f"match={match}")
@@ -320,10 +325,10 @@ def test_raises_match_wrong_type(self):
def test_raises_exception_looks_iterable(self):
class Meta(type):
def __getitem__(self, item):
- return 1 / 0
+ return 1 / 0 # pragma: no cover
def __len__(self):
- return 1
+ return 1 # pragma: no cover
class ClassLooksIterableException(Exception, metaclass=Meta):
pass
@@ -332,7 +337,8 @@ class ClassLooksIterableException(Exception, metaclass=Meta):
Failed,
match=r"DID NOT RAISE ",
):
- pytest.raises(ClassLooksIterableException, lambda: None)
+ with pytest.raises(ClassLooksIterableException):
+ ... # pragma: no cover
def test_raises_with_raising_dunder_class(self) -> None:
"""Test current behavior with regard to exceptions via __class__ (#4284)."""
diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py
index ca417e86ee5..32065308550 100644
--- a/testing/test_cacheprovider.py
+++ b/testing/test_cacheprovider.py
@@ -51,7 +51,8 @@ def test_config_cache_dataerror(self, pytester: Pytester) -> None:
config = pytester.parseconfigure()
assert config.cache is not None
cache = config.cache
- pytest.raises(TypeError, lambda: cache.set("key/name", cache))
+ with pytest.raises(TypeError):
+ cache.set("key/name", cache)
config.cache.set("key/name", 0)
config.cache._getvaluepath("key/name").write_bytes(b"123invalid")
val = config.cache.get("key/name", -2)
@@ -143,7 +144,8 @@ def test_cachefuncarg(cache):
val = cache.get("some/thing", None)
assert val is None
cache.set("some/thing", [1])
- pytest.raises(TypeError, lambda: cache.get("some/thing"))
+ with pytest.raises(TypeError):
+ cache.get("some/thing")
val = cache.get("some/thing", [])
assert val == [1]
"""
diff --git a/testing/test_capture.py b/testing/test_capture.py
index d9dacebd938..330050589f1 100644
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -96,7 +96,8 @@ def test_init_capturing(self):
try:
capman = CaptureManager("fd")
capman.start_global_capturing()
- pytest.raises(AssertionError, capman.start_global_capturing)
+ with pytest.raises(AssertionError):
+ capman.start_global_capturing()
capman.stop_global_capturing()
finally:
capouter.stop_capturing()
@@ -885,7 +886,8 @@ def test_text(self) -> None:
def test_unicode_and_str_mixture(self) -> None:
f = capture.CaptureIO()
f.write("\u00f6")
- pytest.raises(TypeError, f.write, b"hello")
+ with pytest.raises(TypeError):
+ f.write(b"hello") # type: ignore[arg-type]
def test_write_bytes_to_buffer(self) -> None:
"""In python3, stdout / stderr are text io wrappers (exposing a buffer
@@ -912,7 +914,8 @@ def test_unicode_and_str_mixture(self) -> None:
sio = io.StringIO()
f = capture.TeeCaptureIO(sio)
f.write("\u00f6")
- pytest.raises(TypeError, f.write, b"hello")
+ with pytest.raises(TypeError):
+ f.write(b"hello") # type: ignore[arg-type]
def test_dontreadfrominput() -> None:
@@ -921,19 +924,29 @@ def test_dontreadfrominput() -> None:
f = DontReadFromInput()
assert f.buffer is f # type: ignore[comparison-overlap]
assert not f.isatty()
- pytest.raises(OSError, f.read)
- pytest.raises(OSError, f.readlines)
+ with pytest.raises(OSError):
+ f.read()
+ with pytest.raises(OSError):
+ f.readlines()
iter_f = iter(f)
- pytest.raises(OSError, next, iter_f)
- pytest.raises(UnsupportedOperation, f.fileno)
- pytest.raises(UnsupportedOperation, f.flush)
+ with pytest.raises(OSError):
+ next(iter_f)
+ with pytest.raises(UnsupportedOperation):
+ f.fileno()
+ with pytest.raises(UnsupportedOperation):
+ f.flush()
assert not f.readable()
- pytest.raises(UnsupportedOperation, f.seek, 0)
+ with pytest.raises(UnsupportedOperation):
+ f.seek(0)
assert not f.seekable()
- pytest.raises(UnsupportedOperation, f.tell)
- pytest.raises(UnsupportedOperation, f.truncate, 0)
- pytest.raises(UnsupportedOperation, f.write, b"")
- pytest.raises(UnsupportedOperation, f.writelines, [])
+ with pytest.raises(UnsupportedOperation):
+ f.tell()
+ with pytest.raises(UnsupportedOperation):
+ f.truncate(0)
+ with pytest.raises(UnsupportedOperation):
+ f.write(b"") # type: ignore[arg-type]
+ with pytest.raises(UnsupportedOperation):
+ f.writelines([])
assert not f.writable()
assert isinstance(f.encoding, str)
f.close() # just for completeness
@@ -1000,7 +1013,8 @@ def test_simple(self, tmpfile: BinaryIO) -> None:
cap = capture.FDCapture(fd)
data = b"hello"
os.write(fd, data)
- pytest.raises(AssertionError, cap.snap)
+ with pytest.raises(AssertionError):
+ cap.snap()
cap.done()
cap = capture.FDCapture(fd)
cap.start()
@@ -1022,7 +1036,8 @@ def test_simple_fail_second_start(self, tmpfile: BinaryIO) -> None:
fd = tmpfile.fileno()
cap = capture.FDCapture(fd)
cap.done()
- pytest.raises(AssertionError, cap.start)
+ with pytest.raises(AssertionError):
+ cap.start()
def test_stderr(self) -> None:
cap = capture.FDCapture(2)
@@ -1073,7 +1088,8 @@ def test_simple_resume_suspend(self) -> None:
assert s == "but now yes\n"
cap.suspend()
cap.done()
- pytest.raises(AssertionError, cap.suspend)
+ with pytest.raises(AssertionError):
+ cap.suspend()
assert repr(cap) == (
f""
@@ -1155,7 +1171,8 @@ def test_reset_twice_error(self) -> None:
with self.getcapture() as cap:
print("hello")
out, err = cap.readouterr()
- pytest.raises(ValueError, cap.stop_capturing)
+ with pytest.raises(ValueError):
+ cap.stop_capturing()
assert out == "hello\n"
assert not err
@@ -1213,7 +1230,8 @@ def test_stdin_nulled_by_default(self) -> None:
print("XXX which indicates an error in the underlying capturing")
print("XXX mechanisms")
with self.getcapture():
- pytest.raises(OSError, sys.stdin.read)
+ with pytest.raises(OSError):
+ sys.stdin.read()
class TestTeeStdCapture(TestStdCapture):
@@ -1667,9 +1685,8 @@ def test_encodedfile_writelines(tmpfile: BinaryIO) -> None:
def test__get_multicapture() -> None:
assert isinstance(_get_multicapture("no"), MultiCapture)
- pytest.raises(ValueError, _get_multicapture, "unknown").match(
- r"^unknown capturing method: 'unknown'"
- )
+ with pytest.raises(ValueError, match=r"^unknown capturing method: 'unknown'$"):
+ _get_multicapture("unknown") # type: ignore[arg-type]
def test_logging_while_collecting(pytester: Pytester) -> None:
diff --git a/testing/test_config.py b/testing/test_config.py
index bb08c40fef4..a40ef6e36d7 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -556,7 +556,8 @@ def test_args_source_testpaths(self, pytester: Pytester):
class TestConfigCmdlineParsing:
def test_parsing_again_fails(self, pytester: Pytester) -> None:
config = pytester.parseconfig()
- pytest.raises(AssertionError, lambda: config.parse([]))
+ with pytest.raises(AssertionError):
+ config.parse([])
def test_explicitly_specified_config_file_is_loaded(
self, pytester: Pytester
@@ -647,7 +648,8 @@ def pytest_addoption(parser):
config = pytester.parseconfig("--hello=this")
for x in ("hello", "--hello", "-X"):
assert config.getoption(x) == "this"
- pytest.raises(ValueError, config.getoption, "qweqwe")
+ with pytest.raises(ValueError):
+ config.getoption("qweqwe")
config_novalue = pytester.parseconfig()
assert config_novalue.getoption("hello") is None
@@ -673,7 +675,8 @@ def pytest_addoption(parser):
def test_config_getvalueorskip(self, pytester: Pytester) -> None:
config = pytester.parseconfig()
- pytest.raises(pytest.skip.Exception, config.getvalueorskip, "hello")
+ with pytest.raises(pytest.skip.Exception):
+ config.getvalueorskip("hello")
verbose = config.getvalueorskip("verbose")
assert verbose == config.option.verbose
@@ -721,7 +724,8 @@ def pytest_addoption(parser):
config = pytester.parseconfig()
val = config.getini("myname")
assert val == "hello"
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])
def test_addini_paths(self, pytester: Pytester, config_type: str) -> None:
@@ -751,7 +755,8 @@ def pytest_addoption(parser):
assert len(values) == 2
assert values[0] == inipath.parent.joinpath("hello")
assert values[1] == inipath.parent.joinpath("world/sub.py")
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
def make_conftest_for_args(self, pytester: Pytester) -> None:
pytester.makeconftest(
diff --git a/testing/test_debugging.py b/testing/test_debugging.py
index 08ebf600253..950386a4923 100644
--- a/testing/test_debugging.py
+++ b/testing/test_debugging.py
@@ -324,12 +324,13 @@ def test_pdb_interaction_exception(self, pytester: Pytester) -> None:
def globalfunc():
pass
def test_1():
- pytest.raises(ValueError, globalfunc)
+ with pytest.raises(ValueError):
+ globalfunc()
"""
)
child = pytester.spawn_pytest(f"--pdb {p1}")
child.expect(".*def test_1")
- child.expect(".*pytest.raises.*globalfunc")
+ child.expect(r"with pytest.raises\(ValueError\)")
child.expect("Pdb")
child.sendline("globalfunc")
child.expect(".*function")
diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py
index 72854e4e5c0..ba7f93b1016 100644
--- a/testing/test_legacypath.py
+++ b/testing/test_legacypath.py
@@ -141,7 +141,8 @@ def pytest_addoption(parser):
assert len(values) == 2
assert values[0] == inipath.parent.joinpath("hello")
assert values[1] == inipath.parent.joinpath("world/sub.py")
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
def test_override_ini_paths(pytester: pytest.Pytester) -> None:
diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py
index 0e992e298ec..4aa822a9878 100644
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -29,7 +29,8 @@ class A:
x = 1
monkeypatch = MonkeyPatch()
- pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2)
+ with pytest.raises(AttributeError):
+ monkeypatch.setattr(A, "notexists", 2)
monkeypatch.setattr(A, "y", 2, raising=False)
assert A.y == 2 # type: ignore
monkeypatch.undo()
@@ -110,7 +111,8 @@ class A:
monkeypatch = MonkeyPatch()
monkeypatch.delattr(A, "x")
- pytest.raises(AttributeError, monkeypatch.delattr, A, "y")
+ with pytest.raises(AttributeError):
+ monkeypatch.delattr(A, "y")
monkeypatch.delattr(A, "y", raising=False)
monkeypatch.setattr(A, "x", 5, raising=False)
assert A.x == 5
@@ -167,7 +169,8 @@ def test_delitem() -> None:
monkeypatch.delitem(d, "x")
assert "x" not in d
monkeypatch.delitem(d, "y", raising=False)
- pytest.raises(KeyError, monkeypatch.delitem, d, "y")
+ with pytest.raises(KeyError):
+ monkeypatch.delitem(d, "y")
assert not d
monkeypatch.setitem(d, "y", 1700)
assert d["y"] == 1700
@@ -193,7 +196,8 @@ def test_delenv() -> None:
name = "xyz1234"
assert name not in os.environ
monkeypatch = MonkeyPatch()
- pytest.raises(KeyError, monkeypatch.delenv, name, raising=True)
+ with pytest.raises(KeyError):
+ monkeypatch.delenv(name, raising=True)
monkeypatch.delenv(name, raising=False)
monkeypatch.undo()
os.environ[name] = "1"
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index 36db7b13989..2abbd5d6dbb 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -24,7 +24,8 @@ def parser() -> parseopt.Parser:
class TestParser:
def test_no_help_by_default(self) -> None:
parser = parseopt.Parser(usage="xyz", _ispytest=True)
- pytest.raises(UsageError, lambda: parser.parse(["-h"]))
+ with pytest.raises(UsageError):
+ parser.parse(["-h"])
def test_custom_prog(self, parser: parseopt.Parser) -> None:
"""Custom prog can be set for `argparse.ArgumentParser`."""
diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py
index db85124bf0d..d6f21167b7e 100644
--- a/testing/test_pluginmanager.py
+++ b/testing/test_pluginmanager.py
@@ -268,8 +268,10 @@ def test_register_imported_modules(self) -> None:
assert pm.is_registered(mod)
values = pm.get_plugins()
assert mod in values
- pytest.raises(ValueError, pm.register, mod)
- pytest.raises(ValueError, lambda: pm.register(mod))
+ with pytest.raises(ValueError):
+ pm.register(mod)
+ with pytest.raises(ValueError):
+ pm.register(mod)
# assert not pm.is_registered(mod2)
assert pm.get_plugins() == values
@@ -376,8 +378,10 @@ def test_hello(pytestconfig):
def test_import_plugin_importname(
self, pytester: Pytester, pytestpm: PytestPluginManager
) -> None:
- pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
- pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("pytest_qweqwx.y")
pytester.syspathinsert()
pluginname = "pytest_hello"
@@ -396,8 +400,10 @@ def test_import_plugin_importname(
def test_import_plugin_dotted_name(
self, pytester: Pytester, pytestpm: PytestPluginManager
) -> None:
- pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
- pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("pytest_qweqwex.y")
pytester.syspathinsert()
pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8")
@@ -423,9 +429,8 @@ def test_consider_conftest_deps(
class TestPytestPluginManagerBootstrapping:
def test_preparse_args(self, pytestpm: PytestPluginManager) -> None:
- pytest.raises(
- ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
- )
+ with pytest.raises(ImportError):
+ pytestpm.consider_preparse(["xyz", "-p", "hello123"])
# Handles -p without space (#3532).
with pytest.raises(ImportError) as excinfo:
diff --git a/testing/test_pytester.py b/testing/test_pytester.py
index 721e8c19d8b..780d4ff4b3d 100644
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -71,7 +71,8 @@ class rep2:
recorder.unregister() # type: ignore[attr-defined]
recorder.clear()
recorder.hook.pytest_runtest_logreport(report=rep3) # type: ignore[attr-defined]
- pytest.raises(ValueError, recorder.getfailures)
+ with pytest.raises(ValueError):
+ recorder.getfailures()
def test_parseconfig(pytester: Pytester) -> None:
@@ -196,7 +197,8 @@ def test_hookrecorder_basic(holder) -> None:
call = rec.popcall("pytest_xyz")
assert call.arg == 123
assert call._name == "pytest_xyz"
- pytest.raises(pytest.fail.Exception, rec.popcall, "abc")
+ with pytest.raises(pytest.fail.Exception):
+ rec.popcall("abc")
pm.hook.pytest_xyz_noarg()
call = rec.popcall("pytest_xyz_noarg")
assert call._name == "pytest_xyz_noarg"
diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py
index 384f2b66a15..14b5d10e89e 100644
--- a/testing/test_recwarn.py
+++ b/testing/test_recwarn.py
@@ -97,7 +97,8 @@ def test_recording(self) -> None:
rec.clear()
assert len(rec.list) == 0
assert values is rec.list
- pytest.raises(AssertionError, rec.pop)
+ with pytest.raises(AssertionError):
+ rec.pop()
def test_warn_stacklevel(self) -> None:
"""#4243"""
@@ -145,10 +146,12 @@ def dep_explicit(self, i: int) -> None:
def test_deprecated_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
- pytest.deprecated_call(self.dep, 3, 5)
+ with pytest.deprecated_call():
+ self.dep(3, 5)
def test_deprecated_call(self) -> None:
- pytest.deprecated_call(self.dep, 0, 5)
+ with pytest.deprecated_call():
+ self.dep(0, 5)
def test_deprecated_call_ret(self) -> None:
ret = pytest.deprecated_call(self.dep, 0)
@@ -170,11 +173,14 @@ def test_deprecated_call_preserves(self) -> None:
def test_deprecated_explicit_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception):
- pytest.deprecated_call(self.dep_explicit, 3)
+ with pytest.deprecated_call():
+ self.dep_explicit(3)
def test_deprecated_explicit_call(self) -> None:
- pytest.deprecated_call(self.dep_explicit, 0)
- pytest.deprecated_call(self.dep_explicit, 0)
+ with pytest.deprecated_call():
+ self.dep_explicit(0)
+ with pytest.deprecated_call():
+ self.dep_explicit(0)
@pytest.mark.parametrize("mode", ["context_manager", "call"])
def test_deprecated_call_no_warning(self, mode) -> None:
@@ -198,7 +204,7 @@ def f():
)
@pytest.mark.parametrize("mode", ["context_manager", "call"])
@pytest.mark.parametrize("call_f_first", [True, False])
- @pytest.mark.filterwarnings("ignore")
+ @pytest.mark.filterwarnings("ignore:hi")
def test_deprecated_call_modes(self, warning_type, mode, call_f_first) -> None:
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
block/function.
@@ -256,11 +262,14 @@ def test_check_callable(self) -> None:
def test_several_messages(self) -> None:
# different messages, b/c Python suppresses multiple identical warnings
- pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
+ with pytest.warns(RuntimeWarning):
+ warnings.warn("w1", RuntimeWarning)
with pytest.warns(RuntimeWarning):
with pytest.raises(pytest.fail.Exception):
- pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
- pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
+ with pytest.warns(UserWarning):
+ warnings.warn("w2", RuntimeWarning)
+ with pytest.warns(RuntimeWarning):
+ warnings.warn("w3", RuntimeWarning)
def test_function(self) -> None:
pytest.warns(
@@ -268,20 +277,14 @@ def test_function(self) -> None:
)
def test_warning_tuple(self) -> None:
- pytest.warns(
- (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning)
- )
- pytest.warns(
- (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
- )
- with pytest.warns():
- pytest.raises(
- pytest.fail.Exception,
- lambda: pytest.warns(
- (RuntimeWarning, SyntaxWarning),
- lambda: warnings.warn("w3", UserWarning),
- ),
- )
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w1", RuntimeWarning)
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w2", SyntaxWarning)
+ with pytest.warns(UserWarning, match="^w3$"):
+ with pytest.raises(pytest.fail.Exception):
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w3", UserWarning)
def test_as_contextmanager(self) -> None:
with pytest.warns(RuntimeWarning):
@@ -420,7 +423,7 @@ def test_none_of_multiple_warns(self) -> None:
warnings.warn("bbbbbbbbbb", UserWarning)
warnings.warn("cccccccccc", UserWarning)
- @pytest.mark.filterwarnings("ignore")
+ @pytest.mark.filterwarnings("ignore:ohai")
def test_can_capture_previously_warned(self) -> None:
def f() -> int:
warnings.warn(UserWarning("ohai"))
diff --git a/testing/test_runner.py b/testing/test_runner.py
index 0245438a47d..a0d0ca2c614 100644
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -780,8 +780,10 @@ def f():
# check that importorskip reports the actual call
# in this test the test_runner.py file
assert path.stem == "test_runner"
- pytest.raises(SyntaxError, pytest.importorskip, "x y z")
- pytest.raises(SyntaxError, pytest.importorskip, "x=y")
+ with pytest.raises(SyntaxError):
+ pytest.importorskip("x y z")
+ with pytest.raises(SyntaxError):
+ pytest.importorskip("x=y")
mod = types.ModuleType("hello123")
mod.__version__ = "1.3" # type: ignore
monkeypatch.setitem(sys.modules, "hello123", mod)
diff --git a/testing/test_session.py b/testing/test_session.py
index ba904916033..c6b5717ca83 100644
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -63,7 +63,8 @@ def test_raises_output(self, pytester: Pytester) -> None:
"""
import pytest
def test_raises_doesnt():
- pytest.raises(ValueError, int, "3")
+ with pytest.raises(ValueError):
+ int("3")
"""
)
passed, skipped, failed = reprec.listoutcomes()
From 4be284f6cbdfda8f9e9a61c63e4f9edc3c970e40 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:47:26 +0200
Subject: [PATCH 2/5] fix failing tests
---
testing/code/test_excinfo.py | 12 ++++++++++++
testing/python/raises.py | 3 +--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py
index a3b15296e4d..8d64f14a537 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -325,6 +325,8 @@ def f():
assert reprcrash.lineno == co.firstlineno + 1 + 1
def test_getreprcrash_empty(self):
+ __tracebackhide__ = True
+
def g():
__tracebackhide__ = True
raise ValueError
@@ -952,6 +954,7 @@ def entry():
assert not lines[1:]
def test_repr_traceback_tbfilter(self, importasmod):
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -1000,6 +1003,7 @@ def entry():
assert last_lines[1] == "E ValueError: hello"
def test_repr_traceback_and_excinfo(self, importasmod) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -1029,6 +1033,7 @@ def entry():
assert repr.reprcrash.message == "ValueError: 0"
def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -1164,6 +1169,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
assert x == "я"
def test_toterminal_long(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1198,6 +1204,7 @@ def f():
def test_toterminal_long_missing_source(
self, importasmod, tmp_path: Path, tw_mock
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1231,6 +1238,7 @@ def f():
def test_toterminal_long_incomplete_source(
self, importasmod, tmp_path: Path, tw_mock
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1344,6 +1352,7 @@ def foo():
assert file.getvalue()
def test_traceback_repr_style(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
def f():
@@ -1392,6 +1401,7 @@ def i():
assert tw_mock.lines[20] == ":9: ValueError"
def test_exc_chain_repr(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
class Err(Exception):
@@ -1492,6 +1502,7 @@ def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
- When the exception is raised with "from None"
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
"""
+ __tracebackhide__ = True
raise_suffix = " from None" if mode == "from_none" else ""
mod = importasmod(
f"""
@@ -1582,6 +1593,7 @@ def g():
)
def test_exc_chain_repr_cycle(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
class Err(Exception):
diff --git a/testing/python/raises.py b/testing/python/raises.py
index 66b48465307..dee922546ba 100644
--- a/testing/python/raises.py
+++ b/testing/python/raises.py
@@ -270,8 +270,7 @@ def test_raises_match(self) -> None:
pytest.raises(ValueError, int, "asdf").match(msg)
assert str(excinfo.value) == expr
- with pytest.raises(TypeError, match="invalid"):
- int() # noqa: UP018
+ pytest.raises(TypeError, int, match="invalid") # type: ignore[call-overload]
def tfunc(match):
raise ValueError(f"match={match}")
From 85f478637530e5fe41ba6728df1c6638c29e3449 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:49:28 +0200
Subject: [PATCH 3/5] oh I did have a changelog entry
---
changelog/13241.improvement.rst | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 changelog/13241.improvement.rst
diff --git a/changelog/13241.improvement.rst b/changelog/13241.improvement.rst
new file mode 100644
index 00000000000..41ba55e280a
--- /dev/null
+++ b/changelog/13241.improvement.rst
@@ -0,0 +1,2 @@
+:func:`pytest.raises`, :func:`pytest.warns` and :func:`pytest.deprecated_call` now uses :class:`ParamSpec` for the type hint to the (old and not recommended) callable overload, instead of :class:`Any`. This allows type checkers to raise errors when passing incorrect function parameters.
+``func`` can now also be passed as a kwarg, which the type hint previously showed as possible but didn't accept.
From 611d96ae3a1c99920abc30559abe633c07489a6b Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 16:24:33 +0200
Subject: [PATCH 4/5] remove legacy form from docs, update some wording
---
doc/en/how-to/assert.rst | 2 +-
src/_pytest/python_api.py | 3 ---
src/_pytest/raises.py | 19 -------------------
src/_pytest/recwarn.py | 12 ++++--------
4 files changed, 5 insertions(+), 31 deletions(-)
diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index 8f8208b77ee..53e0498715c 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -283,7 +283,7 @@ Alternate `pytest.raises` form (legacy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. warning::
- This will be deprecated and removed in a future release.
+ This form is likely to be deprecated and removed in a future release.
There is an alternate form of :func:`pytest.raises` where you pass
diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py
index 026c2ea851f..07794abea95 100644
--- a/src/_pytest/python_api.py
+++ b/src/_pytest/python_api.py
@@ -16,9 +16,6 @@
if TYPE_CHECKING:
from numpy import ndarray
- from typing_extensions import ParamSpec
-
- P = ParamSpec("P")
def _compare_approx(
diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py
index fd9b77a15af..f48932f7989 100644
--- a/src/_pytest/raises.py
+++ b/src/_pytest/raises.py
@@ -238,25 +238,6 @@ def raises(
:ref:`assertraises` for more examples and detailed discussion.
- **Legacy form**
-
- It is possible to specify a callable by passing a to-be-called lambda::
-
- >>> raises(ZeroDivisionError, lambda: 1/0)
-
-
- or you can specify an arbitrary callable with arguments::
-
- >>> def f(x): return 1/x
- ...
- >>> raises(ZeroDivisionError, f, 0)
-
- >>> raises(ZeroDivisionError, f, x=0)
-
-
- The form above is going to be deprecated in a future pytest release as the
- context manager form is regarded as more readable and less error-prone.
-
.. note::
Similar to caught exception objects in Python, explicitly clearing
local references to returned ``ExceptionInfo`` objects can
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index 804ff6399d0..2eb52f818cd 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -73,14 +73,10 @@ def deprecated_call(
>>> with pytest.deprecated_call(match="^use v3 of this api$") as warning_messages:
... assert api_call_v2() == 200
- It can also be used by passing a function and ``*args`` and ``**kwargs``,
- in which case it will ensure calling ``func(*args, **kwargs)`` produces one of
- the warnings types above. The return value is the return value of the function.
-
- In the context manager form you may use the keyword argument ``match`` to assert
+ You may use the keyword argument ``match`` to assert
that the warning matches a text or regex.
- The context manager produces a list of :class:`warnings.WarningMessage` objects,
+ The return value is a list of :class:`warnings.WarningMessage` objects,
one for each warning emitted
(regardless of whether it is an ``expected_warning`` or not).
"""
@@ -127,13 +123,13 @@ def warns(
each warning emitted (regardless of whether it is an ``expected_warning`` or not).
Since pytest 8.0, unmatched warnings are also re-emitted when the context closes.
- This function can be used as a context manager::
+ This function should be used as a context manager::
>>> import pytest
>>> with pytest.warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
- In the context manager form you may use the keyword argument ``match`` to assert
+ The ``match`` keyword argument can be used to assert
that the warning matches a text or regex::
>>> with pytest.warns(UserWarning, match='must be 0 or None'):
From a86483a98d9d7c72770481d0a80e3da340124faf Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 10 Jun 2025 15:13:08 +0200
Subject: [PATCH 5/5] doc updates, replace 3 more stray callable raises
---
doc/en/how-to/assert.rst | 7 -------
src/_pytest/recwarn.py | 1 -
testing/python/fixtures.py | 8 ++++----
testing/test_mark.py | 4 ++--
testing/test_skipping.py | 3 ++-
5 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index 53e0498715c..2366cbc598c 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -282,10 +282,6 @@ exception at a specific level; exceptions contained directly in the top
Alternate `pytest.raises` form (legacy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. warning::
- This form is likely to be deprecated and removed in a future release.
-
-
There is an alternate form of :func:`pytest.raises` where you pass
a function that will be executed, along with ``*args`` and ``**kwargs``. :func:`pytest.raises`
will then execute the function with those arguments and assert that the given exception is raised:
@@ -299,9 +295,6 @@ will then execute the function with those arguments and assert that the given ex
pytest.raises(ValueError, func, x=-1)
-The reporter will provide you with helpful output in case of failures such as *no
-exception* or *wrong exception*.
-
This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
being considered more readable.
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index 2eb52f818cd..6fdb6d6eb80 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -81,7 +81,6 @@ def deprecated_call(
(regardless of whether it is an ``expected_warning`` or not).
"""
__tracebackhide__ = True
- # Potential QoL: allow `with deprecated_call:` - i.e. no parens
dep_warnings = (DeprecationWarning, PendingDeprecationWarning, FutureWarning)
if func is None:
return warns(dep_warnings, *args, **kwargs)
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index fb76fe6cf96..6e420cd9c51 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -3447,8 +3447,8 @@ def myscoped(request):
for x in {ok.split()}:
assert hasattr(request, x)
for x in {error.split()}:
- pytest.raises(AttributeError, lambda:
- getattr(request, x))
+ with pytest.raises(AttributeError):
+ getattr(request, x)
assert request.session
assert request.config
def test_func():
@@ -3467,8 +3467,8 @@ def arg(request):
for x in {ok.split()!r}:
assert hasattr(request, x)
for x in {error.split()!r}:
- pytest.raises(AttributeError, lambda:
- getattr(request, x))
+ with pytest.raises(AttributeError):
+ getattr(request, x)
assert request.session
assert request.config
def test_func(arg):
diff --git a/testing/test_mark.py b/testing/test_mark.py
index 1e51f9db18f..a088586612c 100644
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -724,8 +724,8 @@ def pytest_collection_modifyitems(session):
session.add_marker("mark1")
session.add_marker(pytest.mark.mark2)
session.add_marker(pytest.mark.mark3)
- pytest.raises(ValueError, lambda:
- session.add_marker(10))
+ with pytest.raises(ValueError):
+ session.add_marker(10)
"""
)
pytester.makepyfile(
diff --git a/testing/test_skipping.py b/testing/test_skipping.py
index 9a6c2c4b6aa..1d27c39ba0f 100644
--- a/testing/test_skipping.py
+++ b/testing/test_skipping.py
@@ -919,7 +919,8 @@ def test_func():
pass
"""
)
- pytest.raises(pytest.skip.Exception, lambda: pytest_runtest_setup(item))
+ with pytest.raises(pytest.skip.Exception):
+ pytest_runtest_setup(item)
@pytest.mark.parametrize(
"marker, msg1, msg2",