Giter Site home page Giter Site logo

borda / pydeprecate Goto Github PK

View Code? Open in Web Editor NEW
51.0 3.0 4.0 114 KB

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

Home Page: https://borda.github.io/pyDeprecate/

License: Apache License 2.0

Python 100.00%
deprecated compatibility routing python redirection

pydeprecate's Introduction

pyDeprecate

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

PyPI - Python Version PyPI Status PyPI Status Conda Conda License

CI testing Code formatting codecov CodeFactor pre-commit.ci status


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 (warning, logger, etc.).

Installation

Simple installation from PyPI:

pip install pyDeprecate
Other installations

Simply install with pip from source:

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

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
  • define deprecation logic for self arguments
  • specify warning count per:
    • called function (for func deprecation)
    • used arguments (for argument deprecation)
  • define conditional skip (e.g. depending on some package version)

In particular the target values (cases):

  • None - raise only warning message (ignore all argument mapping)
  • True - deprecation some argument of itself (argument mapping shall be specified)
  • Callable - forward call to new methods (optional also argument mapping or extras)

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))
sample output: ``` 3 ```

Advanced target argument mapping

Another more complex example is using argument mapping is:

Advanced example
import logging
from sklearn.metrics import accuracy_score
from deprecate import deprecated, void


@deprecated(
    # use standard sklearn accuracy implementation
    target=accuracy_score,
    # custom warning stream
    stream=logging.warning,
    # number or warnings per lifetime (with -1 for always_
    num_warns=5,
    # custom message template
    template_mgs="`%(source_name)s` was deprecated, use `%(target_path)s`",
    # as target args are different, define mapping from source to target func
    args_mapping={"preds": "y_pred", "target": "y_true", "blabla": None},
)
def depr_accuracy(preds: list, target: list, blabla: float) -> float:
    """My deprecated function which is mapping to sklearn accuracy."""
    # to stop complain your IDE about unused argument you can use void/empty function
    return void(preds, target, blabla)


# call this function will raise deprecation warning:
#   WARNING:root:`depr_accuracy` was deprecated, use `sklearn.metrics.accuracy_score`
print(depr_accuracy([1, 0, 1, 2], [0, 1, 1, 2], 1.23))

sample output:

0.5

Deprecation warning only

Base use-case with no forwarding and just raising warning :

from deprecate import deprecated


@deprecated(target=None, deprecated_in="0.1", remove_in="0.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))
sample output: ``` 3 ```

Self argument mapping

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

from deprecate import deprecated


@deprecated(
    # define as deprecation 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))
sample output: ``` 8 ```

Multiple deprecation levels

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

Multiple deprecation levels
from deprecate import deprecated


@deprecated(
    True,
    deprecated_in="0.3",
    remove_in="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,
    deprecated_in="0.4",
    remove_in="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:
#   FutureWarning('Depr: v0.3 rm v0.6 for args: `c1` -> `nc1`.')
#   FutureWarning('Depr: v0.4 rm v0.7 for args: `nc1` -> `nc2`.')
print(any_pow(2, 3))

sample output:

8

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))
sample output: ``` 0.25 4 ```

This can be beneficial with multiple deprecation levels shown above...

Class deprecation

This case can be quite complex as you may deprecate just some methods, here we show full class deprecation:

class NewCls:
    """My new class anywhere in the codebase or other package."""

    def __init__(self, c: float, d: str = "abc"):
        self.my_c = c
        self.my_d = d


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

from deprecate import deprecated, void


class PastCls(NewCls):
    """
    The deprecated class shall be inherited from the successor class
     to hold all methods.
    """

    @deprecated(target=NewCls, deprecated_in="0.2", remove_in="0.4")
    def __init__(self, c: int, d: str = "efg"):
        """
        You place the decorator around __init__ as you want
         to warn user just at the time of creating object.
        """
        void(c, d)


# call this function will raise deprecation warning:
#   The `PastCls` was deprecated since v0.2 in favor of `__main__.NewCls`.
#   It will be removed in v0.4.
inst = PastCls(7)
print(inst.my_c)  # returns: 7
print(inst.my_d)  # returns: "efg"
sample output: ``` 7 efg ```

Contribution

Have you faced this in past or even now, do you have good ideas for improvement, all is welcome!

pydeprecate's People

Contributors

borda avatar carmocca avatar deepsource-autofix[bot] avatar deepsourcebot avatar dependabot[bot] avatar jungbaepark avatar lgtm-com[bot] avatar pre-commit-ci[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pydeprecate's Issues

Kwargs it's not supportd

๐Ÿ› Bug

If the target function has kwargs and contains kwargs.get, the deprecated function will not be worked and cause Type Error.

To Reproduce

Steps to reproduce the behavior:

  1. Go to 'test" & add kwargs
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)

& change test code of tests.test_classes

class PastCls(NewCls):

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



def test_deprecated_class_forward() -> None:
    with pytest.deprecated_call(
        match='The `PastCls` was deprecated since v0.2 in favor of `tests.collection_targets.NewCls`.'
        ' It will be removed in v0.4.'
    ):
        past = PastCls(2, e=0.1)
    assert past.my_c == 2
    assert past.my_d == "efg"
    assert past.my_e == 0.1
    assert isinstance(past, NewCls)
    assert isinstance(past, PastCls)

    # check that the warning is raised only once per function
    with no_warning_call():
        assert PastCls(c=2, d="", e=0.9999)

    PastCls.__init__._warned = False
    with pytest.deprecated_call(match='It will be removed in v0.4.'):
        PastCls(2)
  1. Run 'test codes.
  2. Scroll down to 'Type Error'
  3. See error

extend flexibility

๐Ÿš€ Feature

Some other enhancements:

  • specify how many times warning shall be raised
  • allow passing warning function so a user can stream it to a logger
  • allow also argument mapping if source and target names differ
  • pass custom message or template message
  • add depreciation info also to the docstring if missing

Motivation

extend the functionality

Additional context

Stay simple and be COOL! ๐ŸŽ‰

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.