jenkins-bot has submitted this change and it was merged.
Change subject: @deprecated support for any object ......................................................................
@deprecated support for any object
Decorator @deprecated and @deprecate_arg currently assume they are run on a bound method. This changeset fixes constraints in the previous implementation.
Also always report which module the deprecated method is in, as the user may not recognise names like 'BaseSite' if they are using many packages, and pywikibot might be used indirectly by another package. To achieve this, a new constraint is that all decorators must call pywikibot.tools.add_decorated_full_name(), even if they dont use the full name, so that is available for as other decorators in the chain.
Output 'deprecated' instead of 'DEPRECATED'.
Change-Id: I2fefef7e62dbd0595b2b771f61e34c569c7b3773 --- M pywikibot/site.py M pywikibot/tools.py M tests/deprecation_tests.py M tests/dry_api_tests.py M tests/dry_site_tests.py M tox.ini 6 files changed, 388 insertions(+), 81 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved XZise: Looks good to me, but someone else must approve jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py index 43b781c..ac78b07 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -29,7 +29,7 @@ import pywikibot.family from pywikibot.tools import ( itergroup, deprecated, deprecate_arg, UnicodeMixin, ComparableMixin, - redirect_func, + redirect_func, add_decorated_full_name, ) from pywikibot.tools import MediaWikiVersion as LV from pywikibot.throttle import Throttle @@ -900,6 +900,10 @@ return fn(self, *args, **kwargs) callee.__name__ = fn.__name__ callee.__doc__ = fn.__doc__ + callee.__module__ = callee.__module__ + if not hasattr(fn, '__full_name__'): + add_decorated_full_name(fn) + callee.__full_name__ = fn.__full_name__ return callee
return decorator @@ -922,6 +926,11 @@ return fn(self, *args, **kwargs) callee.__name__ = fn.__name__ callee.__doc__ = fn.__doc__ + callee.__module__ = fn.__module__ + if not hasattr(fn, '__full_name__'): + add_decorated_full_name(fn) + callee.__full_name__ = fn.__full_name__ + return callee return decorator
diff --git a/pywikibot/tools.py b/pywikibot/tools.py index 22e2203..dde154a 100644 --- a/pywikibot/tools.py +++ b/pywikibot/tools.py @@ -11,6 +11,7 @@ import sys import threading import time +import inspect import re from collections import Mapping from distutils.version import Version @@ -319,54 +320,207 @@
EMPTY_DEFAULT = EmptyDefault()
+# Decorators +# +# Decorator functions without parameters are _invoked_ differently from +# decorator functions with function syntax. For example, @deprecated causes +# a different invocation to @deprecated().
-def deprecated(instead=None): - """Decorator to output a method deprecation warning. +# The former is invoked with the decorated function as args[0]. +# The latter is invoked with the decorator arguments as *args & **kwargs, +# and it must return a callable which will be invoked with the decorated +# function as args[0]. + +# The follow deprecators may support both syntax, e.g. @deprecated and +# @deprecated() both work. In order to achieve that, the code inspects +# args[0] to see if it callable. Therefore, a decorator must not accept +# only one arg, and that arg be a callable, as it will be detected as +# a deprecator without any arguments. + + +def add_decorated_full_name(obj): + """Extract full object name, including class, and store in __full_name__. + + This must be done on all decorators that are chained together, otherwise + the second decorator will have the wrong full name. + + @param obj: A object being decorated + @type obj: object + """ + if hasattr(obj, '__full_name__'): + return + # The current frame is add_decorated_full_name + # The next frame is the decorator + # The next frame is the object being decorated + frame = inspect.currentframe().f_back.f_back + class_name = frame.f_code.co_name + if class_name and class_name != '<module>': + obj.__full_name__ = (obj.__module__ + '.' + + class_name + '.' + + obj.__name__) + else: + obj.__full_name__ = (obj.__module__ + '.' + + obj.__name__) + + +def add_full_name(obj): + """ + A decorator to add __full_name__ to the function being decorated. + + This should be done for all decorators used in pywikibot, as any + decorator that does not add __full_name__ will prevent other + decorators in the same chain from being able to obtain it. + + This can be used to monkey-patch decorators in other modules. + e.g. + <xyz>.foo = add_full_name(<xyz>.foo) + + @param obj: The function to decorate + @type obj: callable + @return: decorating function + @rtype: function + """ + def outer_wrapper(*outer_args, **outer_kwargs): + """Outer wrapper. + + The outer wrapper may be the replacement function if the decorated + decorator was called without arguments, or the replacement decorator + if the decorated decorator was called without arguments. + + @param outer_args: args + @type outer_args: list + @param outer_kwargs: kwargs + @type: outer_kwargs: dict + """ + def inner_wrapper(*args, **kwargs): + """Replacement function. + + If the decorator supported arguments, they are in outer_args, + and this wrapper is used to process the args which belong to + the function that the decorated decorator was decorating. + + @param args: args passed to the decorated function. + @param kwargs: kwargs passed to the decorated function. + """ + add_decorated_full_name(args[0]) + return obj(*outer_args, **outer_kwargs)(*args, **kwargs) + + inner_wrapper.__doc__ = obj.__doc__ + inner_wrapper.__name__ = obj.__name__ + inner_wrapper.__module__ = obj.__module__ + + # The decorator being decorated may have args, so both + # syntax need to be supported. + if (len(outer_args) == 1 and len(outer_kwargs) == 0 and + callable(outer_args[0])): + add_decorated_full_name(outer_args[0]) + return obj(outer_args[0]) + else: + return inner_wrapper + + return outer_wrapper + + +@add_full_name +def deprecated(*args, **kwargs): + """Decorator to output a deprecation warning.
@param instead: if provided, will be used to specify the replacement @type instead: string """ - def decorator(method): + def decorator(obj): + """Outer wrapper. + + The outer wrapper is used to create the decorating wrapper. + + @param obj: function being wrapped + @type obj: object + """ def wrapper(*args, **kwargs): - funcname = method.__name__ - classname = args[0].__class__.__name__ + """Replacement function. + + @param args: args passed to the decorated function. + @type args: list + @param kwargs: kwargs passed to the decorated function. + @type kwargs: dict + @return: the value returned by the decorated function + @rtype: any + """ + name = obj.__full_name__ if instead: - warning(u"%s.%s is DEPRECATED, use %s instead." - % (classname, funcname, instead)) + warning(u"%s is deprecated, use %s instead." % (name, instead)) else: - warning(u"%s.%s is DEPRECATED." % (classname, funcname)) - return method(*args, **kwargs) - wrapper.__name__ = method.__name__ + warning(u"%s is deprecated." % (name)) + return obj(*args, **kwargs) + + wrapper.__doc__ = obj.__doc__ + wrapper.__name__ = obj.__name__ + wrapper.__module__ = obj.__module__ return wrapper - return decorator + + without_parameters = len(args) == 1 and len(kwargs) == 0 and callable(args[0]) + if 'instead' in kwargs: + instead = kwargs['instead'] + elif not without_parameters and len(args) == 1: + instead = args[0] + else: + instead = False + + # When called as @deprecated, return a replacement function + if without_parameters: + return decorator(args[0]) + # Otherwise return a decorator, which returns a replacement function + else: + return decorator
def deprecate_arg(old_arg, new_arg): """Decorator to declare old_arg deprecated and replace it with new_arg.""" _logger = ""
- def decorator(method): + def decorator(obj): + """Outer wrapper. + + The outer wrapper is used to create the decorating wrapper. + + @param obj: function being wrapped + @type obj: object + """ def wrapper(*__args, **__kw): - meth_name = method.__name__ + """Replacement function. + + @param __args: args passed to the decorated function + @type __args: list + @param __kwargs: kwargs passed to the decorated function + @type __kwargs: dict + @return: the value returned by the decorated function + @rtype: any + """ + name = obj.__full_name__ if old_arg in __kw: if new_arg: if new_arg in __kw: warning( -u"%(new_arg)s argument of %(meth_name)s replaces %(old_arg)s; cannot use both." +u"%(new_arg)s argument of %(name)s replaces %(old_arg)s; cannot use both." % locals()) else: warning( -u"%(old_arg)s argument of %(meth_name)s is deprecated; use %(new_arg)s instead." +u"%(old_arg)s argument of %(name)s is deprecated; use %(new_arg)s instead." % locals()) __kw[new_arg] = __kw[old_arg] else: debug( -u"%(old_arg)s argument of %(meth_name)s is deprecated." +u"%(old_arg)s argument of %(name)s is deprecated." % locals(), _logger) del __kw[old_arg] - return method(*__args, **__kw) - wrapper.__doc__ = method.__doc__ - wrapper.__name__ = method.__name__ + return obj(*__args, **__kw) + + wrapper.__doc__ = obj.__doc__ + wrapper.__name__ = obj.__name__ + wrapper.__module__ = obj.__module__ + if not hasattr(obj, '__full_name__'): + add_decorated_full_name(obj) + wrapper.__full_name__ = obj.__full_name__ return wrapper return decorator
diff --git a/tests/deprecation_tests.py b/tests/deprecation_tests.py index 6880bdd..3f5894a 100644 --- a/tests/deprecation_tests.py +++ b/tests/deprecation_tests.py @@ -7,8 +7,69 @@ # __version__ = '$Id$'
-from pywikibot.tools import deprecated, deprecate_arg -from tests.aspects import unittest, DeprecationTestCase +from pywikibot.tools import deprecated, deprecate_arg, add_full_name +from tests.aspects import unittest, DeprecationTestCase, TestCase + + +@add_full_name +def noop(foo=None): + """Dummy decorator.""" + def decorator(obj): + def wrapper(*args, **kwargs): + raise Exception(obj.__full_name__) + return obj(*args, **kwargs) + return wrapper + return decorator + + +@add_full_name +def noop2(): + """Dummy decorator.""" + def decorator(obj): + def wrapper(*args, **kwargs): + raise Exception(obj.__full_name__) + return obj(*args, **kwargs) + return wrapper + return decorator + + +@noop() +def decorated_func(): + """Test dummy decorator.""" + pass + + +@noop(foo='bar') +def decorated_func2(): + """Test dummy decorator.""" + pass + + +@noop('baz') +def decorated_func3(): + """Test dummy decorator.""" + pass + + +class DecoratorFullNameTestCase(TestCase): + + """Class with methods deprecated.""" + + net = False + + def test_add_full_name_decorator(self): + self.assertRaisesRegex( + Exception, + __name__ + '.decorated_func', + decorated_func) + self.assertRaisesRegex( + Exception, + __name__ + '.decorated_func2', + decorated_func2) + self.assertRaisesRegex( + Exception, + __name__ + '.decorated_func3', + decorated_func3)
@deprecated() @@ -19,6 +80,12 @@
@deprecated def deprecated_func2(foo=None): + """Deprecated function.""" + return foo + + +@deprecated(instead='baz') +def deprecated_func_instead(foo=None): """Deprecated function.""" return foo
@@ -110,55 +177,75 @@
net = False
- @unittest.expectedFailure def test_deprecated_function_zero_arg(self): """Test @deprecated with functions, with zero arguments.""" rv = deprecated_func() self.assertEqual(rv, None) - self.assertDeprecation('deprecated_func is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func is deprecated.')
def test_deprecated_function(self): """Test @deprecated with functions.""" rv = deprecated_func('a') self.assertEqual(rv, 'a') - self.assertDeprecation('str.deprecated_func is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func(1) self.assertEqual(rv, 1) - self.assertDeprecation('int.deprecated_func is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func is deprecated.')
- @unittest.expectedFailure def test_deprecated_function2(self): """Test @deprecated with functions.""" rv = deprecated_func2('a') self.assertEqual(rv, 'a') - self.assertDeprecation('str.deprecated_func is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func2 is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func2(1) self.assertEqual(rv, 1) - self.assertDeprecation('int.deprecated_func is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func2 is deprecated.') + + def test_deprecated_function_instead(self): + """Test @deprecated with functions, using instead.""" + rv = deprecated_func_instead('a') + self.assertEqual(rv, 'a') + self.assertDeprecation( + __name__ + '.deprecated_func_instead is deprecated, use baz instead.')
def test_deprecated_function_bad_args(self): - """Test weakness in @deprecated.""" rv = deprecated_func_bad_args(None) self.assertEqual(rv, None) - self.assertDeprecation('NoneType.deprecated_func_bad_args is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func_bad_args is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func_bad_args('a') self.assertEqual(rv, 'a') - self.assertDeprecation('str.deprecated_func_bad_args is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func_bad_args is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func_bad_args(1) self.assertEqual(rv, 1) - self.assertDeprecation('int.deprecated_func_bad_args is DEPRECATED.') + self.assertDeprecation( + __name__ + '.deprecated_func_bad_args is deprecated.') + + DeprecatorTestCase._reset_messages() + + f = DeprecatedMethodClass() + rv = deprecated_func_bad_args(f) + self.assertEqual(rv, f) + self.assertDeprecation( + __name__ + '.deprecated_func_bad_args is deprecated.')
def test_deprecated_instance_method(self): f = DeprecatedMethodClass() @@ -166,93 +253,93 @@ rv = f.instance_method() self.assertEqual(rv, None) self.assertEqual(f.foo, None) - self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = f.instance_method('a') self.assertEqual(rv, 'a') self.assertEqual(f.foo, 'a') - self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = f.instance_method(1) self.assertEqual(rv, 1) self.assertEqual(f.foo, 1) - self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
- @unittest.expectedFailure + #@unittest.expectedFailure def test_deprecated_instance_method2(self): f = DeprecatedMethodClass()
rv = f.instance_method2() self.assertEqual(rv, None) self.assertEqual(f.foo, None) - self.assertDeprecation('DeprecatedMethodClass.instance_method2 is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.instance_method2 is deprecated.')
def test_deprecated_class_method(self): """Test @deprecated with class methods.""" rv = DeprecatedMethodClass.class_method() self.assertEqual(rv, None) - self.assertDeprecation('type.class_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.class_method('a') self.assertEqual(rv, 'a') - self.assertDeprecation('type.class_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.class_method(1) self.assertEqual(rv, 1) - self.assertDeprecation('type.class_method is DEPRECATED.') + self.assertDeprecation( + __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
- @unittest.expectedFailure def test_deprecated_static_method_zero_args(self): """Test @deprecated with static methods, with zero arguments.""" rv = DeprecatedMethodClass.static_method() - # raises: IndexError: tuple index out of range - # because args[0] is used even if args is empty self.assertEqual(rv, None) - self.assertDeprecation('DeprecatedMethodClass.static_method is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
def test_deprecated_static_method(self): """Test @deprecated with static methods.""" rv = DeprecatedMethodClass.static_method('a') self.assertEqual(rv, 'a') - self.assertDeprecation('str.static_method is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.static_method(1) self.assertEqual(rv, 1) - self.assertDeprecation('int.static_method is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
- @unittest.expectedFailure def test_deprecate_class_zero_arg(self): """Test @deprecated with classes, without arguments.""" df = DeprecatedClassNoInit() - # raises: IndexError: tuple index out of range - # because args[0] is used even if args is empty self.assertEqual(df.__doc__, 'Deprecated class.') - self.assertDeprecation('DeprecatedClassNoInit is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedClassNoInit is deprecated.')
DeprecatorTestCase._reset_messages()
df = DeprecatedClass() self.assertEqual(df.foo, None) - self.assertDeprecation('DeprecatedClass is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedClass is deprecated.')
def test_deprecate_class(self): """Test @deprecated with classes.""" df = DeprecatedClass('a') self.assertEqual(df.foo, 'a') - self.assertDeprecation('str.DeprecatedClass is DEPRECATED.') + self.assertDeprecation(__name__ + '.DeprecatedClass is deprecated.')
- def test_deprecated_function_arg(self): - """Test @deprecate_arg with function arguments.""" + def test_deprecate_function_arg(self): rv = deprecated_func_arg() self.assertEqual(rv, None) self.assertNoDeprecation() @@ -263,7 +350,7 @@
rv = deprecated_func_arg(bah='b') self.assertEqual(rv, 'b') - self.assertDeprecation('bah argument of deprecated_func_arg is deprecated; use foo instead.') + self.assertDeprecation('bah argument of ' + __name__ + '.deprecated_func_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -293,7 +380,7 @@ self.assertEqual(rv, 'b') self.assertEqual(f.foo, 'b') self.assertDeprecation( - 'bah argument of deprecated_instance_method_arg is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -313,27 +400,27 @@ rv = f.deprecated_instance_method_args(bah='b', bah2='c') self.assertEqual(rv, ('b', 'c')) self.assertDeprecation( - 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.') self.assertDeprecation( - 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.') + 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
rv = f.deprecated_instance_method_args(foo='b', bah2='c') self.assertEqual(rv, ('b', 'c')) self.assertNoDeprecation( - 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.') self.assertDeprecation( - 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.') + 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
rv = f.deprecated_instance_method_args(foo2='c', bah='b') self.assertEqual(rv, ('b', 'c')) self.assertDeprecation( - 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.') self.assertNoDeprecation( - 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.') + 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
@@ -349,9 +436,9 @@ self.assertEqual(rv, 'a') self.assertEqual(f.foo, 'a') self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.') self.assertNoDeprecation( - 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -359,9 +446,9 @@ self.assertEqual(rv, 'b') self.assertEqual(f.foo, 'b') self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.') self.assertDeprecation( - 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -369,21 +456,21 @@ self.assertEqual(rv, 1) self.assertEqual(f.foo, 1) self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.') self.assertNoDeprecation( - 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
def test_deprecated_instance_method_and_arg2(self): - """Test @deprecate_arg and @deprecated with instance methods.""" + """Test @deprecated and @deprecate_arg with instance methods.""" f = DeprecatedMethodClass()
rv = f.deprecated_instance_method_and_arg2('a') self.assertEqual(rv, 'a') self.assertEqual(f.foo, 'a') self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.') self.assertNoDeprecation( - 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -391,9 +478,9 @@ self.assertEqual(rv, 'b') self.assertEqual(f.foo, 'b') self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.') self.assertDeprecation( - 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -401,9 +488,9 @@ self.assertEqual(rv, 1) self.assertEqual(f.foo, 1) self.assertDeprecation( - 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.') + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.') self.assertNoDeprecation( - 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.') + 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
if __name__ == '__main__': diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py index ec26fd9..39645d9 100644 --- a/tests/dry_api_tests.py +++ b/tests/dry_api_tests.py @@ -21,13 +21,11 @@ #
import os -import sys import datetime
import pywikibot from pywikibot.data.api import ( Request, - MIMEMultipart, CachedRequest, QueryGenerator, ) diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py index 56a44dd..524c9cd 100644 --- a/tests/dry_site_tests.py +++ b/tests/dry_site_tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +"""Tests against a fake Site object.""" # # (C) Pywikibot team, 2012-2014 # @@ -8,14 +9,18 @@ #
import pywikibot +from pywikibot.tools import deprecated from pywikibot.site import must_be, need_version from pywikibot.comms.http import user_agent
from tests.utils import DummySiteinfo -from tests.aspects import unittest, TestCase +from tests.aspects import unittest, TestCase, DeprecationTestCase
class DrySite(pywikibot.site.APISite): + + """Fake APISite object.""" + _loginstatus = pywikibot.site.LoginStatus.NOT_ATTEMPTED
@property @@ -28,6 +33,8 @@
class TestDrySite(TestCase): + + """Tests against a fake Site object."""
net = False
@@ -167,7 +174,7 @@ self.assertRaises(pywikibot.NoSuchSite, self.call_this_user_req_function, args, kwargs)
-class TestNeedVersion(TestCase): +class TestNeedVersion(DeprecationTestCase):
"""Test cases for the need_version decorator."""
@@ -191,11 +198,62 @@ def older(self): return True
- def testNeedVersion(self): + @need_version("1.14") + @deprecated + def deprecated_unavailable_method(self): + return True + + @deprecated + @need_version("1.14") + def deprecated_unavailable_method2(self): + return True + + @need_version("1.12") + @deprecated + def deprecated_available_method(self): + return True + + @deprecated + @need_version("1.12") + def deprecated_available_method2(self): + return True + + def test_need_version(self): self.assertRaises(NotImplementedError, self.too_new) self.assertTrue(self.old_enough()) self.assertTrue(self.older())
+ def test_need_version_fail_with_deprecated(self): + """Test order of combined version check and deprecation warning.""" + # The outermost decorator is the version check, so no deprecation message. + self.assertRaisesRegex( + NotImplementedError, + #__name__ + '.TestNeedVersion.deprecated_unavailable_method', + 'deprecated_unavailable_method', + self.deprecated_unavailable_method) + self.assertNoDeprecation() + + # The deprecator is first, but the version check still raises exception. + self.assertRaisesRegex( + NotImplementedError, + #__name__ + '.TestNeedVersion.deprecated_unavailable_method2', + 'deprecated_unavailable_method2', + self.deprecated_unavailable_method2) + self.assertDeprecation( + __name__ + '.TestNeedVersion.deprecated_unavailable_method2 is deprecated.') + + def test_need_version_success_with_deprecated(self): + """Test order of combined version check and deprecation warning.""" + self.deprecated_available_method() + self.assertDeprecation( + __name__ + '.TestNeedVersion.deprecated_available_method is deprecated.') + + TestNeedVersion._reset_messages() + + self.deprecated_available_method2() + self.assertDeprecation( + __name__ + '.TestNeedVersion.deprecated_available_method2 is deprecated.') +
if __name__ == '__main__': unittest.main() diff --git a/tox.ini b/tox.ini index aa1aa33..ebce604 100644 --- a/tox.ini +++ b/tox.ini @@ -61,6 +61,7 @@ ./tests/deprecation_tests.py \ ./tests/api_tests.py \ ./tests/dry_api_tests.py \ + ./tests/dry_site_tests.py \ ./scripts/maintenance/cache.py \ ./tests/upload_tests.py
pywikibot-commits@lists.wikimedia.org