Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,6 @@ repos:
entry: python scripts/validate_unwanted_patterns.py --validation-type="bare_pipe_alternation_in_message"
types: [python]
files: ^pandas/tests/
- id: unwanted-patterns-bare-assert-produces-warning
name: Check that assert_produces_warning also checks the warning message
language: python
entry: python scripts/validate_unwanted_patterns.py --validation-type="bare_assert_produces_warning"
types: [python]
files: ^pandas/tests/
- id: no-return-exception
name: Use raise instead of return for exceptions
language: pygrep
Expand Down
3 changes: 3 additions & 0 deletions doc/source/development/contributing_codebase.rst
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,9 @@ and specify the warning message using the ``match`` argument.
with tm.assert_produces_warning(DeprecationWarning, match="the warning message"):
pd.deprecated_function()

Both arguments are required whenever a warning is expected. In the rare case where
the message genuinely cannot be asserted, pass ``match=None`` to opt out.

If a warning should specifically not happen in a block of code, pass ``False`` into the context manager.

.. code-block:: python
Expand Down
37 changes: 27 additions & 10 deletions pandas/_testing/_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
)
import warnings

from pandas._libs import lib

if TYPE_CHECKING:
from collections.abc import (
Generator,
Expand All @@ -25,13 +27,13 @@

@contextmanager
def assert_produces_warning(
expected_warning: type[Warning] | bool | tuple[type[Warning], ...] | None = Warning,
expected_warning: type[Warning] | bool | tuple[type[Warning], ...] | None,
filter_level: Literal[
"error", "ignore", "always", "default", "module", "once"
] = "always",
check_stacklevel: bool = True,
raise_on_extra_warnings: bool = True,
match: str | tuple[str | None, ...] | None = None,
match: str | tuple[str | None, ...] | lib.NoDefault | None = lib.no_default,
Comment thread
mroeschke marked this conversation as resolved.
must_find_all_warnings: bool = True,
) -> Generator[list[warnings.WarningMessage]]:
"""
Expand All @@ -42,7 +44,7 @@ def assert_produces_warning(

Parameters
----------
expected_warning : {Warning, False, tuple[Warning, ...], None}, default Warning
expected_warning : {Warning, False, tuple[Warning, ...], None}
The type of Exception raised. ``exception.Warning`` is the base
class for all warnings. To raise multiple types of exceptions,
pass them as a tuple. To check that no warning is returned,
Expand All @@ -68,8 +70,10 @@ class for all warnings. To raise multiple types of exceptions,
raise_on_extra_warnings : bool, default True
Whether extra warnings not of the type `expected_warning` should
cause the test to fail.
match : {str, tuple[str, ...]}, optional
Match warning message. If it's a tuple, it has to be the size of
match : {str, tuple[str, ...]} or None
Match warning message. Required whenever a warning is expected; pass
``match=None`` to opt out when the message genuinely cannot be
asserted. If it's a tuple, it has to be the size of
`expected_warning`. If additionally `must_find_all_warnings` is
True, each expected warning's message gets matched with a respective
match. Otherwise, multiple values get treated as an alternative.
Expand All @@ -81,14 +85,14 @@ class for all warnings. To raise multiple types of exceptions,
Examples
--------
>>> import warnings
>>> with assert_produces_warning():
... warnings.warn(UserWarning())
>>> with assert_produces_warning(UserWarning, match="hello"):
... warnings.warn(UserWarning("hello"))
>>> with assert_produces_warning(False):
... warnings.warn(RuntimeWarning())
Traceback (most recent call last):
...
AssertionError: Caused unexpected warning(s): ['RuntimeWarning'].
>>> with assert_produces_warning(UserWarning):
>>> with assert_produces_warning(UserWarning, match="hello"):
... warnings.warn(RuntimeWarning())
Traceback (most recent call last):
...
Expand All @@ -98,6 +102,15 @@ class for all warnings. To raise multiple types of exceptions,
"""
__tracebackhide__ = True

if match is lib.no_default:
if expected_warning:
raise TypeError(
"assert_produces_warning() requires a match argument so the "
"warning message is checked and not just its class. Pass "
"match=None if the message genuinely cannot be asserted here."
)
match = None

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter(filter_level)
try:
Expand Down Expand Up @@ -143,13 +156,17 @@ class for all warnings. To raise multiple types of exceptions,


def maybe_produces_warning(
warning: type[Warning], condition: bool, **kwargs: Any
warning: type[Warning],
condition: bool,
*,
match: str | tuple[str | None, ...] | None,
**kwargs: Any,
) -> AbstractContextManager:
"""
Return a context manager that possibly checks a warning based on the condition
"""
if condition:
return assert_produces_warning(warning, **kwargs)
return assert_produces_warning(warning, match=match, **kwargs)
else:
return nullcontext()

Expand Down
Loading
Loading