Package: pyDeprecate
pyDeprecateSimple tooling for marking deprecated functions or classes and re-routing to the new successors' instance.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.).InstallationSimple installation from PyPI:pipinstallpyDeprecateOther installationsSimply install with pip from source:pipinstallhttps://github.com/Borda/pyDeprecate/archive/main.zipUse-casesThe 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 streamextended argument mapping to target function/methoddefine deprecation logic for self argumentsspecify 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 forwardingIt is very straight forward, you forward your function call to new function and all arguments are mapped:defbase_sum(a:int=0,b:int=3)->int:"""My new function anywhere in codebase or even other package."""returna+b# ---------------------------fromdeprecateimportdeprecated@deprecated(target=base_sum,deprecated_in="0.1",remove_in="0.5")defdepr_sum(a:int,b:int=5)->int:"""My deprecated function which now has empty bodyas 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 mappingAnother more complex example is using argument mapping is:Advanced exampleimportloggingfromsklearn.metricsimportaccuracy_scorefromdeprecateimportdeprecated,void@deprecated(# use standard sklearn accuracy implementationtarget=accuracy_score,# custom warning streamstream=logging.warning,# number or warnings per lifetime (with -1 for always_num_warns=5,# custom message templatetemplate_mgs="`%(source_name)s` was deprecated, use `%(target_path)s`",# as target args are different, define mapping from source to target funcargs_mapping={'preds':'y_pred','target':'y_true','blabla':None})defdepr_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 functionreturnvoid(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.5Deprecation warning onlyBase use-case with no forwarding and just raising warning :fromdeprecateimportdeprecated@deprecated(target=None,deprecated_in="0.1",remove_in="0.5")defmy_sum(a:int,b:int=5)->int:"""My deprecated function which still has to have implementation."""returna+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 mappingWe also support deprecation and argument mapping for the function itself:fromdeprecateimportdeprecated@deprecated(# define as depreaction some self argument - mappingtarget=True,args_mapping={'coef':'new_coef'},# common version infodeprecated_in="0.2",remove_in="0.4",)defany_pow(base:float,coef:float=0,new_coef:float=0)->float:"""My function with deprecated argument `coef` mapped to `new_coef`."""returnbase**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 levelsEventually you can set multiple deprecation levels via chaining deprecation arguments as each could be deprecated in another version:Multiple deprecation levelsfromdeprecateimportdeprecated@deprecated(True,"0.3","0.6",args_mapping=dict(c1='nc1'),template_mgs="Depr: v%(deprecated_in)srm v%(remove_in)sfor args:%(argument_map)s.")@deprecated(True,"0.4","0.7",args_mapping=dict(nc1='nc2'),template_mgs="Depr: v%(deprecated_in)srm v%(remove_in)sfor args:%(argument_map)s.")defany_pow(base,c1:float=0,nc1:float=0,nc2:float=2)->float:returnbase**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))sample output:8Conditional skipConditional skip of which can be used for mapping between different target functions depending on additional input such as package versionfromdeprecateimportdeprecatedFAKE_VERSION=1defversion_greater_1():returnFAKE_VERSION>1@deprecated(True,"0.3","0.6",args_mapping=dict(c1='nc1'),skip_if=version_greater_1)defskip_pow(base,c1:float=1,nc1:float=1)->float:returnbase**(c1-nc1)# call this function will raise deprecation warningprint(skip_pow(2,3))# change the fake versionsFAKE_VERSION=2# Will not raise any warningprint(skip_pow(2,3))sample output:```
  0.25
  4
  ```This can be beneficial with multiple deprecation levels shown above...Class deprecationThis case can be quite complex as you may deprecate just some methods, here we show full class deprecation:classNewCls:"""My new class anywhere in the codebase or other package."""def__init__(self,c:float,d:str="abc"):self.my_c=cself.my_d=d# ---------------------------fromdeprecateimportdeprecated,voidclassPastCls(NewCls):"""The deprecated class shall be inherited from the successor classto 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 wantto warn user just at the time of creating object."""returnvoid(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: 7print(inst.my_d)# returns: "efg"sample output:```
  7
  efg
  ```ContributionHave you faced this in past or even now, do you have good ideas for improvement, all is welcome!