catch_warnings: Capture Python Warnings in Tests

Published on: October 7, 2026
Reading time: 5 minutes
Python code showing warnings controlled with catch_warnings

The warnings.catch_warnings context manager gives Python applications temporary control over warnings in tests, libraries, migration scripts, and legacy integrations. Instead of changing warning filters permanently, it saves the current state, applies rules inside a block, and restores the previous configuration when the block ends. This makes it useful for capturing, converting, ignoring, or validating warnings without affecting unrelated code.

Warnings are not exceptions. They report situations that deserve attention, such as deprecated APIs, behavior scheduled to change, possible precision loss, or questionable resource usage. Execution normally continues. Mature tests should therefore verify not only return values but also the warnings produced by an operation.

Basic usage

import warnings

with warnings.catch_warnings(record=True) as captured:
    warnings.simplefilter("always")
    warnings.warn("old feature", DeprecationWarning)

assert len(captured) == 1
assert captured[0].category is DeprecationWarning
assert "old feature" in str(captured[0].message)

With record=True, the context returns a list of WarningMessage objects. Each entry contains the message, category, file name, line number, and other details. The always filter is important in tests because Python may otherwise suppress repeated warnings through its internal warning registry.

Why state restoration matters

Calls such as warnings.simplefilter and warnings.filterwarnings modify warning state. If one test ignores every warning and fails to restore the configuration, later tests can pass incorrectly. The context manager reduces that risk by restoring filters even if an exception leaves the block.

Restoration does not mean every use is automatically concurrency-safe. Warning configuration can involve shared state, and behavior should be checked against the Python version used by the application. In multithreaded or asynchronous programs, avoid wide contexts while unrelated execution changes filters. Prefer short blocks, isolated tests, and centralized startup policies.

Capturing one category

import warnings

def legacy_api():
    warnings.warn(
        "legacy_api will be removed",
        DeprecationWarning,
        stacklevel=2,
    )
    return 42

with warnings.catch_warnings(record=True) as captured:
    warnings.simplefilter("always", DeprecationWarning)
    result = legacy_api()

assert result == 42
assert len(captured) == 1
assert issubclass(captured[0].category, DeprecationWarning)

Filtering by category is safer than hiding every warning. A broad ignore rule can conceal ResourceWarning, RuntimeWarning, or important dependency notices. A narrow rule communicates the test’s intention and reduces accidental suppression.

Turning warnings into errors

During testing and continuous integration, treating selected warnings as exceptions can help teams remove deprecated APIs before upgrades break production.

with warnings.catch_warnings():
    warnings.simplefilter("error", DeprecationWarning)
    legacy_api()

The call now raises DeprecationWarning as an exception. This policy works well in controlled test suites, but applying it indiscriminately in production can stop requests because of third-party library warnings. Start with selected categories and modules.

Filtering by message and module

filterwarnings supports detailed criteria including action, a regular expression for the message, warning category, module expression, and line number.

with warnings.catch_warnings(record=True) as captured:
    warnings.filterwarnings(
        "always",
        message=r".*old parameter.*",
        category=DeprecationWarning,
        module=r"my_package\..*",
    )
    run_flow()

Keep expressions simple and stable. Tests tied to the complete wording of a dependency can fail after harmless editorial changes. Prefer checking the category, a meaningful fragment, and the source.

The role of stacklevel

Library authors should set stacklevel so the warning points to the caller’s code rather than the internal line that invokes warnings.warn. A value of two commonly points one frame above, although wrappers may require a larger number.

def new_name():
    return 10

def old_name():
    warnings.warn(
        "use new_name()",
        DeprecationWarning,
        stacklevel=2,
    )
    return new_name()

A correctly located warning reduces investigation time. Tests can validate filename and lineno when source location is part of the public contract.

Warning registries

Python maintains registries to avoid showing certain messages repeatedly. This explains why a warning can appear once and disappear in later calls. In tests, simplefilter("always") inside catch_warnings makes behavior more deterministic. Imported modules may still have their own registries, so suites should avoid relying on execution order.

Integration with logging

logging.captureWarnings(True) redirects warnings to Python’s logging system. This is useful for services, but it differs from collecting a list for assertions. Unit tests often benefit from catch_warnings(record=True), while production logging provides timestamps, correlation information, and centralized destinations.

Good practices for library authors

Choose categories deliberately. DeprecationWarning is commonly used for developer-facing removal notices, while FutureWarning may fit changes that directly affect end users. Custom categories can separate domain-specific notices. Document when the warning was introduced, the recommended replacement, and the planned removal window.

Do not use a warning when the result is invalid; raise an exception instead. Avoid emitting the same warning repeatedly inside hot loops, because it creates noise and additional overhead.

Testing for unexpected warnings

with warnings.catch_warnings(record=True) as captured:
    warnings.simplefilter("always")
    run_stable_operation()

unexpected = [w for w in captured if w.category is not UserWarning]
assert not unexpected

This pattern accepts a known category while rejecting others. Large projects can wrap it in reusable helpers that provide clearer failure messages and consistent filtering.

Concurrency and scope

The main operational risk is assuming filters are local while another execution path changes the same warning state. Keep contexts short and avoid surrounding long network calls, sleeps, or parallel processing. In servers, define the broad warning policy at startup and reserve temporary capture mostly for tests and controlled jobs.

When to use catch_warnings

Use it to test deprecations, validate library behavior, silence one known warning in a narrow section, convert selected categories into errors, or inspect message and origin. Do not use it merely to make a terminal look clean. Repeated warnings usually indicate an outdated dependency, old API, or behavior that needs correction.

For more information, read the official warnings documentation and the section about logging integration. On Academify, continue with the sections about Python, Python testing, programming, and the Python course.

Conclusion

warnings.catch_warnings provides temporary and testable control over Python warnings. Reliable usage combines specific filters, short scopes, record=True for assertions, correct stacklevel values when emitting notices, and caution around shared state. With these practices, warnings become useful signals for quality, compatibility, and maintenance instead of background noise.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Binary code representing Python buffers and memory views
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: Count Values Without Buffer Copies

    Learn Python memoryview.count to count bytes and values in buffers without copies, with formats, limits, and practical safety.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code and type annotations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Avoid Circular Imports in Annotations

    Learn Python annotationlib to inspect deferred annotations, avoid circular imports, and build safer runtime tooling.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code on screen representing module and package inspection
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    inspect.ispackage: Identify Python Packages

    Learn Python inspect.ispackage to identify packages, explore module trees, and build safer introspection and plugin tools.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026
    Laptop displaying code and performance graphs for Python sys._jit analysis
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys._jit: Detect and Measure Experimental JIT

    Learn Python sys._jit to detect experimental JIT support, benchmark it correctly, and avoid fragile runtime decisions.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026
    Numerical precision visualization for Python math.fma calculations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    math.fma: Fused Multiply-Add with One Rounding

    Learn Python math.fma for fused multiply-add calculations with one rounding step and better numerical stability.

    Ler mais

    Tempo de leitura: 6 minutos
    04/10/2026