Skip to content

Getting Started🔗

You renamed a function (or retired an argument) and now you need the old name to keep working, callers to see a clear deprecation notice, and a firm removal date you can enforce. pyDeprecate does all of that with a single decorator so you can focus on the new API instead of the plumbing.

Why pyDeprecate instead of warnings.warn?🔗

warnings.warn tells callers that something is deprecated — but they still have to update their code manually and the old code path keeps running. pyDeprecate adds three things on top:

  • Automatic call forwarding — every call to the old function is transparently redirected to the replacement; no stale code runs.
  • Argument mapping — rename or drop arguments across the API boundary with args_mapping={"old": "new"} to help callers migrate to the new signature.
  • CI deadline enforcement — validate_deprecation_expiry() (via pip install 'pyDeprecate[audit]') returns a message for every wrapper whose removal version has been reached, and pydeprecate expiry exits 1 on any, so CI fails and deprecated code cannot quietly outlive its deadline.

If you only need a one-line notice with no forwarding and no deadline tracking, warnings.warn is sufficient. Choose pyDeprecate when you need the old name to keep working while callers migrate.

Installation🔗

pyDeprecate requires Python 3.9 or later and has zero runtime dependencies. Python 3.9 is past its upstream end of life but is still listed as supported in the package metadata.

Choose the install that matches the workflow you need:

Workflow Command Includes
Runtime deprecation wrappers pip install pyDeprecate @deprecated, @deprecated_class, deprecated_instance, docstring helpers, and most audit metadata helpers
CI deadline checks pip install 'pyDeprecate[audit]' Adds packaging for PEP 440 version comparison in validate_deprecation_expiry() and validate_deprecation_policy()
Command-line audit workflows pip install 'pyDeprecate[audit,cli]' Adds packaging version support for pydeprecate expiry, pydeprecate policy, and pydeprecate all; the CLI itself needs no dependency (cli is an empty extra kept for compatibility)

Base install from PyPI:

pip install pyDeprecate

To install directly from source (for pre-release or development versions):

pip install https://github.com/Borda/pyDeprecate/archive/main.zip

The audit extra adds packaging for version comparison, needed only by validate_deprecation_expiry and validate_deprecation_policy. Install it when you want to enforce removal deadlines or a deprecation policy in CI:

pip install 'pyDeprecate[audit]'

For command-line audit workflows:

pip install 'pyDeprecate[audit,cli]'

Quick Start🔗

Here is the most common scenario: you renamed a function and need the old name to keep working during the transition. The decorator handles call forwarding automatically, so the deprecated wrapper needs no implementation.

from deprecate import deprecated


# NEW/FUTURE API — renamed to be more explicit about what it computes
def compute_sum(a: int = 0, b: int = 3) -> int:
    return a + b


# DEPRECATED API — `addition` was the original name before the rename
@deprecated(target=compute_sum, deprecated_in="1.0", remove_in="2.0")
def addition(a: int, b: int = 5) -> int:
    pass  # body is not needed — calls are forwarded to compute_sum


# Using the original name still works but emits a deprecation notice
print(addition(1, 2))
Output: addition(1, 2)
3

All calls to addition() are automatically forwarded to compute_sum() with a FutureWarning. The old function's body is never executed.

Customizing deprecation messages

To customize the message template or redirect deprecation output to a logger, see Customization.

API at a Glance🔗

Not sure which API to reach for? This table maps common scenarios to the right tool. For worked examples of each, see Use Cases.

Pick the right decorator:

Scenario API to use
Renaming a function or method @deprecated(target=new_func)
Renaming an argument within the same function @deprecated(target=TargetMode.ARGS_REMAP, args_mapping={"old": "new"})
Notice only — original body still runs @deprecated(target=TargetMode.NOTIFY)
Deprecating a class, Enum, or dataclass name @deprecated_class(target=NewClass)
Deprecating a module-level constant or object deprecated_instance(obj, ...)

All @deprecated parameters:

Param Default Purpose
target TargetMode.AUTO Callable to forward to · TargetMode.ARGS_REMAP to remap args · TargetMode.NOTIFY warn-only · TargetMode.AUTO (default) infers the mode from the other arguments
deprecated_in "" Version when deprecated (e.g. "1.0")
remove_in "" Version when removed (e.g. "2.0")
stream deprecation_warning Output sink callable (set None to silence deprecation messages)
num_warns 1 0 never · 1 once · -1 always · N exactly N times
args_mapping None {"old": "new"} rename · {"old": None} drop
message_template None Custom deprecation message template (%-style placeholders)
args_extra None Fixed kwargs injected into the target call
skip_if False bool or Callable → bool; deactivate the deprecation machinery when true
as_exception None Raise DeprecatedError instead of warning; None follows the global deprecate.AS_EXCEPTIONS
update_docstring False Append Sphinx .. deprecated:: notice to docstring
docstring_style "auto" Style of the injected notice: "auto", "rst", "mkdocs", "markdown"

Prefer passing an explicit target (TargetMode.NOTIFY, TargetMode.ARGS_REMAP, or a callable) so the intent is visible at the call site — TargetMode.AUTO is only the front-door default that resolves an omitted target, and you never write it yourself.

All three decorators (@deprecated, deprecated_callable(), deprecated_class()) share every parameter above. The only differences: deprecated_class() adds the class-only attrs_mapping (attribute-name remapping — TypeError on the other two), and a class source is dispatched to deprecated_class by @deprecated but rejected with TypeError by deprecated_callable(). TargetMode.AUTO is front-door-only: deprecated_callable() defaults target to TargetMode.NOTIFY, deprecated_class() leaves it unset, and both raise TypeError when handed TargetMode.AUTO explicitly.

deprecated_instance() shares deprecated_in, remove_in, num_warns, stream, args_extra, message_template, skip_if, and as_exception; it requires obj and adds name (display name) and read_only.

Parameter matrix across the API🔗

Every parameter each factory accepts, with its default. 🚫 marks a parameter the factory does not accept — passing it raises TypeError. required marks a parameter with no default.

Parameter @deprecated deprecated_callable deprecated_class deprecated_instance deprecated_module
obj 🚫 🚫 🚫 required 🚫
name 🚫 🚫 🚫 "" None
target TargetMode.AUTO TargetMode.NOTIFY None 🚫 None
args_mapping None None None 🚫 🚫
attrs_mapping 🚫 🚫 None 🚫 None
args_extra None None None None 🚫
deprecated_in "" "" "" "" ""
remove_in "" "" "" "" ""
num_warns 1 1 1 1 -1
stream deprecation_warning deprecation_warning deprecation_warning deprecation_warning <warnings.warn>
message_template None None None None None
skip_if False False False False 🚫
read_only 🚫 🚫 🚫 False 🚫
as_exception None None None None None
update_docstring False False False 🚫 🚫
docstring_style "auto" "auto" "auto" 🚫 🚫

Notes: deprecation_warning is the default FutureWarning sink (stream=None silences it). deprecated_module is the exception: omit stream to warn through warnings.warn; an explicit stream=None still warns there and emits a FutureWarning at the call, because it silences the module warning only from v1.0. name is a display label for deprecated_instance and the module's __name__ for deprecated_module (auto-detected from the caller when omitted). message_template is the custom-notice override on every factory; template_mgs is its deprecated alias (renamed in v0.12; emits a FutureWarning, removed in v1.0) — pass message_template instead. The front door @deprecated exposes only the parameters common to deprecated_callable and deprecated_class; the class-only attrs_mapping is 🚫 there — use deprecated_class(attrs_mapping=...) directly.


Enforce and automate🔗

Deprecation lifecycle cycle: old API in blue, new API and decorator in yellow, callers migrate, pre-commit, CI and pytest flag the expired wrapper, delete it and repeat

  • Fatal deprecations — as_exception=True (or deprecate.as_exceptions() / DEPRECATE_AS_EXCEPTIONS=1) raises DeprecatedError instead of warning, so a test run can refuse deprecated calls. See Fatal deprecations.
  • CLI — pydeprecate check, expiry, policy, chains and all audit a package from the shell with CI-friendly exit codes. See the CLI Reference.
  • pre-commit and GitHub Action — run the same checks as a pre-commit hook or a GitHub Action step.

Next: Use Cases — overview of all deprecation patterns with links to each topic. See also: Customization · Audit Tools · Troubleshooting