Skip to content

Customization🔗

Deprecation Messages and Templates🔗

pyDeprecate picks a deprecation message template automatically based on how you configured the decorator. Override it with message_template when the defaults do not fit.

Default templates🔗

Three built-in templates cover the common scenarios:

Template When it fires Example output
TEMPLATE_WARNING_CALLABLE target is a callable (function forwarding) The `score` was deprecated since v1.0 in favor of `pkg.score_predictions`. It will be removed in v2.0.
TEMPLATE_WARNING_ARGUMENTS TargetMode.ARGS_REMAP with args_mapping and the caller passes a deprecated argument The `my_func` uses deprecated arguments: `lr` -> `learning_rate`. They were deprecated since v1.0 and will be removed in v2.0.
TEMPLATE_WARNING_NO_TARGET TargetMode.NOTIFY (notice-only, no forwarding) The `legacy_func` was deprecated since v1.0. It will be removed in v2.0.

The selection logic is:

  1. If target is a callable (function, method, or class) → TEMPLATE_WARNING_CALLABLE
  2. If TargetMode.ARGS_REMAP and the caller passes a deprecated argument from args_mapping → TEMPLATE_WARNING_ARGUMENTS
  3. If TargetMode.NOTIFY → TEMPLATE_WARNING_NO_TARGET

When you provide message_template, your custom template replaces whichever default would have been chosen.

Placeholder variables🔗

Custom templates use Python %-style formatting (%(key)s). Available placeholders depend on the deprecation type:

Placeholder Available when Value
source_name Always Name of the deprecated function (e.g. "score")
source_path Always Fully qualified path (e.g. "mypackage.score")
target_name target is callable Name of the replacement function
target_path target is callable Fully qualified path of the replacement
deprecated_in Always Value of deprecated_in parameter
remove_in Always Value of remove_in parameter
argument_map TargetMode.ARGS_REMAP with args_mapping Formatted string like `old` -> `new`

With deprecated_class and deprecated_instance, source_path depends on what the proxy wraps:

  • a class (or a plain function) gives its module-qualified path, e.g. "mypackage.LegacyConfig";
  • an instance has no module of its own, so the path is the proxy's name, e.g. "config_dict";
  • a deprecated attrs_mapping attribute appends the attribute name, e.g. "mypackage.LegacyConfig.old_attr" (its source_name is the attribute name).

Custom template example🔗

from deprecate import TargetMode, deprecated


# NEW API — detects objects and returns a scaled count
def detect_objects(x: int) -> int:
    return x * 10


# DEPRECATED API — `detect` replaced by `detect_objects`
@deprecated(
    target=detect_objects,
    deprecated_in="2.0",
    remove_in="3.0",
    message_template=("[MIGRATION] `%(source_name)s` is removed in v%(remove_in)s. Switch to `%(target_path)s`."),
)
def detect(x: int) -> int:
    pass


# Emits: [MIGRATION] `detect` is removed in v3.0. Switch to `your_module.detect_objects`.
result = detect(5)
print(result)
Output: detect(5)
50

For argument deprecation, a custom template that references the mapping:

from deprecate import TargetMode, deprecated


@deprecated(
    target=TargetMode.ARGS_REMAP,
    deprecated_in="1.5",
    remove_in="2.0",
    args_mapping={"lr": "learning_rate"},
    message_template="%(source_name)s: renamed args %(argument_map)s (since v%(deprecated_in)s, removal v%(remove_in)s)",
)
def train(lr: float = 0.01, learning_rate: float = 0.01) -> float:
    return learning_rate


# Emits: train: renamed args `lr` -> `learning_rate` (since v1.5, removal v2.0)
print(train(lr=0.001))
Output: train(lr=0.001)
0.001

Deprecation Output Sink (stream)🔗

stream controls where deprecation messages go. It accepts a callable that can receive the warning message, or None to silence output entirely. pyDeprecate probes the callable signature to see whether it can also receive stacklevel; if that signature cannot be inspected, it falls back to calling the stream with the message only. In that fallback, a custom stream that forwards to warnings.warn cannot receive pyDeprecate's caller stack level.

The stream runs exactly once per warning — a TypeError it raises is never retried — so the decision is made from its signature before the call:

  • A stream that can take stacklevel as a keyword (a stacklevel parameter, or **kwargs) is called as stream(msg, stacklevel=...); any other stream is called as stream(msg).
  • A stream that declares **kwargs is therefore handed stacklevel= and must accept or drop it: forward it to a callable that takes it (warnings.warn, logging.warning), never to one that rejects it, such as print(msg, **kwargs), which raises TypeError. To receive the message alone, declare the stream without **kwargs.
  • A bare (*args, **kwargs) pass-through built with functools.wraps is judged by the callable it wraps, so an adapter around a message-only sink stays message-only. A narrow stream that only copied another callable's metadata is judged by its own parameters.

Default: FutureWarning via warnings.warn🔗

By default, stream is functools.partial(warnings.warn, category=FutureWarning). In practice this means:

  • Deprecation notices appear as FutureWarning, visible by default in scripts and interactive sessions.
  • Standard warning filters apply — suppress with warnings.filterwarnings("ignore", category=FutureWarning) when needed.
  • The warning points at your line that calls the deprecated API, not at pyDeprecate's wrapper code: the location is measured when the warning fires, skipping every frame inside the deprecate package, so module-scoped warning filters and -W error tracebacks name your code. A custom stream receives the same stacklevel; one written in Python that forwards it to warnings.warn adds its own frame and lands one frame short — see Troubleshooting.

Silencing deprecation output entirely🔗

Pass stream=None to disable all deprecation output for a specific function. Call forwarding still works — only the message is suppressed. This is useful for internal wrappers that exist solely for backwards compatibility without user-facing noise.

Testing gotcha: stream=None suppresses FutureWarning

When stream=None is set, no FutureWarning is emitted, so pytest.warns(FutureWarning) will fail. Test the call-forwarding result directly instead of asserting the warning. See Testing Deprecated Code for patterns.

from deprecate import deprecated


def _compute(x: int) -> int:
    return x + 1


@deprecated(target=_compute, deprecated_in="1.0", remove_in="2.0", stream=None)
def _compute_raw(x: int) -> int:
    pass


# No warning emitted, but call is still forwarded to _compute
print(_compute_raw(5))
Output: _compute_raw(5)
6

Redirecting to a logger🔗

Pass logging.warning (or any logging level method) to route deprecation messages through Python's logging system. This plugs straight into your existing log aggregation, filtering, and formatting.

import logging
from deprecate import deprecated

logging.basicConfig(level=logging.WARNING)


# NEW API — sorts a list and returns the sorted copy
def sort_items(data: list) -> list:
    return sorted(data)


# DEPRECATED API — `process_batch` replaced by `sort_items`
@deprecated(
    target=sort_items,
    deprecated_in="1.0",
    remove_in="2.0",
    stream=logging.warning,
)
def process_batch(data: list) -> list:
    pass


# Instead of a FutureWarning, this emits a WARNING-level log line:
#   WARNING:root:The `process_batch` was deprecated since v1.0 in favor of `your_module.sort_items`.
#   It will be removed in v2.0.
print(process_batch([3, 1, 2]))
Output: process_batch([3, 1, 2])
[1, 2, 3]

Pick the log level that matches the urgency:

  • logging.warning — standard choice; visible in default configs
  • logging.error — critical deprecations nearing removal deadline
  • logging.info — low-priority deprecations during early migration

Combine num_warns=-1 with stream=logging.warning for migration tracking

With unlimited notices routed to your logger, every deprecated call site appears in your log aggregation system (ELK, Datadog, CloudWatch). Query the logs to measure migration progress and find remaining callers before the removal deadline.

Using print for simple console output🔗

For quick debugging or scripts where you want immediate stdout output without the warnings module:

from deprecate import deprecated


# NEW API — formats a greeting string for the given name
def say_hello(name: str) -> str:
    return f"Hello, {name}!"


# doc-example only: forces realistic path in warning; real modules set __module__ automatically
say_hello.__module__ = "your_module"


# DEPRECATED API — `greet` replaced by `say_hello`
@deprecated(target=say_hello, deprecated_in="1.0", remove_in="2.0", stream=print)
def greet(name: str) -> str:
    pass


# Prints directly to stdout:
#   The `greet` was deprecated since v1.0 in favor of `your_module.say_hello`.
#   It will be removed in v2.0.
print(greet("World"))
Output: greet("World")
The `greet` was deprecated since v1.0 in favor of `your_module.say_hello`. It will be removed in v2.0.
Hello, World!

Custom stream callable🔗

Any callable accepting the message as a single string argument works; one that also declares stacklevel or **kwargs is additionally passed stacklevel= and must accept or drop it (see Deprecation Output Sink). Here is an example that collects deprecation messages into a list for later processing:

from deprecate import deprecated

collected_warnings: list = []


def collector(msg: str) -> None:
    collected_warnings.append(msg)


# NEW API — doubles the input value
def target_fn(x: int) -> int:
    return x * 2


# doc-example only: forces realistic path in warning; real modules set __module__ automatically
target_fn.__module__ = "your_module"


# DEPRECATED API — `source_fn` replaced by `target_fn`
@deprecated(target=target_fn, deprecated_in="1.0", remove_in="2.0", stream=collector)
def source_fn(x: int) -> int:
    pass


source_fn(10)
print(collected_warnings)
Output: print(collected_warnings)
['The `source_fn` was deprecated since v1.0 in favor of `your_module.target_fn`. It will be removed in v2.0.']

See also🔗

  • Use Cases — worked examples of all deprecation patterns including stream and template usage
  • Audit Tools — complement custom streams with CI enforcement of removal deadlines
  • Troubleshooting — how to redirect deprecation output to a Python logger instead of warnings.warn

Next: Audit Tools — validate decorator configuration, enforce removal deadlines, and detect deprecation chains in CI.