Skip to content

Releases: Borda/pyDeprecate

🦜 Deprecation Proxy

03 Mar 17:50

Choose a tag to compare

Pre-release
v0.6.0rc0

releasing `0.6.0.rc0`

Deprecation Lifecycle Management

23 Feb 22:34

Choose a tag to compare

🎉 pyDeprecate 0.5.0

We're excited to announce pyDeprecate 0.5.0 — the deprecation lifecycle release!
This version ships a brand-new audit module that brings CI-grade tooling for keeping your deprecated APIs healthy, discoverable, and eventually removed on schedule.


✨ What's New

🔍 audit Module — Deprecation Lifecycle Management

Note

These utilities are designed for use with pyDeprecate's @deprecated decorator and inspect its __deprecated__ metadata. They are not a general-purpose deprecation auditing framework.

The audit module requires the optional [audit] extra: pip install pyDeprecate[audit]

The headline feature is a dedicated deprecate.audit module that groups all inspection and enforcement utilities into one coherent API designed to be called from pytest or your CI scripts.

import mypackage
from deprecate.audit import find_deprecated_callables

# Scan an entire package for deprecated wrappers
infos = find_deprecated_callables(mypackage)
for info in infos:
    print(info.module, info.function, info.no_effect)

Three complementary audit capabilities are included:

🛡️ Zero-Impact Wrapper Validation

validate_deprecated_callable and find_deprecated_callables detect wrappers that have no real effect — invalid args_mapping keys, identity mappings, missing version fields, or a target that points back to the same function.

from deprecate.audit import validate_deprecated_callable
from mypackage import old_func

info = validate_deprecated_callable(old_func)
assert not info.no_effect, f"Misconfigured wrapper: {info.invalid_args}"

⏰ Expiry Enforcement

validate_deprecation_expiry scans a module or package and raises when any wrapper's remove_in version has been reached or passed — so zombie code never ships past its scheduled removal deadline.

Auto-detects the installed package version via importlib.metadata (falls back to __version__):

from deprecate.audit import validate_deprecation_expiry
import mypackage

# Raises if any deprecated wrapper is past its remove_in deadline
validate_deprecation_expiry(mypackage)

# Or pin the version explicitly
validate_deprecation_expiry(mypackage, current_version="0.5.0")

🔗 Deprecation Chain Detection

validate_deprecation_chains detects wrappers whose target is itself a deprecated callable, forming chains that users traverse unnecessarily.
Two chain kinds are reported via the new ChainType enum: TARGET (forwarding chain) and STACKED (composed argument mappings).

from deprecate.audit import validate_deprecation_chains
import mypackage

chains = validate_deprecation_chains(mypackage)
assert not chains, f"Chained deprecations found: {chains}"

🐛 Enum Signature Fix

@deprecated wrappers now correctly handle callables with var-positional Enum parameters in their signatures — a subtle edge case that previously caused incorrect argument forwarding (#104).


🔧 Under the Hood

  • Comparison table added to README showing how pyDeprecate stacks up against alternatives (#97)
  • Test suite reorganized into tests/unittests/ and tests/integration/ for clearer separation of concerns (#112, #95)
  • mypy moved from CI step to pre-commit hook for faster local feedback (#87)
  • Link checker added to CI to catch broken documentation references (#91)
  • Linting, type hint, and code quality improvements throughout (#85, #86, #98)

📦 Installation

pip install --upgrade pyDeprecate

# For audit features:
pip install --upgrade "pyDeprecate[audit]"

🙏 Thank You

A big thank you to everyone who contributed to this release!


Full Changelog: v0.4.0...v0.5.0

Enhanced Documentation & Modernization

03 Dec 10:39

Choose a tag to compare

🎉 pyDeprecate 0.4.0

We're excited to announce pyDeprecate 0.4.0, bringing automatic documentation updates and broader Python version support to make API deprecation even smoother!

✨ What's New

📝 Automatic Documentation Updates

The headline feature: update_docs option automatically inserts deprecation notices directly into your function and class docstrings!

@deprecated(target=new_function, update_docs=True, deprecated_in="0.4", remove_in="1.0")
def old_function():
    """Does something useful."""
    pass

Now your IDE tooltips and generated documentation will automatically show deprecation warnings, making it easier for users to discover they're using deprecated APIs without even running the code.

🐍 Expanded Python Support

Full support for modern Python versions:

  • ✅ Python 3.10
  • ✅ Python 3.11
  • ✅ Python 3.12
  • ✅ Python 3.13

Keep your deprecation strategy up-to-date with the latest Python releases!

🔧 Under the Hood

  • Modernized build configuration and dependencies
  • Enhanced CI/CD testing across all supported Python versions
  • Improved code quality and type hints
  • Better test coverage

📦 Installation

pip install --upgrade pyDeprecate

🙏 Thank You

Thanks to everyone who has used pyDeprecate and provided feedback!


Full changelog: 0.3.2...0.4.0

New Contributors:

Support containing `kwargs` in target function

11 Jun 09:32
bf57030

Choose a tag to compare

Expand usage of the target function has kwargs and uses kwargs.get.

class NewCls:
    def __init__(self, c: float, d: str = "abc", **kwargs):
        self.my_c = c
        self.my_d = d
        self.my_e = kwargs.get("e", 0.2)

class PastCls(NewCls):
    @deprecated(target=NewCls, deprecated_in="0.2", remove_in="0.4")
    def __init__(self, c: int, d: str = "efg", **kwargs):
        pass

Thanks to @jungbaepark

Fixed `void` typing

31 May 16:48

Choose a tag to compare

Fixed typing for void helper to by mypy compliment, see sample:

from deprecate import deprecated, void

@deprecated(...)
def depr_accuracy(preds: list, target: list, blabla: float) -> float:
    # to stop complain your IDE about unused argument you can use void/empty function
    return void(preds, target, blabla)

Conditional skip

21 Apr 09:57

Choose a tag to compare

Conditional skip

Conditional skip of which can be used for mapping between different target functions depending on additional input such as package version

from deprecate import deprecated

FAKE_VERSION = 1

def version_greater_1():
    return FAKE_VERSION > 1

@deprecated(
  True, "0.3", "0.6", args_mapping=dict(c1='nc1'), skip_if=version_greater_1
)
def skip_pow(base, c1: float = 1, nc1: float = 1) -> float:
    return base**(c1 - nc1)

# call this function will raise deprecation warning
print(skip_pow(2, 3))

# change the fake versions
FAKE_VERSION = 2

# Will not raise any warning
print(skip_pow(2, 3))

This can be beneficial with multiple deprecation levels introduced earlier...

Improved self arg deprecations

29 Mar 08:26

Choose a tag to compare

Self argument mapping

We also support deprecation and argument mapping for the function itself:

from deprecate import deprecated

@deprecated(
  # define as depreaction some self argument - mapping
  target=True, args_mapping={'coef': 'new_coef'},
  # common version info
  deprecated_in="0.2", remove_in="0.4",
)
def any_pow(base: float, coef: float = 0, new_coef: float = 0) -> float:
    """My function with deprecated argument `coef` mapped to `new_coef`."""
    return base ** new_coef

# call this function will raise deprecation warning:
#   The `any_pow` uses deprecated arguments: `coef` -> `new_coef`.
#   They were deprecated since v0.2 and will be removed in v0.4.
print(any_pow(2, 3))

Eventually, you can set multiple deprecation levels via chaining deprecation arguments as each could be deprecated in another version:

from deprecate import deprecated

@deprecated(
  True, "0.3", "0.6", args_mapping=dict(c1='nc1'),
  template_mgs="Depr: v%(deprecated_in)s rm v%(remove_in)s for args: %(argument_map)s."
)
@deprecated(
  True, "0.4", "0.7", args_mapping=dict(nc1='nc2'),
  template_mgs="Depr: v%(deprecated_in)s rm v%(remove_in)s for args: %(argument_map)s."
)
def any_pow(base, c1: float = 0, nc1: float = 0, nc2: float = 2) -> float:
    return base ** nc2

# call this function will raise deprecation warning:
#   DeprecationWarning('Depr: v0.3 rm v0.6 for args: `c1` -> `nc1`.')
#   DeprecationWarning('Depr: v0.4 rm v0.7 for args: `nc1` -> `nc2`.')
print(any_pow(2, 3))

Allow infinite waring

21 Mar 22:30

Choose a tag to compare

Allow infinite waring raising.

from deprecate import deprecated

@deprecated(
  target=None, deprecated_in="0.1", remove_in="0.5",
  # number or warnings per lifetime (with -1 for always)
  num_warns=5
)
def my_sum(a: int, b: int = 5) -> int:
    """My deprecated function which still has to have implementation."""
    return a + b

# call this function will raise deprecation warning:
#   The `my_sum` was deprecated since v0.1. It will be removed in v0.5.
print(my_sum(1, 2))

Initial release

20 Mar 08:42

Choose a tag to compare

Simple tooling for marking deprecated functions or classes and re-routing to the new successors' instance.

Overview

The common use-case is moving your functions across codebase or outsourcing some functionalities to new packages. For most of these cases, you want to hold some compatibility, so you cannot simply remove past function, and also for some time you want to warn users that functionality they have been using is moved and not it is deprecated in favor of another function (which shall be used instead) and soon it will be removed completely.

Another good aspect is to do not overwhelm a user with too many warnings, so per function/class, this warning is raised only N times in the preferable stream.

Installation

Simple installation from PyPI:

pip install pyDeprecate

Use-cases

The functionality is kept simple and all default shall be reasonable, but still, you can do extra customization such as:

  • define user warning message and preferable stream
  • extended argument mapping to target function/method

Simple function forwarding

It is very straight forward, you forward your function call to new function and all arguments are mapped:

def base_sum(a: int = 0, b: int = 3) -> int:
    """My new function anywhere in codebase or even other package."""
    return a + b

# ---------------------------

from deprecate import deprecated

@deprecated(target=base_sum, deprecated_in="0.1", remove_in="0.5")
def depr_sum(a: int, b: int = 5) -> int:
    """
    My deprecated function which now has empty body
     as all calls are routed to the new function.
    """
    pass  # or you can just place docstring as one above

# call this function will raise deprecation warning:
#   The `depr_sum` was deprecated since v0.1 in favor of `__main__.base_sum`.
#   It will be removed in v0.5.
print(depr_sum(1, 2))