recwarn.py
Overview
The `recwarn.py` module provides utilities for **recording, asserting, and managing Python warnings during test execution** within the pytest framework. It offers a fixture and context managers that enable test functions to capture warnings emitted by the code under test, verify that expected warnings occur, optionally match warning messages using patterns, and fail tests when warnings are missing or unexpected.
Warnings recorded are instances of Python’s `warnings.WarningMessage` and include the warning category, message, source location, and other metadata. This module enhances test reliability and clarity by integrating warnings into pytest’s assertion and reporting mechanisms.
Classes, Functions, and Methods
Fixture: recwarn
@fixture
def recwarn() -> Generator[WarningsRecorder]:
Purpose: Provides a pytest fixture that yields a
WarningsRecorderinstance during a test, capturing all warnings emitted.Usage: Inject
recwarninto a test function to access and assert warnings raised during the test.Returns: A
WarningsRecorderobject that records warnings.Example:
def test_deprecation(recwarn): import warnings warnings.warn("deprecated feature", DeprecationWarning) w = recwarn.pop(DeprecationWarning) assert "deprecated" in str(w.message)
Function: deprecated_call
def deprecated_call(
func: Callable[..., Any] | None = None,
*,
match: str | re.Pattern[str] | None = None,
**kwargs: Any
) -> WarningsRecorder | Any:
Purpose: Assert that code triggers a deprecation-related warning (
DeprecationWarning,PendingDeprecationWarning, orFutureWarning).Usage:
As a context manager to wrap code expected to emit a deprecation warning.
As a function wrapper: pass a callable and its arguments; the function is called and its return value returned, asserting the warning occurs.
Parameters:
func(optional): The callable to invoke.match(optional): String or regex pattern to match the warning message.*args,**kwargs: Arguments to pass tofuncif provided.
Returns:
If used as a context manager: a
WarningsRecorderinstance.If used as a function wrapper: the return value of
func.
Example (context manager):
with deprecated_call(match="use v3"): some_old_api()Example (function call):
result = deprecated_call(some_old_api, arg1, arg2)
Function: warns
def warns(
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
*args: Any,
match: str | re.Pattern[str] | None = None,
**kwargs: Any,
) -> WarningsChecker | Any:
Purpose: Assert that code raises warnings of a particular class or classes.
Usage:
As a context manager to wrap code expected to raise warnings.
As a function wrapper to call a function and assert it produces warnings.
Parameters:
expected_warning: Warning class or tuple of classes to expect (default:Warning).func(optional): Callable to invoke if provided as the first positional argument.*args,**kwargs: Arguments for the callable.match(optional): String or regex to filter warnings matching the message.
Returns:
If used as a context manager: a
WarningsCheckerinstance.If used as a function wrapper: the return value of
func.
Usage Example (context manager):
with warns(RuntimeWarning, match="deprecated"): warnings.warn("deprecated usage", RuntimeWarning)Usage Example (function call):
result = warns(UserWarning, some_func, arg1, kwarg1=value)
Class: WarningsRecorder
class WarningsRecorder(warnings.catch_warnings):
Purpose: Context manager that records all warnings emitted during its scope.
Inherits From: Python’s built-in
warnings.catch_warningswithrecord=True.Key Attributes:
_list: Internal list storingwarnings.WarningMessageobjects.
Properties:
.list: Returns the list of recorded warnings.
Methods:
__getitem__(i): Get the warning at indexi.__iter__(): Iterate over recorded warnings.__len__(): Number of recorded warnings.pop(cls=Warning): Remove and return the first warning of exact or subclass match forcls, raisingAssertionErrorif none found.clear(): Clear all recorded warnings.__enter__(): Enter the context manager, activate warning capture.__exit__(): Exit the context manager, deactivate capture.
Usage:
with WarningsRecorder() as wrec: warnings.warn("This is a warning") assert any(isinstance(w.message, Warning) for w in wrec.list)Notes:
The
popmethod prioritizes exact matches but allows subclass matches if no exact match is found.The context manager is reusable and guards against multiple entries/exits.
Class: WarningsChecker
@final
class WarningsChecker(WarningsRecorder):
Purpose: Extends
WarningsRecorderto assert that expected warnings occurred during the context and optionally match a regex.Key Parameters:
expected_warning: Warning class or tuple of warning classes expected.match_expr: Optional string or compiled regex pattern that warning messages must match.
Methods:
matches(warning): ReturnsTrueif the warning matches the expected warning class and optional regex.__exit__: On exiting the context, checks recorded warnings:Fails the test if no expected warnings were raised.
Fails if expected warnings raised but none matched the pattern.
Re-emits unmatched warnings to avoid hiding unexpected warnings.
Failure Behavior: Uses pytest’s
fail()to abort tests on failures.Example:
with WarningsChecker(UserWarning, match="deprecated") as wc: warnings.warn("deprecated usage", UserWarning)Important:
Re-emits unmatched warnings after checking.
Raises a
TypeErrorif warning messages are not strings or proper warning instances.Suppresses control flow exceptions like
pytest.ExitorSystemExitfrom causing false failures.
Important Implementation Details and Algorithms
Warning Recording: Inherits from
warnings.catch_warningswithrecord=True, which collects all warnings emitted in the context into a list.Matching Warnings:
WarningsChecker.matchesuses subclass checks (issubclass) to allow matching derived warning classes. It uses Python regexre.searchto allow partial matching of warning messages.Warning Assertion Logic: On context exit,
WarningsCheckerinspects recorded warnings:If no warnings of the expected type are found, it fails.
If warnings of the expected type are found but none match the regex, it fails.
Regardless of success or failure, all unmatched warnings are re-emitted using
warnings.warn_explicit()so they continue to be visible to pytest and other handlers.
Handling Edge Cases:
The module guards against entering or exiting the
WarningsRecordercontext multiple times, raisingRuntimeError.It performs type checks on warning classes and messages to ensure consistency.
Special handling ensures that control flow exceptions (like pytest’s
Exit) do not cause false failures.
Type Annotations and Overloads: The
warnsanddeprecated_callfunctions are overloaded to support both context manager and callable invocation forms, improving usability.
Interactions with Other Parts of the System
pytest Fixture System:
recwarnis a pytest fixture that test functions can request to obtain aWarningsRecorder.pytest Warning Reporting: The warnings recorded by
WarningsRecorderand checked byWarningsCheckerintegrate with pytest’s internal warning reporting hooks (pytest_warning_recorded), allowing warnings to be reported in test output.Helper Functions:
warnsanddeprecated_callare user-facing helpers built on top ofWarningsCheckerandWarningsRecorderto simplify warning assertions in tests.Dependency on
_pytest.deprecatedand_pytest.outcomes: Uses internal pytest helpers likecheck_ispytestto validate internal state andfail/Exitto manage test failures and control flow.Python Standard Library: Utilizes Python’s
warningsmodule,refor regex matching, andpprint.pformatfor formatting messages.
Visual Diagram: Class Structure of recwarn.py
classDiagram
class WarningsRecorder {
- _list: list[WarningMessage]
- _entered: bool
+ list: list[WarningMessage]
+ __getitem__(int) : WarningMessage
+ __iter__() : Iterator[WarningMessage]
+ __len__() : int
+ pop(cls: type) : WarningMessage
+ clear() : None
+ __enter__() : Self
+ __exit__(exc_type, exc_val, exc_tb) : None
}
class WarningsChecker {
- expected_warning: tuple[type]
- match_expr: str | re.Pattern | None
+ __init__(expected_warning, match_expr, _ispytest=False)
+ matches(warning: WarningMessage) : bool
+ __exit__(exc_type, exc_val, exc_tb) : None
}
WarningsChecker --|> WarningsRecorder
Summary
The `recwarn.py` module is a crucial component of pytest's warning management system. It provides:
The
recwarnfixture to record warnings during tests.The
WarningsRecorderclass to accumulate warnings contextually.The
WarningsCheckerclass to assert that expected warnings occurred, optionally matching message patterns, and re-emitting unmatched warnings to keep test output transparent.Helper functions
warnsanddeprecated_callfor convenient warning assertions, both as context managers and function wrappers.
This module ensures that Python warnings are first-class citizens in pytest test suites, allowing developers to write precise tests that verify the presence or absence of warnings related to deprecated features, future compatibility, and other runtime conditions.
Example Usage Snippets
Using recwarn fixture
def test_deprecated_api(recwarn):
import warnings
warnings.warn("deprecated!", DeprecationWarning)
w = recwarn.pop(DeprecationWarning)
assert "deprecated" in str(w.message)
Using warns as context manager
with warns(UserWarning, match="value is deprecated"):
warnings.warn("value is deprecated", UserWarning)
Using deprecated_call as function wrapper
def old_function():
warnings.warn("use new_function instead", DeprecationWarning)
return 42
result = deprecated_call(old_function)
assert result == 42