Compare Python Deprecation Tools🔗
Python has several ways to signal deprecation. The right choice depends on whether you need only a warning, static type checker visibility, or full runtime compatibility during an API migration.
Quick comparison🔗
| Tool | Best for | Runtime forwarding | Generator / async | Argument rename | Argument deprecation | Class/object alias | Testing helpers | CI audit | Static checker signal |
|---|---|---|---|---|---|---|---|---|---|
warnings.warn |
One-off internal warnings | No | No | No | No | No | No | No | No |
warnings.deprecated |
Python 3.13+ static-checker visibility | No | No | No | No | No | No | No | Yes |
deprecation |
Simple decorator warnings | No | No | No | No | No | @fail_if_not_removed |
No | No |
Deprecated |
Simple decorator warnings | No | No | No | No | No | No | No | No |
| pyDeprecate (this library) | Full API migration compatibility | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Via stacking |
Use warnings.warn when🔗
Use raw warnings for small internal messages where callers do not need a compatibility shim.
import warnings
def internal_hook() -> None:
warnings.warn("internal_hook is deprecated", DeprecationWarning, stacklevel=2)
Use warnings.deprecated when🔗
Use warnings.deprecated/typing_extensions.deprecated for static-checker-only visibility when runtime behavior does not need to change.
from typing_extensions import deprecated
@deprecated("Use new_api instead.")
def old_api() -> None:
pass
Use pyDeprecate when🔗
Use pyDeprecate when old callers must keep working while receiving a deprecation warning.
from deprecate import deprecated
def new_api(value: int) -> int:
return value + 1
@deprecated(
target=new_api,
deprecated_in="1.2",
remove_in="2.0",
# FutureWarning: "The `old_api` was deprecated since v1.2 in favor of `new_api`. It will be removed in v2.0."
)
def old_api(value: int) -> int:
raise RuntimeError("Forwarded by pyDeprecate.")
print(old_api(1))
Migrating from warnings.warn🔗
Replace manual forwarding boilerplate with a single decorator.
Before: manual warning and forwarding
import warnings
def new_api(value: int) -> int:
return value + 1
def old_api(value: int) -> int:
warnings.warn(
"old_api is deprecated; use new_api instead.",
DeprecationWarning,
stacklevel=2,
)
return new_api(value)
print(old_api(1))
After: pyDeprecate owns the migration contract
from deprecate import deprecated
def new_api(value: int) -> int:
return value + 1
@deprecated(
target=new_api,
deprecated_in="1.2",
remove_in="2.0",
# FutureWarning: "The `old_api` was deprecated since v1.2 in favor of `new_api`. It will be removed in v2.0."
)
def old_api(value: int) -> int:
raise RuntimeError("Forwarded by pyDeprecate; this body is not used.")
print(old_api(1))
Decision guide🔗
- Need only an internal warning: use
warnings.warn. - Need static checker visibility only: use
warnings.deprecatedon Python 3.13+. - Need public runtime compatibility: use pyDeprecate.
- Need to deprecate
async deffunctions, generator functions, or async generators: use pyDeprecate. - Need argument rename or dropped argument compatibility: use pyDeprecate with
TargetMode.ARGS_REMAP. - Need class, constant, or object alias compatibility: use
deprecated_classordeprecated_instance. - Need removal deadline checks in CI: use pyDeprecate audit tools.
- One-off internal warning with no forwarding or CI audit:
warnings.warnis sufficient.
Fair strengths in alternative tools worth considering🔗
To keep this comparison fair, here are capabilities where alternatives can be the better fit:
warnings.deprecated: native static diagnostics in mypy/pyright/IDEs without adding runtime wrappers; adds no per-call overhead when the warning category is suppressed.deprecation: includes the@fail_if_not_removedtest decorator for direct test-failure enforcement when removal deadlines are reached.Deprecated:deprecated.sphinxincludes@versionaddedand@versionchangeddecorators that inject Sphinx directives into docstrings for lifecycle annotation in Sphinx-built API docs.warnings.warn: no dependency and minimal surface area for quick internal notices where migration behavior is not needed.