Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to warn about class (name) deprecation

I have renamed a python class that is part of a library. I am willing to leave a possibility to use its previous name for some time but would like to warn user that it's deprecated and will be removed in the future.

I think that to provide backward compatibility it will be enough to use an alias like that:

class NewClsName:     pass  OldClsName = NewClsName 

I have no idea how to mark the OldClsName as deprecated in an elegant way. Maybe I could make OldClsName a function which emits a warning (to logs) and constructs the NewClsName object from its parameters (using *args and **kvargs) but it doesn't seem elegant enough (or maybe it is?).

However, I don't know how Python standard library deprecation warnings work. I imagine that there may be some nice magic to deal with deprecation, e.g. allowing treating it as errors or silencing depending on some interpreter's command line option.

The question is: How to warn users about using an obsolete class alias (or obsolete class in general).

EDIT: The function approach doesn't work for me (I already gave it a try) because the class has some class methods (factory methods) which can't be called when the OldClsName is defined as a function. Following code won't work:

class NewClsName(object):     @classmethod     def CreateVariant1( cls, ... ):         pass      @classmethod     def CreateVariant2( cls, ... ):         pass  def OldClsName(*args, **kwargs):     warnings.warn("The 'OldClsName' class was renamed [...]",                   DeprecationWarning )     return NewClsName(*args, **kwargs)  OldClsName.CreateVariant1( ... ) 

Because of:

AttributeError: 'function' object has no attribute 'CreateVariant1' 

Is inheritance my only option? To be honest, it doesn't look very clean to me - it affects class hierarchy through introduction of unnecessary derivation. Additionally, OldClsName is not NewClsName what is not an issue in most cases but may be a problem in case of poorly written code using the library.

I could also create a dummy, unrelated OldClsName class and implement a constructor as well as wrappers for all class methods in it, but it is even worse solution, in my opinion.

like image 363
Dariusz Walczak Avatar asked Jan 25 '12 18:01

Dariusz Walczak


People also ask

How do you add a deprecation warning?

To warn about deprecation, you need to set Python's builtin DeprecationWarning as category. To let the warning refer to the caller, so you know exactly where you use deprecated code, you have to set stacklevel=2 .

What is a deprecation warning?

Deprecation warnings are a common thing in our industry. They are warnings that notify us that a specific feature (e.g. a method) will be removed soon (usually in the next minor or major version) and should be replaced with something else.

How do I stop deprecation warning in Python?

When nothing else works: $ pip install shutup . Then at the top of the code import shutup;shutup. please() . This will disable all warnings.

What Python mechanism is best suited for telling a user that they are using a deprecated function?

And you should be able to indicate deprecation when the function is imported from the module. Decorator would be a right tool for that.


2 Answers

Maybe I could make OldClsName a function which emits a warning (to logs) and constructs the NewClsName object from its parameters (using *args and **kvargs) but it doesn't seem elegant enough (or maybe it is?).

Yup, I think that's pretty standard practice:

def OldClsName(*args, **kwargs):     from warnings import warn     warn("get with the program!")     return NewClsName(*args, **kwargs) 

The only tricky thing is if you have things that subclass from OldClsName - then we have to get clever. If you just need to keep access to class methods, this should do it:

class DeprecationHelper(object):     def __init__(self, new_target):         self.new_target = new_target      def _warn(self):         from warnings import warn         warn("Get with the program!")      def __call__(self, *args, **kwargs):         self._warn()         return self.new_target(*args, **kwargs)      def __getattr__(self, attr):         self._warn()         return getattr(self.new_target, attr)  OldClsName = DeprecationHelper(NewClsName) 

I haven't tested it, but that should give you the idea - __call__ will handle the normal-instantation route, __getattr__ will capture accesses to the class methods & still generate the warning, without messing with your class heirarchy.

like image 84
AdamKG Avatar answered Sep 18 '22 01:09

AdamKG


Please have a look at warnings.warn.

As you'll see, the example in the documentation is a deprecation warning:

def deprecation(message):     warnings.warn(message, DeprecationWarning, stacklevel=2) 
like image 41
jcollado Avatar answered Sep 21 '22 01:09

jcollado