Package logilab :: Package common :: Module deprecation

[frames] | no frames]

# Source Code for Module logilab.common.deprecation

      1  # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
      2  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
      3  #
      4  # This file is part of logilab-common.
      5  #
      6  # logilab-common is free software: you can redistribute it and/or modify it under
      7  # the terms of the GNU Lesser General Public License as published by the Free
      8  # Software Foundation, either version 2.1 of the License, or (at your option) any
      9  # later version.
     10  #
     11  # logilab-common is distributed in the hope that it will be useful, but WITHOUT
     12  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     13  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     14  # details.
     15  #
     16  # You should have received a copy of the GNU Lesser General Public License along
     17  # with logilab-common.  If not, see <http://www.gnu.org/licenses/>.
     18  """Deprecation utilities."""
     19
     20  __docformat__ = "restructuredtext en"
     21
     22  import sys
     23  from warnings import warn
     24
     25  from logilab.common.changelog import Version
     26
     27
     28 -class DeprecationWrapper(object):
     29      """proxy to print a warning on access to any attribute of the wrapped object
     30      """
     31 -    def __init__(self, proxied, msg=None):
     32          self._proxied = proxied
     33          self._msg = msg
     34
     35 -    def __getattr__(self, attr):
     36          warn(self._msg, DeprecationWarning, stacklevel=2)
     37          return getattr(self._proxied, attr)
     38
     39 -    def __setattr__(self, attr, value):
     40          if attr in ('_proxied', '_msg'):
     41              self.__dict__[attr] = value
     42          else:
     43              warn(self._msg, DeprecationWarning, stacklevel=2)
     44              setattr(self._proxied, attr, value)
     45
     46
     47 -class DeprecationManager(object):
     48      """Manage the deprecation message handling. Messages are dropped for
     49      versions more recent than the 'compatible' version. Example::
     50
     51          deprecator = deprecation.DeprecationManager("module_name")
     52          deprecator.compatibility('1.3')
     53
     54          deprecator.warn('1.2', "message.")
     55
     56          @deprecator.deprecated('1.2', 'Message')
     57          def any_func():
     58              pass
     59
     60          class AnyClass(object):
     61              __metaclass__ = deprecator.class_deprecated('1.2')
     62      """
     63 -    def __init__(self, module_name=None):
     64          """
     65          """
     66          self.module_name = module_name
     67          self.compatible_version = None
     68
     69 -    def compatibility(self, compatible_version):
     70          """Set the compatible version.
     71          """
     72          self.compatible_version = Version(compatible_version)
     73
     74 -    def deprecated(self, version=None, reason=None, stacklevel=2, name=None, doc=None):
     75          """Display a deprecation message only if the version is older than the
     76          compatible version.
     77          """
     78          def decorator(func):
     79              message = reason or 'The function "%s" is deprecated'
     80              if '%s' in message:
     81                  message %= func.__name__
     82              def wrapped(*args, **kwargs):
     83                  self.warn(version, message, stacklevel+1)
     84                  return func(*args, **kwargs)
     85              return wrapped
     86          return decorator
     87
     88 -    def class_deprecated(self, version=None):
     89          class metaclass(type):
     90              """metaclass to print a warning on instantiation of a deprecated class"""
     91
     92              def __call__(cls, *args, **kwargs):
     93                  msg = getattr(cls, "__deprecation_warning__",
     94                                "%(cls)s is deprecated") % {'cls': cls.__name__}
     95                  self.warn(version, msg, stacklevel=3)
     96                  return type.__call__(cls, *args, **kwargs)
     97          return metaclass
     98
     99 -    def moved(self, version, modpath, objname):
    100          """use to tell that a callable has been moved to a new module.
    101
    102          It returns a callable wrapper, so that when its called a warning is printed
    103          telling where the object can be found, import is done (and not before) and
    104          the actual object is called.
    105
    106          NOTE: the usage is somewhat limited on classes since it will fail if the
    107          wrapper is use in a class ancestors list, use the `class_moved` function
    108          instead (which has no lazy import feature though).
    109          """
    110          def callnew(*args, **kwargs):
    111              from logilab.common.modutils import load_module_from_name
    112              message = "object %s has been moved to module %s" % (objname, modpath)
    113              self.warn(version, message)
    114              m = load_module_from_name(modpath)
    115              return getattr(m, objname)(*args, **kwargs)
    116          return callnew
    117
    118 -    def class_renamed(self, version, old_name, new_class, message=None):
    119          clsdict = {}
    120          if message is None:
    121              message = '%s is deprecated, use %s' % (old_name, new_class.__name__)
    122          clsdict['__deprecation_warning__'] = message
    123          try:
    124              # new-style class
    125              return self.class_deprecated(version)(old_name, (new_class,), clsdict)
    126          except (NameError, TypeError):
    127              # old-style class
    128              warn = self.warn
    129              class DeprecatedClass(new_class):
    130                  """FIXME: There might be a better way to handle old/new-style class
    131                  """
    132                  def __init__(self, *args, **kwargs):
    133                      warn(version, message, stacklevel=3)
    134                      new_class.__init__(self, *args, **kwargs)
    135              return DeprecatedClass
    136
    137 -    def class_moved(self, version, new_class, old_name=None, message=None):
    138          """nice wrapper around class_renamed when a class has been moved into
    139          another module
    140          """
    141          if old_name is None:
    142              old_name = new_class.__name__
    143          if message is None:
    144              message = 'class %s is now available as %s.%s' % (
    145                  old_name, new_class.__module__, new_class.__name__)
    146          return self.class_renamed(version, old_name, new_class, message)
    147
    148 -    def warn(self, version=None, reason="", stacklevel=2):
    149          """Display a deprecation message only if the version is older than the
    150          compatible version.
    151          """
    152          if (self.compatible_version is None
    153              or version is None
    154              or Version(version) < self.compatible_version):
    155              if self.module_name and version:
    156                  reason = '[%s %s] %s' % (self.module_name, version, reason)
    157              elif self.module_name:
    158                  reason = '[%s] %s' % (self.module_name, reason)
    159              elif version:
    160                  reason = '[%s] %s' % (version, reason)
    161              warn(reason, DeprecationWarning, stacklevel=stacklevel)
    162
    163  _defaultdeprecator = DeprecationManager()
    164
    165 -def deprecated(reason=None, stacklevel=2, name=None, doc=None):
    166      return _defaultdeprecator.deprecated(None, reason, stacklevel, name, doc)
    167
    168  class_deprecated = _defaultdeprecator.class_deprecated()
    169
    170 -def moved(modpath, objname):
    171      return _defaultdeprecator.moved(None, modpath, objname)
    172  moved.__doc__ = _defaultdeprecator.moved.__doc__
    173
    174 -def class_renamed(old_name, new_class, message=None):
    175      """automatically creates a class which fires a DeprecationWarning
    176      when instantiated.
    177
    178      >>> Set = class_renamed('Set', set, 'Set is now replaced by set')
    179      >>> s = Set()
    180      sample.py:57: DeprecationWarning: Set is now replaced by set
    181      s = Set()
    182      >>>
    183      """
    184      return _defaultdeprecator.class_renamed(None, old_name, new_class, message)
    185
    186 -def class_moved(new_class, old_name=None, message=None):
    187      return _defaultdeprecator.class_moved(None, new_class, old_name, message)
    188  class_moved.__doc__ = _defaultdeprecator.class_moved.__doc__
    189