From dd8fd2b955e22577e016580d2a8b53df8882d332 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Tue, 11 Jul 2017 00:25:40 +0200 Subject: [PATCH 01/12] Improvement: You can give a "reason" message to help the developer to choose another function/class. --- README.md | 33 ++++++++++- deprecated/__init__.py | 83 ++++++++++++++++++++++----- tests/__init__.py | 12 ---- tests/test_deprecated.py | 118 +++++++++++++++++++++++++++++++++++---- 4 files changed, 207 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 2e490f7..c677cf1 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 f271756..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 emmitted + 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 {}.".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..c1fd57a 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -1,24 +1,120 @@ # -*- 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): + with self.assertRaises(TypeError): + deprecated(5) + + def test_should_have_a_docstring(self): + docstring = old_function_with_docstring.__doc__ + self.assertIsNotNone(docstring) + self.assertIn("This is an old function.", docstring) From 8b4a986a7ec3a071358f31ed14aa0610a76159d0 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Tue, 11 Jul 2017 00:30:04 +0200 Subject: [PATCH 02/12] Minor change: fix spelling in ``README.md``. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c677cf1..569413b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Python @deprecated decorator to deprecate old python functions [![Build Status](https://travis-ci.org/vrcmarcos/python-deprecated.svg?branch=master)](https://travis-ci.org/vrcmarcos/python-deprecated) [![Coverage Status](https://coveralls.io/repos/github/vrcmarcos/python-deprecated/badge.svg?branch=master)](https://coveralls.io/github/vrcmarcos/python-deprecated?branch=master) [![GitHub version](https://badge.fury.io/gh/vrcmarcos%2Fpython-deprecated.svg)](https://badge.fury.io/gh/vrcmarcos%2Fpython-deprecated) [![Code Health](https://landscape.io/github/vrcmarcos/python-deprecated/master/landscape.svg?style=flat)](https://landscape.io/github/vrcmarcos/python-deprecated/master) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/vrcmarcos/python-deprecated/master/LICENSE) -## Instalation +## Installation ```shell pip install Python-Deprecated From c1510244999e1e88dd66f62857d855e466cec570 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Mon, 10 Jul 2017 22:54:38 +0200 Subject: [PATCH 03/12] Replace invalid string format '{}' by '{0}' for Python 2.6 compatibility. --- deprecated/__init__.py | 83 +++++++----------------------------------- 1 file changed, 14 insertions(+), 69 deletions(-) diff --git a/deprecated/__init__.py b/deprecated/__init__.py index 59dbdb3..8ff31bb 100644 --- a/deprecated/__init__.py +++ b/deprecated/__init__.py @@ -1,80 +1,25 @@ # -*- coding: utf-8 -*- import functools -import inspect import warnings -string_types = (type(b''), type(u'')) - -def deprecated(reason): +def deprecated(func): """ This is a decorator which can be used to mark functions - as deprecated. It will result in a warning being emitted + as deprecated. It will result in a warning being emmitted when the function is used. """ - 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))) + @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 From ed97a1b687218088fd6a46158851351052364954 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Mon, 10 Jul 2017 23:00:08 +0200 Subject: [PATCH 04/12] Change in ``setup.py``: add a long description of the project. Set the **license** and the **development status** in the classifiers property. --- setup.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) mode change 100644 => 100755 setup.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 0dd2931..2df1c42 --- a/setup.py +++ b/setup.py @@ -1,6 +1,65 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# -*- coding: utf-8 -*- +u""" +Python-Deprecated +----------------- -from setuptools import setup, find_packages +Python ``@deprecated`` decorator to deprecate old python functions or methods. + +Deprecated is Easy to Use +````````````````````````` + +If you need to mark a function or a method as deprecated, +you can use the ``@deprecated`` decorator: + +Save in a hello.py: + +.. code:: python + + from deprecated import deprecated + + + @deprecated + def some_old_function(x, y): + return x + y + + + class SomeClass(object): + @deprecated + def some_old_method(self, x, y): + return x + y + + + some_old_function(12, 34) + obj = SomeClass() + obj.some_old_method(5, 8) + + +And Easy to Setup +````````````````` + +And run it: + +.. code:: bash + + $ pip install Python-Deprecated + $ python hello.py + hello.py:15: DeprecationWarning: Call to deprecated function some_old_function. + some_old_function(12, 34) + hello.py:17: DeprecationWarning: Call to deprecated function some_old_method. + obj.some_old_method(5, 8) + + +Links +````` + +* `website `_ +* `StackOverFlow Question `_ +* `development version + `_ + +""" +from setuptools import setup setup( name='Python-Deprecated', @@ -10,13 +69,16 @@ author='Marcos Cardoso', author_email='vrcmarcos@gmail.com', description='Python Deprecated Decorator', - packages=find_packages(), + long_description=__doc__, + packages=['deprecated'], zip_safe=False, include_package_data=True, platforms='any', classifiers=[ + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules' From b875188079e8e9379e4b450796cc490b48d2058b Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Mon, 10 Jul 2017 23:02:30 +0200 Subject: [PATCH 05/12] Change the classifiers in ``setup.py`` and improve the Travis configuration file because the library should be universal (compatible from Python 2.6 to 3.7-dev, and PyPy). --- .travis.yml | 9 +++++++++ setup.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/.travis.yml b/.travis.yml index c1bc804..f08b2c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,15 @@ language: python python: + - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + - "3.7-dev" # 3.7 development branch + - "nightly" # currently points to 3.7-dev + - "pypy" install: - pip install -r requirements_test.txt - pip install coveralls diff --git a/setup.py b/setup.py index 2df1c42..7d40f17 100755 --- a/setup.py +++ b/setup.py @@ -81,6 +81,14 @@ def some_old_method(self, x, y): 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Libraries :: Python Modules' ] ) From 1ecada40574a21cc6b9ed887348e18a786034ed8 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Mon, 10 Jul 2017 23:19:25 +0200 Subject: [PATCH 06/12] Remove "3.2" from the Travis configuration file because py.test has no support for Python 3.2. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f08b2c0..e291133 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - "2.6" - "2.7" - - "3.2" +# - "3.2" # py.test has no support for Python 3.2 - "3.3" - "3.4" - "3.5" From 691364dd9c71a14f5c6080c4f4e206434e8d74f2 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Mon, 10 Jul 2017 23:26:22 +0200 Subject: [PATCH 07/12] Change the makefile to build and upload an universal Wheel. --- Makefile | 2 +- setup.cfg | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 78764b1..12dd959 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,4 @@ test: test_install @py.test --cov-report term-missing --cov=deprecated upload: - @python setup.py sdist upload \ No newline at end of file + @python setup.py sdist bdist_wheel upload \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index dc62e1d..13e9aaf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[bdist_wheel] +universal=1 + [pytest] python_files=test*.py From f8d37aea76b865dcd1710670ad506a813d0bddff Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Tue, 11 Jul 2017 00:25:40 +0200 Subject: [PATCH 08/12] Improvement: You can give a "reason" message to help the developer to choose another function/class. --- deprecated/__init__.py | 81 +++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/deprecated/__init__.py b/deprecated/__init__.py index 8ff31bb..a25c480 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 emmitted 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))) From 925e2c1dc69ad3f6ae3c162964ad3d49df9993f3 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Fri, 14 Jul 2017 00:07:14 +0200 Subject: [PATCH 09/12] Minor change: fix spelling in ``deprecated/__init__.py``. --- deprecated/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deprecated/__init__.py b/deprecated/__init__.py index a25c480..59dbdb3 100644 --- a/deprecated/__init__.py +++ b/deprecated/__init__.py @@ -10,7 +10,7 @@ def deprecated(reason): """ This is a decorator which can be used to mark functions - as deprecated. It will result in a warning being emmitted + as deprecated. It will result in a warning being emitted when the function is used. """ From 50273124cc3e9536481d0fbb2e2ee630fad76f3d Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Fri, 14 Jul 2017 14:24:17 +0200 Subject: [PATCH 10/12] Fix unit test compatibility with Python2.6. --- tests/test_deprecated.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index c1fd57a..b906c4b 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -116,5 +116,6 @@ def test_should_raise_TypeError(self): def test_should_have_a_docstring(self): docstring = old_function_with_docstring.__doc__ - self.assertIsNotNone(docstring) + # assertIsNotNone() is not available in Python 2.6 + self.assertTrue(docstring is not None) self.assertIn("This is an old function.", docstring) From 7e7ce388681745ae493f83b7ba0123b3255fc464 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Fri, 14 Jul 2017 14:33:31 +0200 Subject: [PATCH 11/12] Fix unit test compatibility with Python2.6: assertIsNotNone() and assertIn() are not available in Python 2.6. --- tests/test_deprecated.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index b906c4b..b8968b4 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -116,6 +116,6 @@ def test_should_raise_TypeError(self): def test_should_have_a_docstring(self): docstring = old_function_with_docstring.__doc__ - # assertIsNotNone() is not available in Python 2.6 + # assertIsNotNone() and assertIn() are not available in Python 2.6 self.assertTrue(docstring is not None) - self.assertIn("This is an old function.", docstring) + self.assertTrue("This is an old function." in docstring) From 983f6777d3dadc5046cbbc70e2d3fe3762579a9d Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Fri, 14 Jul 2017 14:50:54 +0200 Subject: [PATCH 12/12] Fix unit test compatibility with Python2.6: can't use self.assertRaises() as a context manager. --- tests/test_deprecated.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index b8968b4..b6db10e 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -111,11 +111,17 @@ def test_should_not_warn_non_deprecated_function(self): self.assertEqual(len(warns), 0) def test_should_raise_TypeError(self): - with self.assertRaises(TypeError): + try: deprecated(5) + self.fail("TypeError not called") + except TypeError: + pass def test_should_have_a_docstring(self): docstring = old_function_with_docstring.__doc__ - # assertIsNotNone() and assertIn() are not available in Python 2.6 self.assertTrue(docstring is not None) self.assertTrue("This is an old function." in docstring) + + +if __name__ == '__main__': + unittest.main(module='tests.test_deprecated')