Skip to content

Modules🔗

deprecated_module() marks an entire Python module as deprecated by replacing its __class__ with an intercepting wrapper, so every public attribute access on the old module name invokes its FutureWarning path. Standard warning filters can suppress repeated display. Three patterns are supported: warn in place without moving any code (Mode 1), redirect all attribute access to a replacement module (Mode 2), and expose a deprecated module name as a package attribute (Mode 3). For deprecating individual functions see Functions; for classes see Classes.

When to use deprecated_module()🔗

Reach for deprecated_module() when a module is being replaced or renamed and you want every attribute access on the old name to invoke the configured warning path automatically. Place the call at the bottom of the file being deprecated: it reassigns the module's __class__ to an intercepting wrapper and attaches __deprecation_config__ metadata so that find_deprecation_wrappers() can discover it.

Mode 1 — in-place warn🔗

Use this when you want to keep the module in place and invoke its warning path on every attribute access. Call deprecated_module(__name__, ...) at the bottom of old_calculator.py:

# phmdoctest:skip — old_calculator.py in-place warn template
# old_calculator.py — DEPRECATED API; use new_calculator instead
# NEW API — same functions now live in new_calculator.py

from deprecate import deprecated_module


def add(a: float, b: float) -> float:
    """Add two numbers. Prefer new_calculator.add."""
    return a + b


def multiply(a: float, b: float) -> float:
    """Multiply two numbers. Prefer new_calculator.multiply."""
    return a * b


# Mark this module deprecated — call once at the bottom.
# name is optional; when omitted it is auto-detected from the caller's __name__.
# Without message_template, the warning reads:
#   FutureWarning: The `old_calculator` module was deprecated since v2.0. It will be removed in v3.0.
# With message_template (as below), it replaces the built-in notice entirely:
#   FutureWarning: Use `new_calculator` instead.
deprecated_module(
    deprecated_in="2.0",
    remove_in="3.0",
    message_template="Use `new_calculator` instead.",
)

import old_calculator; old_calculator.add(1, 2) invokes the FutureWarning path and returns the result. Every public attribute access — real functions, classes, constants — takes that path because deprecated_module() replaces the module's __class__ with an intercepting wrapper; standard filters can suppress repeated display.

Call it at module top level when omitting name. Auto-detection reads the caller frame's __name__, which only names the intended module when the call sits directly in the module body. Calling it without name from inside a function or class body (e.g. a _setup_deprecations() helper) raises TypeError — the frame's __name__ would point at the enclosing module and silently deprecate all of it. Pass name explicitly to call from any other scope.

Mode 2 — redirect to a replacement module🔗

Use this when you rename an entire module. Attribute lookups that are missing from the old module's __dict__ are forwarded to new_calculator, so callers get both a warning and the correct value. Real attributes already defined in the old module (functions, constants) are served from its __dict__ directly — they warn but are not forwarded.

# phmdoctest:skip — CI template; new_calculator is not installed
# old_calculator.py — DEPRECATED API — redirect to new_calculator
# NEW API — same functions live in new_calculator.py

from deprecate import deprecated_module

# Load the replacement module to attach as redirect target
import new_calculator as _new_calculator


deprecated_module(
    __name__,
    target=_new_calculator,  # every unknown attr forwarded here
    deprecated_in="2.0",
    remove_in="3.0",
    message_template="Use `new_calculator` instead.",
)

Now import old_calculator; old_calculator.add(1, 2) invokes the FutureWarning path and returns the result from new_calculator.add; standard warning filters can suppress repeated display. The attrs_mapping parameter lets you rename specific attributes during the redirect:

# phmdoctest:skip — attrs_mapping continuation; requires new_calculator
# Map old attr name to new attr name, or to None to raise AttributeError.
# Keys are the names callers use today (the original API names).
from deprecate import deprecated_module

deprecated_module(
    __name__,
    target=_new_calculator,
    attrs_mapping={"compute": "add", "beta_feature": None},
    deprecated_in="2.0",
    remove_in="3.0",
)

old_calculator.compute invokes the warning path and forwards to new_calculator.add (the function was renamed). old_calculator.beta_feature invokes it and raises AttributeError (no replacement). Standard filters can suppress repeated display; all other names fall through to new_calculator.

Mode 3 — parent alias via deprecated_instance🔗

When you reorganise a package and want the old sub-module name to remain accessible as an attribute on the parent package, use deprecated_instance() in the parent __init__.py. This does not require a new API — it reuses the existing proxy mechanism.

# phmdoctest:skip — CI template; my_package is not installed
# my_package/__init__.py

import my_package.new_calculator as _new_calculator
from deprecate import deprecated_instance

# DEPRECATED API — expose `old_utils` as a package attribute pointing to new_calculator
old_utils = deprecated_instance(
    _new_calculator,
    name="old_utils",
    deprecated_in="2.0",
    remove_in="3.0",
    message_template="Use `my_package.new_calculator` instead.",
)

import my_package; my_package.old_utils.add(1, 2) invokes the FutureWarning path on the first attribute access and forwards to new_calculator; standard filters can suppress display.

Limiting warning volume with num_warns🔗

By default deprecated_module() delivers a warning on every public attribute access, matching its behavior before num_warns existed. Standard warning filters can still suppress repeated display at the same location. Pass num_warns to cap deliveries — the same semantics as @deprecated's num_warns, just defaulting to -1 (unlimited) instead of 1, since a module's whole purpose is warning on every access unless you opt into a budget:

# phmdoctest:skip — old_calculator.py in-place warn template, budgeted variant
from deprecate import deprecated_module

deprecated_module(
    deprecated_in="2.0",
    remove_in="3.0",
    num_warns=1,  # warn once per process, then stay silent
)

0 never delivers a warning, a positive N delivers exactly N, and -1 (the default) delivers on every access. The counter is one budget shared across every attribute name on the module — there is no per-attribute counter the way deprecated_class's attrs_mapping gets one. -1 avoids budget locking but still performs metadata lookup and warning handling. Use a finite budget for a worker service whose custom stream or warning filter would otherwise record repeated accesses.

Star imports also warn🔗

Star imports (from old_calculator import *) still trigger the deprecation warning. CPython's IMPORT_STAR bytecode calls getattr(module, name) for each public name being pulled in, and that call routes through the same __getattribute__ interception installed by deprecated_module() — so a FutureWarning fires once per pulled-in public name, exactly as it would for old_calculator.add(1, 2). This closes a gap that a PEP 562 module-level __getattr__ hook would otherwise leave open, since __getattr__ only fires for missing names and star imports never trigger it.

Audit integration🔗

find_deprecation_wrappers() discovers modules marked with deprecated_module() the same way it discovers function and class wrappers — by reading the __deprecation_config__ attribute attached to the module object:

# phmdoctest:skip — CI template: replace my_package with your actual package
import my_package
from deprecate import find_deprecation_wrappers

deprecated_items = find_deprecation_wrappers(my_package)
module_items = [r for r in deprecated_items if r.api_type == "module"]

Fatal deprecations🔗

deprecated_module(..., as_exception=True) turns every public attribute access on the module into a raised DeprecatedError — see Fatal deprecations for the full contract, including the process-wide DEPRECATE_AS_EXCEPTIONS switch. It ignores num_warns (every access raises, not just the first) and is not silenced by stream=None.

A module is the broadest surface this feature covers, so expect the blast radius to match: any tool that walks module attributes stops at a fatal module rather than skipping it — pytest collection, Sphinx autodoc, pickle, copy. pyDeprecate's own audit is unaffected, because find_deprecation_wrappers() and the pydeprecate CLI read the module's __dict__ rather than going through attribute access, so a fatal module still appears in discovery and reports like any other.

See also🔗

  • Use Cases overview — start here for a guided tour of all deprecation patterns
  • Functions — deprecating individual functions and methods
  • Classes — class, Enum, dataclass, and instance deprecation
  • Audit Tools — enforce removal deadlines and detect deprecation chains in CI
  • Troubleshooting — common errors and fixes

Next: Advanced — docstring updates, args_extra, testing helpers, class/static methods, generators.