deprecation.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. # copyright 2003-2011 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. __docformat__ = "restructuredtext en"
  20. import sys
  21. from warnings import warn
  22. class class_deprecated(type):
  23. """metaclass to print a warning on instantiation of a deprecated class"""
  24. def __call__(cls, *args, **kwargs):
  25. msg = getattr(cls, "__deprecation_warning__",
  26. "%(cls)s is deprecated") % {'cls': cls.__name__}
  27. warn(msg, DeprecationWarning, stacklevel=2)
  28. return type.__call__(cls, *args, **kwargs)
  29. def class_renamed(old_name, new_class, message=None):
  30. """automatically creates a class which fires a DeprecationWarning
  31. when instantiated.
  32. >>> Set = class_renamed('Set', set, 'Set is now replaced by set')
  33. >>> s = Set()
  34. sample.py:57: DeprecationWarning: Set is now replaced by set
  35. s = Set()
  36. >>>
  37. """
  38. clsdict = {}
  39. if message is None:
  40. message = '%s is deprecated, use %s' % (old_name, new_class.__name__)
  41. clsdict['__deprecation_warning__'] = message
  42. try:
  43. # new-style class
  44. return class_deprecated(old_name, (new_class,), clsdict)
  45. except (NameError, TypeError):
  46. # old-style class
  47. class DeprecatedClass(new_class):
  48. """FIXME: There might be a better way to handle old/new-style class
  49. """
  50. def __init__(self, *args, **kwargs):
  51. warn(message, DeprecationWarning, stacklevel=2)
  52. new_class.__init__(self, *args, **kwargs)
  53. return DeprecatedClass
  54. def class_moved(new_class, old_name=None, message=None):
  55. """nice wrapper around class_renamed when a class has been moved into
  56. another module
  57. """
  58. if old_name is None:
  59. old_name = new_class.__name__
  60. if message is None:
  61. message = 'class %s is now available as %s.%s' % (
  62. old_name, new_class.__module__, new_class.__name__)
  63. return class_renamed(old_name, new_class, message)
  64. def deprecated(reason=None, stacklevel=2, name=None, doc=None):
  65. """Decorator that raises a DeprecationWarning to print a message
  66. when the decorated function is called.
  67. """
  68. def deprecated_decorator(func):
  69. message = reason or 'The function "%s" is deprecated'
  70. if '%s' in message:
  71. message = message % func.func_name
  72. def wrapped(*args, **kwargs):
  73. warn(message, DeprecationWarning, stacklevel=stacklevel)
  74. return func(*args, **kwargs)
  75. try:
  76. wrapped.__name__ = name or func.__name__
  77. except TypeError: # readonly attribute in 2.3
  78. pass
  79. wrapped.__doc__ = doc or func.__doc__
  80. return wrapped
  81. return deprecated_decorator
  82. def moved(modpath, objname):
  83. """use to tell that a callable has been moved to a new module.
  84. It returns a callable wrapper, so that when its called a warning is printed
  85. telling where the object can be found, import is done (and not before) and
  86. the actual object is called.
  87. NOTE: the usage is somewhat limited on classes since it will fail if the
  88. wrapper is use in a class ancestors list, use the `class_moved` function
  89. instead (which has no lazy import feature though).
  90. """
  91. def callnew(*args, **kwargs):
  92. from logilab.common.modutils import load_module_from_name
  93. message = "object %s has been moved to module %s" % (objname, modpath)
  94. warn(message, DeprecationWarning, stacklevel=2)
  95. m = load_module_from_name(modpath)
  96. return getattr(m, objname)(*args, **kwargs)
  97. return callnew
  98. class DeprecationWrapper(object):
  99. """proxy to print a warning on access to any attribute of the wrapped object
  100. """
  101. def __init__(self, proxied, msg=None):
  102. self._proxied = proxied
  103. self._msg = msg
  104. def __getattr__(self, attr):
  105. warn(self._msg, DeprecationWarning, stacklevel=2)
  106. return getattr(self._proxied, attr)
  107. def __setattr__(self, attr, value):
  108. if attr in ('_proxied', '_msg'):
  109. self.__dict__[attr] = value
  110. else:
  111. warn(self._msg, DeprecationWarning, stacklevel=2)
  112. setattr(self._proxied, attr, value)