| 1 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 2 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 3 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 4 | # version. |
|---|
| 5 | # |
|---|
| 6 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU General Public License along with |
|---|
| 11 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 13 | """Deprecation utilities |
|---|
| 14 | |
|---|
| 15 | :author: Logilab |
|---|
| 16 | :copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 17 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 18 | """ |
|---|
| 19 | __docformat__ = "restructuredtext en" |
|---|
| 20 | |
|---|
| 21 | from warnings import warn |
|---|
| 22 | |
|---|
| 23 | from logilab.common.modutils import load_module_from_name |
|---|
| 24 | |
|---|
| 25 | class deprecated(type): |
|---|
| 26 | """metaclass to print a warning on instantiation of a deprecated class""" |
|---|
| 27 | |
|---|
| 28 | def __call__(cls, *args, **kwargs): |
|---|
| 29 | msg = getattr(cls, "__deprecation_warning__", |
|---|
| 30 | "%s is deprecated" % cls.__name__) |
|---|
| 31 | warn(msg, DeprecationWarning, stacklevel=2) |
|---|
| 32 | return type.__call__(cls, *args, **kwargs) |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | def class_renamed(old_name, new_class, message=None): |
|---|
| 36 | """automatically creates a class which fires a DeprecationWarning |
|---|
| 37 | when instantiated. |
|---|
| 38 | |
|---|
| 39 | >>> Set = class_renamed('Set', set, 'Set is now replaced by set') |
|---|
| 40 | >>> s = Set() |
|---|
| 41 | sample.py:57: DeprecationWarning: Set is now replaced by set |
|---|
| 42 | s = Set() |
|---|
| 43 | >>> |
|---|
| 44 | """ |
|---|
| 45 | clsdict = {} |
|---|
| 46 | if message is not None: |
|---|
| 47 | clsdict['__deprecation_warning__'] = message |
|---|
| 48 | try: |
|---|
| 49 | # new-style class |
|---|
| 50 | return deprecated(old_name, (new_class,), clsdict) |
|---|
| 51 | except (NameError, TypeError): |
|---|
| 52 | # old-style class |
|---|
| 53 | class DeprecatedClass(new_class): |
|---|
| 54 | """FIXME: There might be a better way to handle old/new-style class |
|---|
| 55 | """ |
|---|
| 56 | def __init__(self, *args, **kwargs): |
|---|
| 57 | warn(message, DeprecationWarning, stacklevel=2) |
|---|
| 58 | new_class.__init__(self, *args, **kwargs) |
|---|
| 59 | return DeprecatedClass |
|---|
| 60 | |
|---|
| 61 | |
|---|
| 62 | def class_moved(new_class, old_name=None, message=None): |
|---|
| 63 | """nice wrapper around class_renamed when a class has been moved into |
|---|
| 64 | another module |
|---|
| 65 | """ |
|---|
| 66 | if old_name is None: |
|---|
| 67 | old_name = new_class.__name__ |
|---|
| 68 | if message is None: |
|---|
| 69 | message = 'class %s is now available as %s.%s' % ( |
|---|
| 70 | old_name, new_class.__module__, new_class.__name__) |
|---|
| 71 | return class_renamed(old_name, new_class, message) |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | def deprecated_function(new_func, message=None): |
|---|
| 75 | """creates a function which fires a DeprecationWarning when used |
|---|
| 76 | |
|---|
| 77 | For example, if <bar> is deprecated in favour of <foo> : |
|---|
| 78 | >>> bar = deprecated_function(foo, 'bar is deprecated') |
|---|
| 79 | >>> bar() |
|---|
| 80 | sample.py:57: DeprecationWarning: bar is deprecated |
|---|
| 81 | bar() |
|---|
| 82 | >>> |
|---|
| 83 | """ |
|---|
| 84 | if message is None: |
|---|
| 85 | message = "this function is deprecated, use %s instead" % ( |
|---|
| 86 | new_func.func_name) |
|---|
| 87 | def deprecated(*args, **kwargs): |
|---|
| 88 | warn(message, DeprecationWarning, stacklevel=2) |
|---|
| 89 | return new_func(*args, **kwargs) |
|---|
| 90 | return deprecated |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | def moved(modpath, objname): |
|---|
| 94 | """use to tell that a callable has been moved to a new module. |
|---|
| 95 | |
|---|
| 96 | It returns a callable wrapper, so that when its called a warning is printed |
|---|
| 97 | telling where the object can be found, import is done (and not before) and |
|---|
| 98 | the actual object is called. |
|---|
| 99 | |
|---|
| 100 | NOTE: the usage is somewhat limited on classes since it will fail if the |
|---|
| 101 | wrapper is use in a class ancestors list, use the `class_moved` function |
|---|
| 102 | instead (which has no lazy import feature though). |
|---|
| 103 | """ |
|---|
| 104 | def callnew(*args, **kwargs): |
|---|
| 105 | message = "this object has been moved, it's now %s.%s" % ( |
|---|
| 106 | modpath, objname) |
|---|
| 107 | warn(message, DeprecationWarning, stacklevel=2) |
|---|
| 108 | m = load_module_from_name(modpath) |
|---|
| 109 | return getattr(m, objname)(*args, **kwargs) |
|---|
| 110 | return callnew |
|---|
| 111 | |
|---|
| 112 | def obsolete(reason="This function is obsolete"): |
|---|
| 113 | """this function is an alternative to `deprecated_function` |
|---|
| 114 | when there's no real replacement for the deprecated function |
|---|
| 115 | """ |
|---|
| 116 | def newdecorator(func): |
|---|
| 117 | def wrapped(*args, **kwargs): |
|---|
| 118 | warn(reason, DeprecationWarning, stacklevel=2) |
|---|
| 119 | return func(*args, **kwargs) |
|---|
| 120 | return wrapped |
|---|
| 121 | return newdecorator |
|---|