Merge pull request #11959 from eerovaher/warn-message-type
Allow using `warnings.warn()` with a `Warning`
This commit is contained in:
commit
6ef0cf150a
1
AUTHORS
1
AUTHORS
|
@ -127,6 +127,7 @@ Edison Gustavo Muenz
|
||||||
Edoardo Batini
|
Edoardo Batini
|
||||||
Edson Tadeu M. Manoel
|
Edson Tadeu M. Manoel
|
||||||
Eduardo Schettino
|
Eduardo Schettino
|
||||||
|
Eero Vaher
|
||||||
Eli Boyarski
|
Eli Boyarski
|
||||||
Elizaveta Shashkova
|
Elizaveta Shashkova
|
||||||
Éloi Rivard
|
Éloi Rivard
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
:func:`pytest.warns` now validates that warning object's ``message`` is of type `str` -- currently in Python it is possible to pass other types than `str` when creating `Warning` instances, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings. See `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion.
|
:func:`pytest.warns` now validates that :func:`warnings.warn` was called with a `str` or a `Warning`.
|
||||||
|
Currently in Python it is possible to use other types, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings (see `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion).
|
||||||
While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
|
While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
|
||||||
|
|
|
@ -314,7 +314,7 @@ class WarningsChecker(WarningsRecorder):
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
def found_str():
|
def found_str() -> str:
|
||||||
return pformat([record.message for record in self], indent=2)
|
return pformat([record.message for record in self], indent=2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -341,14 +341,30 @@ class WarningsChecker(WarningsRecorder):
|
||||||
module=w.__module__,
|
module=w.__module__,
|
||||||
source=w.source,
|
source=w.source,
|
||||||
)
|
)
|
||||||
# Check warnings has valid argument type (#10865).
|
|
||||||
wrn: warnings.WarningMessage
|
|
||||||
for wrn in self:
|
|
||||||
self._validate_message(wrn)
|
|
||||||
|
|
||||||
@staticmethod
|
# Currently in Python it is possible to pass other types than an
|
||||||
def _validate_message(wrn: Any) -> None:
|
# `str` message when creating `Warning` instances, however this
|
||||||
if not isinstance(msg := wrn.message.args[0], str):
|
# causes an exception when :func:`warnings.filterwarnings` is used
|
||||||
|
# to filter those warnings. See
|
||||||
|
# https://github.com/python/cpython/issues/103577 for a discussion.
|
||||||
|
# While this can be considered a bug in CPython, we put guards in
|
||||||
|
# pytest as the error message produced without this check in place
|
||||||
|
# is confusing (#10865).
|
||||||
|
for w in self:
|
||||||
|
if type(w.message) is not UserWarning:
|
||||||
|
# If the warning was of an incorrect type then `warnings.warn()`
|
||||||
|
# creates a UserWarning. Any other warning must have been specified
|
||||||
|
# explicitly.
|
||||||
|
continue
|
||||||
|
if not w.message.args:
|
||||||
|
# UserWarning() without arguments must have been specified explicitly.
|
||||||
|
continue
|
||||||
|
msg = w.message.args[0]
|
||||||
|
if isinstance(msg, str):
|
||||||
|
continue
|
||||||
|
# It's possible that UserWarning was explicitly specified, and
|
||||||
|
# its first argument was not a string. But that case can't be
|
||||||
|
# distinguished from an invalid type.
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Warning message must be str, got {msg!r} (type {type(msg).__name__})"
|
f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
from typing import Union
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -546,24 +547,34 @@ class TestWarns:
|
||||||
result.assert_outcomes()
|
result.assert_outcomes()
|
||||||
|
|
||||||
|
|
||||||
def test_raise_type_error_on_non_string_warning() -> None:
|
def test_raise_type_error_on_invalid_warning() -> None:
|
||||||
"""Check pytest.warns validates warning messages are strings (#10865)."""
|
"""Check pytest.warns validates warning messages are strings (#10865) or
|
||||||
with pytest.raises(TypeError, match="Warning message must be str"):
|
Warning instances (#11959)."""
|
||||||
|
with pytest.raises(TypeError, match="Warning must be str or Warning"):
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
warnings.warn(1) # type: ignore
|
warnings.warn(1) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def test_no_raise_type_error_on_string_warning() -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Check pytest.warns validates warning messages are strings (#10865)."""
|
"message",
|
||||||
with pytest.warns(UserWarning):
|
[
|
||||||
warnings.warn("Warning")
|
pytest.param("Warning", id="str"),
|
||||||
|
pytest.param(UserWarning(), id="UserWarning"),
|
||||||
|
pytest.param(Warning(), id="Warning"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None:
|
||||||
|
"""Check pytest.warns validates warning messages are strings (#10865) or
|
||||||
|
Warning instances (#11959)."""
|
||||||
|
with pytest.warns(Warning):
|
||||||
|
warnings.warn(message)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
hasattr(sys, "pypy_version_info"),
|
hasattr(sys, "pypy_version_info"),
|
||||||
reason="Not for pypy",
|
reason="Not for pypy",
|
||||||
)
|
)
|
||||||
def test_raise_type_error_on_non_string_warning_cpython() -> None:
|
def test_raise_type_error_on_invalid_warning_message_cpython() -> None:
|
||||||
# Check that we get the same behavior with the stdlib, at least if filtering
|
# Check that we get the same behavior with the stdlib, at least if filtering
|
||||||
# (see https://github.com/python/cpython/issues/103577 for details)
|
# (see https://github.com/python/cpython/issues/103577 for details)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
|
|
Loading…
Reference in New Issue