---
id: modules
description: Deprecating an entire Python module — in-place warn intercepting all public attribute access, redirect to a replacement module, and parent alias patterns with their limitations.
---

# 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 emits a `FutureWarning`. 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](functions.md); for classes see [Classes](classes.md).

## 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 warn callers automatically. Place the call at the bottom of the file being deprecated: it reassigns the module's `__class__` to an intercepting wrapper, attaches `__deprecated__` metadata so that [`find_deprecation_wrappers()`](audit.md) can discover it, and emits a `FutureWarning` on every public attribute access.

## Mode 1 — in-place warn

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

```python
# 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)` emits a `FutureWarning` and returns the result. Every public attribute access — real functions, classes, constants — warns because `deprecated_module()` replaces the module's `__class__` with an intercepting wrapper.

> **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.

```python
# 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)` emits a `FutureWarning` and returns the result from `new_calculator.add`. The `attrs_mapping` parameter lets you rename specific attributes during the redirect:

```python
# 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` warns and forwards to `new_calculator.add` (the function was renamed). `old_calculator.beta_feature` warns and raises `AttributeError` (no replacement). 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()`](classes.md#deprecating-constants-and-instances) in the parent `__init__.py`. This does not require a new API — it reuses the existing proxy mechanism.

```python
# 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)` emits a `FutureWarning` on the first attribute access and forwards to `new_calculator`.

## 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 `__deprecated__` attribute attached to the module object:

```python
# 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"]
```

## See also

- [Use Cases overview](use-cases.md) — start here for a guided tour of all deprecation patterns
- [Functions](functions.md) — deprecating individual functions and methods
- [Classes](classes.md) — class, Enum, dataclass, and instance deprecation
- [Audit Tools](audit.md) — enforce removal deadlines and detect deprecation chains in CI
- [Troubleshooting](../troubleshooting.md) — common errors and fixes

______________________________________________________________________

Next: [Advanced](advanced.md) — docstring updates, `args_extra`, testing helpers, class/static methods, generators.
