diff --git a/README.md b/README.md index 21acdb4..569413b 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,38 @@ To use this, decorate your deprecated method with **@deprecated** decorator: ```python from deprecated import deprecated + +@deprecated +def some_old_function(x, y): + return x + y +``` + +You can also decorate a class or a method: + +```python +from deprecated import deprecated + + +class SomeClass(object): + @deprecated + def some_old_method(self, x, y): + return x + y + + @deprecated -def deprecated_function(): - ... +class SomeOldClass(object): + pass +``` + +You can give a "reason" message to help the developer to choose another function/class: + +```python +from deprecated import deprecated + + +@deprecated(reason="use another function") +def some_old_function(x, y): + return x + y ``` ## Author: diff --git a/deprecated/__init__.py b/deprecated/__init__.py index 47e80ab..59dbdb3 100644 --- a/deprecated/__init__.py +++ b/deprecated/__init__.py @@ -1,25 +1,80 @@ # -*- coding: utf-8 -*- import functools +import inspect import warnings +string_types = (type(b''), type(u'')) -def deprecated(func): + +def deprecated(reason): """ This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. """ - @functools.wraps(func) - def new_func(*args, **kwargs): - warnings.simplefilter('always', DeprecationWarning) - warnings.warn( - "Call to deprecated function {0}.".format(func.__name__), - category=DeprecationWarning, - stacklevel=2 - ) - warnings.simplefilter('default', DeprecationWarning) - return func(*args, **kwargs) - - return new_func + if isinstance(reason, string_types): + + # The @deprecated is used with a 'reason'. + # + # .. code-block:: python + # + # @deprecated("please, use another function") + # def old_function(x, y): + # pass + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "Call to deprecated class {name} ({reason})." + else: + fmt1 = "Call to deprecated function {name} ({reason})." + + @functools.wraps(func1) + def new_func1(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt1.format(name=func1.__name__, reason=reason), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func1(*args, **kwargs) + + return new_func1 + + return decorator + + elif inspect.isclass(reason) or inspect.isfunction(reason): + + # The @deprecated is used without any 'reason'. + # + # .. code-block:: python + # + # @deprecated + # def old_function(x, y): + # pass + + func2 = reason + + if inspect.isclass(func2): + fmt2 = "Call to deprecated class {name}." + else: + fmt2 = "Call to deprecated function {name}." + + @functools.wraps(func2) + def new_func2(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt2.format(name=func2.__name__), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func2(*args, **kwargs) + + return new_func2 + + else: + raise TypeError(repr(type(reason))) diff --git a/tests/__init__.py b/tests/__init__.py index 49e7908..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- - -from deprecated import deprecated - - -def function(): - pass - - -@deprecated -def deprecated_function(): - pass diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index ed7c7fa..b6db10e 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -1,24 +1,127 @@ # -*- coding: utf-8 -*- +import unittest import warnings -from unittest import TestCase -from tests import deprecated_function, function +from deprecated import deprecated -class DeprecatedTest(TestCase): +def some_function(): + pass + +@deprecated +def some_old_function1(x, y): + return x + y + + +@deprecated("use another function") +def some_old_function2(x, y): + return x + y + + +@deprecated(reason="use another function") +def some_old_function3(x, y): + return x + y + + +class SomeClass(object): + @deprecated + def some_old_method1(self, x, y): + return x + y + + @deprecated("use another method") + def some_old_method2(self, x, y): + return x + y + + @deprecated(reason="use another method") + def some_old_method3(self, x, y): + return x + y + + +@deprecated +class SomeOldClass1(object): + pass + + +@deprecated("use another class") +class SomeOldClass2(object): + pass + + +@deprecated(reason="use another class") +class SomeOldClass3(object): + pass + + +@deprecated(reason="Use something else!") +def old_function_with_docstring(x, y): + """ + This is an old function. + + :param x: a number + :param y: a number + :return: the sum of two numbers. + """ + return x + y + + +class DeprecatedTest(unittest.TestCase): def test_should_warn_deprecated_function(self): - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - deprecated_function() - self.assertEqual(len(warns), 1) - warn = warns[0] - self.assertTrue(issubclass(warn.category, DeprecationWarning)) - self.assertTrue("deprecated" in str(warn.message)) + for old_function in [some_old_function1, + some_old_function2, + some_old_function3]: + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + old_function(4, 5) + self.assertEqual(len(warns), 1) + warn = warns[0] + self.assertTrue(issubclass(warn.category, DeprecationWarning)) + self.assertTrue("deprecated" in str(warn.message)) + + def test_should_warn_deprecated_method(self): + obj = SomeClass() + for old_method in [obj.some_old_method1, + obj.some_old_method2, + obj.some_old_method3]: + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + old_method(4, 5) + self.assertEqual(len(warns), 1) + warn = warns[0] + self.assertTrue(issubclass(warn.category, DeprecationWarning)) + self.assertTrue("deprecated" in str(warn.message)) + + def test_should_warn_deprecated_class(self): + for old_cls in [SomeOldClass1, + SomeOldClass2, + SomeOldClass3]: + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + old_cls() + self.assertEqual(len(warns), 1) + warn = warns[0] + self.assertTrue(issubclass(warn.category, DeprecationWarning)) + self.assertTrue("deprecated" in str(warn.message)) def test_should_not_warn_non_deprecated_function(self): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") - function() + some_function() self.assertEqual(len(warns), 0) + + def test_should_raise_TypeError(self): + try: + deprecated(5) + self.fail("TypeError not called") + except TypeError: + pass + + def test_should_have_a_docstring(self): + docstring = old_function_with_docstring.__doc__ + self.assertTrue(docstring is not None) + self.assertTrue("This is an old function." in docstring) + + +if __name__ == '__main__': + unittest.main(module='tests.test_deprecated')