Unraisable Exception Management
Purpose
Unraisable Exception Management addresses the challenge of capturing and reporting exceptions that Python’s runtime silently ignores. These "unraisable exceptions" typically occur in contexts where exceptions cannot be propagated normally—such as during object finalization (`__del__` methods) or in background cleanup operations. Without special handling, such exceptions are lost and can obscure underlying issues in test code.
This subtopic provides mechanisms to intercept these exceptions via Python’s `sys.unraisablehook`, collect their context and tracebacks, and then surface them as warnings during test execution. This ensures that even exceptions ignored by Python’s default mechanisms are visible to users, improving test diagnostics and reliability.
Functionality
The core workflow of unraisable exception management involves:
Hook Installation: On pytest configuration, the system replaces Python’s
sys.unraisablehookwith a custom handler that intercepts unraisable exceptions.Exception Capture and Metadata Storage: The custom hook collects detailed metadata about each unraisable exception, including formatted tracebacks and memory allocation snapshots (via tracemalloc), and appends them to a stash keyed to the pytest configuration.
Garbage Collection Triggering: To uncover exceptions that may be delayed in finalization, pytest triggers multiple rounds of garbage collection during cleanup phases. This "harder" GC helps surface pending unraisable exceptions.
Warning Emission: Before and after each test phase (setup, call, teardown), pytest processes the collected unraisable exceptions, issuing
PytestUnraisableExceptionWarningwarnings containing rich diagnostic information. If warnings are treated as errors (e.g., with-Werror), they are raised to fail the test accordingly.Cleanup and Restoration: After the test session or relevant phases, the system restores the original
sys.unraisablehookto avoid side effects outside pytest runs and cleans up internal storage.
This approach ensures unraisable exceptions are systematically detected and reported, integrating seamlessly into pytest’s warning and reporting infrastructure.
Key Code Snippets Illustrating Core Steps
**Hook installation on pytest configuration:**
def pytest_configure(config: Config) -> None:
prev_hook = sys.unraisablehook
deque = collections.deque()
config.stash[unraisable_exceptions] = deque
config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook))
sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append)
**Custom unraisable hook capturing exception metadata:**
def unraisable_hook(unraisable, *, append) -> None:
msg = ... # formatted summary + traceback + tracemalloc info
cause_msg = ...
append(UnraisableMeta(msg=msg, cause_msg=cause_msg, exc_value=unraisable.exc_value))
**Collecting and emitting warnings for unraisable exceptions:**
def collect_unraisable(config: Config) -> None:
while True:
meta = config.stash[unraisable_exceptions].pop()
warnings.warn(pytest.PytestUnraisableExceptionWarning(meta.msg))
Integration
Unraisable Exception Management integrates tightly with the parent topic of warnings and unraisable exception handling by:
Extending pytest’s warning capture system to include exceptions that normally bypass standard exception handling.
Coordinating with pytest’s test lifecycle hooks (
pytest_runtest_setup,pytest_runtest_call,pytest_runtest_teardown) to flush and report unraisable exceptions consistently around each test phase.Using the pytest
stashmechanism for per-session storage, ensuring collected exceptions persist until reported without interfering with other components.Leveraging the standard warnings framework, so users can control visibility and behavior of these exception warnings with existing pytest and Python options.
Complementing other subtopics like Warning Capture and Assertion by filling the gap of exceptions invisible to normal test error reporting.
This subtopic introduces a unique mechanism—replacing and managing `sys.unraisablehook`—not covered in other parts of the warnings or exception handling system, providing comprehensive visibility into otherwise silent test failures.
Diagram
sequenceDiagram
participant Pytest as Pytest Config
participant Sys as sys.unraisablehook
participant GC as Garbage Collector
participant Stash as Unraisable Exception Stash
participant Warn as Warning System
participant Test as Test Execution Phases
Pytest->>Sys: Replace unraisablehook with custom handler
Note right of Sys: Custom hook captures unraisable exceptions\nadds metadata to stash
Test->>GC: Trigger garbage collection during cleanup
GC->>Sys: Finalize objects, unraisable exceptions may occur
Sys->>Stash: Append exception metadata
Test->>Pytest: At setup/call/teardown phases
Pytest->>Stash: Pop collected exceptions
Pytest->>Warn: Emit PytestUnraisableExceptionWarning for each
Warn->>User: Display warnings or raise errors if configured
Pytest->>Sys: Restore original unraisablehook on cleanup
This sequence diagram illustrates how pytest installs a custom unraisable exception hook, collects exceptions during garbage collection and test execution phases, emits warnings for users, and finally restores the original system hook.