summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorKevin L. Mitchell <kevin.mitchell@rackspace.com>2012-01-10 11:22:33 -0600
committerKevin L. Mitchell <kevin.mitchell@rackspace.com>2012-01-10 11:22:33 -0600
commit8e2d6abc28cf749baed1ce545b09a1c5520b93eb (patch)
tree918d69239bb85eabe52b2b6a3ad8b18aefc07b72 /nova
parent799801f856a0f3e7788e89ecdca02828fd64e6ad (diff)
downloadnova-8e2d6abc28cf749baed1ce545b09a1c5520b93eb.tar.gz
nova-8e2d6abc28cf749baed1ce545b09a1c5520b93eb.tar.xz
nova-8e2d6abc28cf749baed1ce545b09a1c5520b93eb.zip
Add @utils.deprecated().
This will allow us to mark deprecated classes and functions/methods as such. A warning is issued each time a deprecated function/method is called, or when a deprecated class is instantiated, or when any class or static method on a deprecated class is called. Change-Id: I4b5858492bc14768ac2e12c542bc343962761e34
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/test_utils.py110
-rw-r--r--nova/utils.py171
2 files changed, 280 insertions, 1 deletions
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index d221212df..7062097ff 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -535,3 +535,113 @@ class MonkeyPatchTestCase(test.TestCase):
in nova.tests.monkey_patch_example.CALLED_FUNCTION)
self.assertFalse(package_b + 'ExampleClassB.example_method_add'
in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+
+
+class DeprecationTest(test.TestCase):
+ def setUp(self):
+ super(DeprecationTest, self).setUp()
+
+ def fake_warn_deprecated_class(cls, msg):
+ self.warn = ('class', cls, msg)
+
+ def fake_warn_deprecated_function(func, msg):
+ self.warn = ('function', func, msg)
+
+ self.stubs.Set(utils, 'warn_deprecated_class',
+ fake_warn_deprecated_class)
+ self.stubs.Set(utils, 'warn_deprecated_function',
+ fake_warn_deprecated_function)
+ self.warn = None
+
+ def test_deprecated_function_no_message(self):
+ def test_function():
+ pass
+
+ decorated = utils.deprecated()(test_function)
+
+ decorated()
+ self.assertEqual(self.warn, ('function', test_function, ''))
+
+ def test_deprecated_function_with_message(self):
+ def test_function():
+ pass
+
+ decorated = utils.deprecated('string')(test_function)
+
+ decorated()
+ self.assertEqual(self.warn, ('function', test_function, 'string'))
+
+ def test_deprecated_class_no_message(self):
+ @utils.deprecated()
+ class TestClass(object):
+ pass
+
+ TestClass()
+ self.assertEqual(self.warn, ('class', TestClass, ''))
+
+ def test_deprecated_class_with_message(self):
+ @utils.deprecated('string')
+ class TestClass(object):
+ pass
+
+ TestClass()
+ self.assertEqual(self.warn, ('class', TestClass, 'string'))
+
+ def test_deprecated_classmethod_no_message(self):
+ @utils.deprecated()
+ class TestClass(object):
+ @classmethod
+ def class_method(cls):
+ pass
+
+ TestClass.class_method()
+ self.assertEqual(self.warn, ('class', TestClass, ''))
+
+ def test_deprecated_classmethod_with_message(self):
+ @utils.deprecated('string')
+ class TestClass(object):
+ @classmethod
+ def class_method(cls):
+ pass
+
+ TestClass.class_method()
+ self.assertEqual(self.warn, ('class', TestClass, 'string'))
+
+ def test_deprecated_staticmethod_no_message(self):
+ @utils.deprecated()
+ class TestClass(object):
+ @staticmethod
+ def static_method():
+ pass
+
+ TestClass.static_method()
+ self.assertEqual(self.warn, ('class', TestClass, ''))
+
+ def test_deprecated_staticmethod_with_message(self):
+ @utils.deprecated('string')
+ class TestClass(object):
+ @staticmethod
+ def static_method():
+ pass
+
+ TestClass.static_method()
+ self.assertEqual(self.warn, ('class', TestClass, 'string'))
+
+ def test_deprecated_instancemethod(self):
+ @utils.deprecated()
+ class TestClass(object):
+ def instance_method(self):
+ pass
+
+ # Instantiate the class...
+ obj = TestClass()
+ self.assertEqual(self.warn, ('class', TestClass, ''))
+
+ # Reset warn...
+ self.warn = None
+
+ # Call the instance method...
+ obj.instance_method()
+
+ # Make sure that did *not* generate a warning
+ self.assertEqual(self.warn, None)
diff --git a/nova/utils.py b/nova/utils.py
index f34db4aba..355f577ab 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -26,6 +26,7 @@ import inspect
import json
import lockfile
import os
+import pyclbr
import random
import re
import shlex
@@ -33,8 +34,9 @@ import socket
import struct
import sys
import time
+import types
import uuid
-import pyclbr
+import warnings
from xml.sax import saxutils
from eventlet import event
@@ -1222,3 +1224,170 @@ def temporary_mutation(obj, **kwargs):
del obj[attr]
else:
setattr(obj, attr, old_value)
+
+
+def warn_deprecated_class(cls, msg):
+ """
+ Issues a warning to indicate that the given class is deprecated.
+ If a message is given, it is appended to the deprecation warning.
+ """
+
+ fullname = '%s.%s' % (cls.__module__, cls.__name__)
+ if msg:
+ fullmsg = _("Class %(fullname)s is deprecated: %(msg)s")
+ else:
+ fullmsg = _("Class %(fullname)s is deprecated")
+
+ # Issue the warning
+ warnings.warn(fullmsg % locals(), DeprecationWarning, stacklevel=3)
+
+
+def warn_deprecated_function(func, msg):
+ """
+ Issues a warning to indicate that the given function is
+ deprecated. If a message is given, it is appended to the
+ deprecation warning.
+ """
+
+ name = func.__name__
+
+ # Find the function's definition
+ sourcefile = inspect.getsourcefile(func)
+
+ # Find the line number, if possible
+ if inspect.ismethod(func):
+ code = func.im_func.func_code
+ else:
+ code = func.func_code
+ lineno = getattr(code, 'co_firstlineno', None)
+
+ if lineno is None:
+ location = sourcefile
+ else:
+ location = "%s:%d" % (sourcefile, lineno)
+
+ # Build up the message
+ if msg:
+ fullmsg = _("Function %(name)s in %(location)s is deprecated: %(msg)s")
+ else:
+ fullmsg = _("Function %(name)s in %(location)s is deprecated")
+
+ # Issue the warning
+ warnings.warn(fullmsg % locals(), DeprecationWarning, stacklevel=3)
+
+
+def _stubout(klass, message):
+ """
+ Scans a class and generates wrapping stubs for __new__() and every
+ class and static method. Returns a dictionary which can be passed
+ to type() to generate a wrapping class.
+ """
+
+ overrides = {}
+
+ def makestub_class(name, func):
+ """
+ Create a stub for wrapping class methods.
+ """
+
+ def stub(cls, *args, **kwargs):
+ warn_deprecated_class(klass, message)
+ return func(*args, **kwargs)
+
+ # Overwrite the stub's name
+ stub.__name__ = name
+ stub.func_name = name
+
+ return classmethod(stub)
+
+ def makestub_static(name, func):
+ """
+ Create a stub for wrapping static methods.
+ """
+
+ def stub(*args, **kwargs):
+ warn_deprecated_class(klass, message)
+ return func(*args, **kwargs)
+
+ # Overwrite the stub's name
+ stub.__name__ = name
+ stub.func_name = name
+
+ return staticmethod(stub)
+
+ for name, kind, _klass, _obj in inspect.classify_class_attrs(klass):
+ # We're only interested in __new__(), class methods, and
+ # static methods...
+ if (name != '__new__' and
+ kind not in ('class method', 'static method')):
+ continue
+
+ # Get the function...
+ func = getattr(klass, name)
+
+ # Override it in the class
+ if kind == 'class method':
+ stub = makestub_class(name, func)
+ elif kind == 'static method' or name == '__new__':
+ stub = makestub_static(name, func)
+
+ # Save it in the overrides dictionary...
+ overrides[name] = stub
+
+ # Apply the overrides
+ for name, stub in overrides.items():
+ setattr(klass, name, stub)
+
+
+def deprecated(message=''):
+ """
+ Marks a function, class, or method as being deprecated. For
+ functions and methods, emits a warning each time the function or
+ method is called. For classes, generates a new subclass which
+ will emit a warning each time the class is instantiated, or each
+ time any class or static method is called.
+
+ If a message is passed to the decorator, that message will be
+ appended to the emitted warning. This may be used to suggest an
+ alternate way of achieving the desired effect, or to explain why
+ the function, class, or method is deprecated.
+ """
+
+ def decorator(f_or_c):
+ # Make sure we can deprecate it...
+ if not callable(f_or_c) or isinstance(f_or_c, types.ClassType):
+ warnings.warn("Cannot mark object %r as deprecated" % f_or_c,
+ DeprecationWarning, stacklevel=2)
+ return f_or_c
+
+ # If we're deprecating a class, create a subclass of it and
+ # stub out all the class and static methods
+ if inspect.isclass(f_or_c):
+ klass = f_or_c
+ _stubout(klass, message)
+ return klass
+
+ # OK, it's a function; use a traditional wrapper...
+ func = f_or_c
+
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ warn_deprecated_function(func, message)
+
+ return func(*args, **kwargs)
+
+ return wrapper
+ return decorator
+
+
+def _showwarning(message, category, filename, lineno, file=None, line=None):
+ """
+ Redirect warnings into logging.
+ """
+
+ fmtmsg = warnings.formatwarning(message, category, filename, lineno, line)
+ LOG.warning(fmtmsg)
+
+
+# Install our warnings handler
+warnings.showwarning = _showwarning