jenkins-bot has submitted this change and it was merged.

Change subject: Use warnings for deprecation
......................................................................


Use warnings for deprecation

Warnings are now captured by logging, fixing buggy code from compat.
  https://www.mediawiki.org/wiki/Special:Code/pywikipedia/11420
Code deprecation is now ignored by default.
Config and command line args deprecation are shown by default.

Also
- fix deprecated config variable available_ssl_project
- lower case of DEPRECATED in redirect_func and DeprecationWrapper
- tool manage_wrapping for decorators
- remove use of locals() in deprecated_args
- use self._logger in ThreadedList debug calls
- intersect_generators debug call moved into ThreadedList
- Ignore import warnings when loading i18n message bundles

And deprecate methods which were too noisy to deprecate previously.

Bug: T72970
Change-Id: I95000736f3fc9ccb80fe32106fb6516abc62cdd6
---
M pywikibot/backports.py
M pywikibot/bot.py
M pywikibot/config2.py
M pywikibot/exceptions.py
M pywikibot/family.py
M pywikibot/i18n.py
M pywikibot/page.py
M pywikibot/pagegenerators.py
M pywikibot/site.py
M pywikibot/tools.py
M pywikibot/userinterfaces/terminal_interface_base.py
M scripts/casechecker.py
M scripts/delete.py
M scripts/states_redirect.py
M tests/__init__.py
M tests/aspects.py
M tests/data_ingestion_tests.py
M tests/deprecation_tests.py
M tests/dry_site_tests.py
M tests/exceptions_tests.py
M tests/family_tests.py
M tests/page_tests.py
M tests/pagegenerators_tests.py
M tests/site_tests.py
M tox.ini
25 files changed, 510 insertions(+), 193 deletions(-)

Approvals:
  John Vandenberg: Looks good to me, but someone else must approve
  XZise: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/pywikibot/backports.py b/pywikibot/backports.py
index 55712da..f24100d 100644
--- a/pywikibot/backports.py
+++ b/pywikibot/backports.py
@@ -54,10 +54,13 @@
 """
 #
 # (C) Python Software Foundation, 2001-2014
-# (C) with modifications from Pywikibot team, 2014
+# (C) with modifications from Pywikibot team, 2015
 #
 # Distributed under the terms of the PSF license.
 #
+
+import logging
+import warnings
 
 
 def format_range_unified(start, stop):
@@ -77,3 +80,86 @@
     if not length:
         beginning -= 1  # empty ranges begin at line just before the range
     return '{0},{1}'.format(beginning, length)
+
+
+# Logging/Warnings integration
+
+_warnings_showwarning = None
+
+
+class NullHandler(logging.Handler):
+
+    """
+    This handler does nothing.
+
+    It's intended to be used to avoid the "No handlers could be found for
+    logger XXX" one-off warning. This is important for library code, which
+    may contain code to log events. If a user of the library does not configure
+    logging, the one-off warning might be produced; to avoid this, the library
+    developer simply needs to instantiate a NullHandler and add it to the
+    top-level logger of the library module or package.
+
+    Copied from C{logging.NullHandler} which was introduced in Python 2.7.
+
+    @see: http://bugs.python.org/issue4384
+    """
+
+    def handle(self, record):
+        """Dummy handling."""
+        pass
+
+    def emit(self, record):
+        """Dummy handling."""
+        pass
+
+    def createLock(self):
+        """Dummy handling."""
+        self.lock = None
+
+
+def _showwarning(message, category, filename, lineno, file=None, line=None):
+    """
+    Implementation of showwarnings which redirects to logging.
+
+    It will first check to see if the file parameter is None. If a file is
+    specified, it will delegate to the original warnings implementation of
+    showwarning. Otherwise, it will call warnings.formatwarning and will log
+    the resulting string to a warnings logger named "py.warnings" with level
+    logging.WARNING.
+
+    Copied from C{logging._showwarning} which was introduced in Python 2.7.
+
+    @see: http://bugs.python.org/issue4384
+    """
+    if file is not None:
+        if _warnings_showwarning is not None:
+            _warnings_showwarning(message, category, filename, lineno, file, 
line)
+    else:
+        s = warnings.formatwarning(message, category, filename, lineno, line)
+        logger = logging.getLogger("py.warnings")
+        if not logger.handlers:
+            logger.addHandler(NullHandler())
+        logger.warning("%s", s)
+
+
+def captureWarnings(capture):
+    """
+    Capture warnings into logging.
+
+    If capture is true, redirect all warnings to the logging package.
+    If capture is False, ensure that warnings are not redirected to logging
+    but to their original destinations.
+
+    Copied from C{logging.captureWarnings} which was introduced in Python 2.7.
+
+    @see: http://bugs.python.org/issue4384
+    """
+    global _warnings_showwarning
+    if capture:
+        if _warnings_showwarning is None:
+            _warnings_showwarning = warnings.showwarning
+            warnings.showwarning = _showwarning
+    else:
+        if _warnings_showwarning is not None:
+            warnings.showwarning = _warnings_showwarning
+            _warnings_showwarning = None
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index e814aef..2e670e8 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -11,16 +11,16 @@
 # class definition that can be subclassed to create new, functional bot
 # scripts, instead of writing each one from scratch.
 
-
-import logging
-import logging.handlers
-# all output goes thru python std library "logging" module
+# Note: all output goes thru python std library "logging" module
 
 import datetime
 import json
+import logging
+import logging.handlers
 import os
 import re
 import sys
+import warnings
 import webbrowser
 
 _logger = "bot"
@@ -32,6 +32,8 @@
 INPUT = 25
 
 import pywikibot
+
+from pywikibot import backports
 from pywikibot import config
 from pywikibot import version
 from pywikibot.tools import deprecated
@@ -103,6 +105,29 @@
 
     def format(self, record):
         """Strip trailing newlines before outputting text to file."""
+        # Warnings captured from the warnings system are not processed by
+        # logoutput(), so the 'context' variables are missing.
+        # The same context details are provided by Python 2.7, but need to
+        # be extracted from the warning message for Python 2.6.
+        if record.name == 'py.warnings' and 'caller_file' not in 
record.__dict__:
+            assert(len(record.args) == 1)
+            msg = record.args[0]
+
+            if sys.version_info < (2, 7):
+                record.pathname = msg.partition(':')[0]
+                record.lineno = msg.partition(':')[2].partition(':')[0]
+                record.module = msg.rpartition('/')[2].rpartition('.')[0]
+            else:
+                assert(msg.startswith(record.pathname + ':'))
+
+            record.__dict__['caller_file'] = record.pathname
+            record.__dict__['caller_name'] = record.module
+            record.__dict__['caller_line'] = record.lineno
+
+            # Remove the path and the line number, and strip the extra space
+            msg = msg.partition(':')[2].partition(':')[2].lstrip()
+            record.args = (msg,)
+
         text = logging.handlers.RotatingFileHandler.format(self, record)
         return text.rstrip("\r\n")
 
@@ -208,8 +233,20 @@
 
     root_logger = logging.getLogger("pywiki")
     root_logger.setLevel(DEBUG + 1)  # all records except DEBUG go to logger
-    if hasattr(root_logger, 'captureWarnings'):
-        root_logger.captureWarnings(True)  # introduced in Python >= 2.7
+
+    warnings_logger = logging.getLogger("py.warnings")
+    warnings_logger.setLevel(DEBUG)
+
+    if hasattr(logging, 'captureWarnings'):
+        logging.captureWarnings(True)  # introduced in Python >= 2.7
+    else:
+        backports.captureWarnings(True)
+
+    if config.debug_log or 'deprecation' in config.log:
+        warnings.filterwarnings("always")
+    elif config.verbose_output:
+        warnings.filterwarnings("module")
+
     root_logger.handlers = []  # remove any old handlers
 
     # configure handler(s) for display to user interface
@@ -243,10 +280,11 @@
             debuglogger.setLevel(DEBUG)
             debuglogger.addHandler(file_handler)
 
+        warnings_logger.addHandler(file_handler)
+
     _handlers_initialized = True
 
     pywikibot.tools.debug = debug
-    pywikibot.tools.warning = warning
 
     writelogheader()
 
@@ -760,6 +798,7 @@
     return nonGlobalArgs
 
 
+@deprecated
 def handleArgs(*args):
     """DEPRECATED. Use handle_args()."""
     return handle_args(args)
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index 6fea03a..fb68ec1 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -22,9 +22,20 @@
 __version__ = '$Id$'
 #
 
+import collections
 import os
 import sys
-import collections
+
+from warnings import warn
+
+
+class _ConfigurationDeprecationWarning(UserWarning):
+
+    """Feature that is no longer supported."""
+
+    pass
+
+
 # Please keep _imported_modules in sync with the imports above
 _imported_modules = ('os', 'sys', 'collections')
 
@@ -40,7 +51,7 @@
 
 _private_values = ['authenticate', 'proxy', 'db_password']
 _deprecated_variables = ['use_SSL_onlogin', 'use_SSL_always',
-                         'available_ssl_project_comment']
+                         'available_ssl_project']
 
 # ############# ACCOUNT SETTINGS ##############
 
@@ -863,8 +874,9 @@
     globals()[_key] = _uc[_key]
 
     if _key in _deprecated_variables:
-        print("WARNING: '%s' is no longer a supported configuration variable."
-              "\nPlease inform the maintainers if you depend on it." % _key)
+        warn("'%s' is no longer a supported configuration variable.\n"
+             "Please inform the maintainers if you depend on it." % _key,
+             _ConfigurationDeprecationWarning)
 
 # Fix up default console_encoding
 if console_encoding is None:
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index 2a948c5..4b4161d 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8  -*-
 """
-Exception classes used throughout the framework.
+Exception and warning classes used throughout the framework.
 
 Error: Base class, all exceptions should the subclass of this class.
   - NoUsername: Username is not in user-config.py, or it is invalid.
@@ -45,6 +45,20 @@
   - CoordinateGlobeUnknownException: globe is not implemented yet.
   - EntityTypeUnknownException: entity type is not available on the site.
 
+DeprecationWarning: old functionality replaced by new functionality
+
+PendingDeprecationWarning: problematic code which has not yet been
+    fully deprecated, possibly because a replacement is not available
+
+RuntimeWarning: problems developers should have fixed, and users need to
+    be aware of its status.
+  - tools._NotImplementedWarning: do not use
+  - NotImplementedWarning: functionality not implemented
+
+UserWarning: warnings targetted at users
+  - config2._ConfigurationDeprecationWarning: user configuration file problems
+  - ArgumentDeprecationWarning: command line argument problems
+  - FamilyMaintenanceWarning: missing information in family definition
 """
 #
 # (C) Pywikibot team, 2008
@@ -55,12 +69,33 @@
 
 import sys
 
-from pywikibot.tools import UnicodeMixin
+from pywikibot.tools import UnicodeMixin, _NotImplementedWarning
 
 if sys.version_info[0] > 2:
     unicode = str
 
 
+class NotImplementedWarning(_NotImplementedWarning):
+
+    """Feature that is no longer implemented."""
+
+    pass
+
+
+class ArgumentDeprecationWarning(UserWarning):
+
+    """Command line argument that is no longer supported."""
+
+    pass
+
+
+class FamilyMaintenanceWarning(UserWarning):
+
+    """Family class is missing definitions."""
+
+    pass
+
+
 class Error(UnicodeMixin, Exception):  # noqa
 
     """Pywikibot error"""
diff --git a/pywikibot/family.py b/pywikibot/family.py
index 130169f..cd90d87 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -13,6 +13,7 @@
 import re
 import collections
 import imp
+import warnings
 
 if sys.version_info[0] > 2:
     import urllib.parse as urlparse
@@ -20,6 +21,7 @@
     import urlparse
 
 import pywikibot
+
 from pywikibot import config2 as config
 from pywikibot.tools import deprecated, deprecate_arg
 from pywikibot.exceptions import UnknownFamily, Error
@@ -875,7 +877,8 @@
 
         try:
             # Ignore warnings due to dots in family names.
-            import warnings
+            # TODO: use more specific filter, so that family classes can use
+            #     RuntimeWarning's while loading.
             with warnings.catch_warnings():
                 warnings.simplefilter("ignore", RuntimeWarning)
                 myfamily = imp.load_source(fam, config.family_files[fam])
diff --git a/pywikibot/i18n.py b/pywikibot/i18n.py
index ac10b82..bd9b418 100644
--- a/pywikibot/i18n.py
+++ b/pywikibot/i18n.py
@@ -16,9 +16,13 @@
 import sys
 import re
 import locale
+import warnings
+
 from pywikibot import Error
 from .plural import plural_rules
+
 import pywikibot
+
 from . import config2 as config
 
 if sys.version_info[0] > 2:
@@ -236,6 +240,18 @@
     pass
 
 
+def _get_messages_bundle(name):
+    """Load all translation messages for a bundle name."""
+    with warnings.catch_warnings():
+        # Ignore 'missing __init__.py' as import looks at the JSON
+        # directories before loading the python file.
+        warnings.simplefilter("ignore", ImportWarning)
+        transdict = getattr(__import__(messages_package_name,
+                                       fromlist=[name]),
+                            name).msg
+    return transdict
+
+
 def _extract_plural(code, message, parameters):
     """Check for the plural variants in message and replace them.
 
@@ -364,9 +380,7 @@
     @type fallback: boolean
     """
     package = twtitle.split("-")[0]
-    transdict = getattr(__import__(messages_package_name, fromlist=[package]),
-                        package).msg
-
+    transdict = _get_messages_bundle(package)
     code_needed = False
     # If a site is given instead of a code, use its language
     if hasattr(code, 'code'):
@@ -498,8 +512,7 @@
     @param twtitle: The TranslateWiki string title, in <package>-<key> format
     """
     package = twtitle.split("-")[0]
-    transdict = getattr(__import__(messages_package_name, fromlist=[package]),
-                        package).msg
+    transdict = _get_messages_bundle(package)
     # If a site is given instead of a code, use its language
     if hasattr(code, 'code'):
         code = code.code
@@ -513,8 +526,7 @@
     @param twtitle: The TranslateWiki string title, in <package>-<key> format
     """
     package = twtitle.split("-")[0]
-    transdict = getattr(__import__(messages_package_name, fromlist=[package]),
-                        package).msg
+    transdict = _get_messages_bundle(package)
     return (lang for lang in sorted(transdict.keys()) if lang != 'qqq')
 
 
diff --git a/pywikibot/page.py b/pywikibot/page.py
index ef4ac5c..6fa838d 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -24,6 +24,8 @@
 import sys
 import unicodedata
 
+from warnings import warn
+
 try:
     from collections import OrderedDict
 except ImportError:
@@ -51,7 +53,7 @@
 )
 from pywikibot.tools import (
     ComparableMixin, deprecated, deprecate_arg, deprecated_args,
-    remove_last_args
+    remove_last_args, _NotImplementedWarning,
 )
 from pywikibot import textlib
 
@@ -412,7 +414,7 @@
                % (self.site.hostname(),
                   self.site.scriptpath(),
                   self.title(asUrl=True),
-                  (oldid if oldid is not None else self.latestRevision()))
+                  (oldid if oldid is not None else self.latest_revision_id))
 
     @property
     def latest_revision_id(self):
@@ -421,6 +423,7 @@
             self.site.loadrevisions(self)
         return self._revid
 
+    @deprecated('latest_revision_id')
     def latestRevision(self):
         """Return the current revision id for this page."""
         return self.latest_revision_id
@@ -581,6 +584,7 @@
         else:
             return min(x.revid for x in history)
 
+    @deprecated('previous_revision_id')
     def previousRevision(self):
         """
         Return the revision id for the previous revision.
@@ -1619,7 +1623,8 @@
         else:
             undelete_revs = []
         if reason is None:
-            pywikibot.warning('Not passing a reason for undelete() is 
deprecated.')
+            warn('Not passing a reason for undelete() is deprecated.',
+                 DeprecationWarning)
             pywikibot.output(u'Undeleting %s.' % (self.title(asLink=True)))
             reason = pywikibot.input(u'Please enter a reason for the 
undeletion:')
         self.site.undelete_page(self, reason, undelete_revs)
@@ -1643,7 +1648,7 @@
                        Defaults to protections is None
         @type  prompt: bool
         """
-        def deprecated(value, arg_name):
+        def process_deprecated_arg(value, arg_name):
             # if protections was set and value is None, don't interpret that
             # argument. But otherwise warn that the parameter was set
             # (even implicit)
@@ -1654,38 +1659,39 @@
                     value = ""
                 if value is not None:  # empty string is allowed
                     protections[arg_name] = value
-                    pywikibot.bot.warning(u'"protections" argument of '
-                                          'protect() replaces 
"{0}".'.format(arg_name))
+                    warn(u'"protections" argument of protect() replaces "{0}"'
+                         .format(arg_name),
+                         DeprecationWarning)
             else:
                 if value:
-                    pywikibot.bot.warning(u'"protections" argument of '
-                                          'protect() replaces "{0}"; cannot '
-                                          'use both.'.format(arg_name))
+                    warn(u'"protections" argument of protect() replaces "{0}";'
+                         u' cannot use both.'.format(arg_name),
+                         RuntimeWarning)
 
         # buffer that, because it might get changed
         called_using_deprecated_arg = protections is None
         if called_using_deprecated_arg:
             protections = {}
-        deprecated(edit, "edit")
-        deprecated(move, "move")
-        deprecated(create, "create")
-        deprecated(upload, "upload")
+        process_deprecated_arg(edit, "edit")
+        process_deprecated_arg(move, "move")
+        process_deprecated_arg(create, "create")
+        process_deprecated_arg(upload, "upload")
 
         if reason is None:
             pywikibot.output(u'Preparing to protection change of %s.'
                              % (self.title(asLink=True)))
             reason = pywikibot.input(u'Please enter a reason for the action:')
         if unprotect:
-            pywikibot.bot.warning(u'"unprotect" argument of protect() is '
-                                  'deprecated')
+            warn(u'"unprotect" argument of protect() is deprecated',
+                 DeprecationWarning, 2)
             protections = dict(
                 [(p_type, "") for p_type in self.applicable_protections()])
         answer = 'y'
         if called_using_deprecated_arg and prompt is None:
             prompt = True
         if prompt:
-            pywikibot.bot.warning(u'"prompt" argument of protect() is '
-                                  'deprecated')
+            warn(u'"prompt" argument of protect() is deprecated',
+                 DeprecationWarning, 2)
         if prompt and not hasattr(self.site, '_noProtectPrompt'):
             answer = pywikibot.input_choice(
                 u'Do you want to change the protection level of %s?'
@@ -1838,12 +1844,14 @@
 
     def removeImage(self, image, put=False, summary=None, safe=True):
         """Old method to remove all instances of an image from page."""
-        pywikibot.warning(u"Page.removeImage() is no longer supported.")
+        warn('Page.removeImage() is no longer supported.',
+             _NotImplementedWarning, 2)
 
     def replaceImage(self, image, replacement=None, put=False, summary=None,
                      safe=True):
         """Old method to replace all instances of an image with another."""
-        pywikibot.warning(u"Page.replaceImage() is no longer supported.")
+        warn('Page.replaceImage() is no longer supported.',
+             _NotImplementedWarning, 2)
 
 
 class Page(BasePage):
@@ -3066,7 +3074,8 @@
 
         return self.id
 
-    def latestRevision(self):
+    @property
+    def latest_revision_id(self):
         """
         Get the revision identifier for the most recent revision of the entity.
 
diff --git a/pywikibot/pagegenerators.py b/pywikibot/pagegenerators.py
index 0053ce6..1a1b15a 100644
--- a/pywikibot/pagegenerators.py
+++ b/pywikibot/pagegenerators.py
@@ -28,6 +28,8 @@
 import sys
 import time
 
+from warnings import warn
+
 import pywikibot
 
 from pywikibot import date, config, i18n
@@ -38,7 +40,8 @@
     intersect_generators,
 )
 from pywikibot.comms import http
-import pywikibot.data.wikidataquery as wdquery
+from pywikibot.data import wikidataquery as wdquery
+from pywikibot.exceptions import ArgumentDeprecationWarning
 from pywikibot.site import Namespace
 
 if sys.version_info[0] > 2:
@@ -541,9 +544,10 @@
             gen = TextfilePageGenerator(textfilename, site=self.site)
         elif arg.startswith('-namespace') or arg.startswith('-ns'):
             if isinstance(self._namespaces, frozenset):
-                pywikibot.warning('Cannot handle arg %s as namespaces can not '
-                                  'be altered after a generator is created.'
-                                  % arg)
+                warn('Cannot handle arg %s as namespaces can not '
+                     'be altered after a generator is created.'
+                     % arg,
+                     ArgumentDeprecationWarning, 2)
                 return True
             value = None
             if arg.startswith('-ns:'):
@@ -771,9 +775,10 @@
                         user = None
                 else:
                     user = None
-                pywikibot.warning(u'The usage of "{0}" is discouraged. Use '
-                                  '-logevents "{1}" instead.'.format(
-                                  arg, ','.join((mode, user or '', 
str(total)))))
+                warn(u'The usage of "{0}" is deprecated. Use -logevents '
+                     u'"{1}" instead'.format(
+                         arg, ','.join((mode, user or '', str(total)))),
+                     ArgumentDeprecationWarning, 2)
                 gen = self._parse_log_events(mode, user, total)
 
         if gen:
diff --git a/pywikibot/site.py b/pywikibot/site.py
index c9e75b9..f86e430 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -18,19 +18,21 @@
 import os
 import re
 import sys
-from collections import Iterable, Container, namedtuple
 import threading
 import time
 import json
 import copy
 import mimetypes
 
+from collections import Iterable, Container, namedtuple
+from warnings import warn
+
 import pywikibot
 import pywikibot.family
 from pywikibot.tools import (
-    itergroup, deprecated, deprecate_arg, UnicodeMixin, ComparableMixin,
-    redirect_func, add_decorated_full_name, deprecated_args, remove_last_args,
-    SelfCallDict, SelfCallString, signature,
+    itergroup, UnicodeMixin, ComparableMixin, SelfCallDict, SelfCallString,
+    deprecated, deprecate_arg, deprecated_args, remove_last_args,
+    redirect_func, manage_wrapping,
 )
 from pywikibot.tools import MediaWikiVersion
 from pywikibot.throttle import Throttle
@@ -51,6 +53,7 @@
     NoPage,
     UnknownSite,
     SiteDefinitionError,
+    FamilyMaintenanceWarning,
     NoUsername,
     SpamfilterError,
     NoCreateError,
@@ -585,15 +588,16 @@
                     # should it just raise an Exception and fail?
                     # this will help to check the dictionary ...
                     except KeyError:
-                        pywikibot.warning(
-                            u"Site {0} has no language defined in doc_subpages 
dict in {1}_family.py file"
-                            .format(self, self.family.name))
+                        warn(u"Site {0} has no language defined in "
+                             u"doc_subpages dict in {1}_family.py file"
+                             .format(self, self.family.name),
+                             FamilyMaintenanceWarning, 2)
             # doc_subpages not defined in x_family.py file
             except AttributeError:
                 doc = ()  # default
-                pywikibot.warning(
-                    u"Site {0} has no doc_subpages dict in {1}_family.py file"
-                    .format(self, self.family.name))
+                warn(u"Site {0} has no doc_subpages dict in {1}_family.py file"
+                     .format(self, self.family.name),
+                     FamilyMaintenanceWarning, 2)
             self._doc_subpage = doc
 
         return self._doc_subpage
@@ -1043,13 +1047,8 @@
         if not __debug__:
             return fn
 
-        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__
-        callee.__signature__ = signature(fn)
+        manage_wrapping(callee, fn)
+
         return callee
 
     return decorator
@@ -1075,13 +1074,7 @@
         if not __debug__:
             return fn
 
-        callee.__name__ = fn.__name__
-        callee.__doc__ = fn.__doc__
-        callee.__module__ = fn.__module__
-        callee.__signature__ = signature(fn)
-        if not hasattr(fn, '__full_name__'):
-            add_decorated_full_name(fn)
-        callee.__full_name__ = fn.__full_name__
+        manage_wrapping(callee, fn)
 
         return callee
     return decorator
diff --git a/pywikibot/tools.py b/pywikibot/tools.py
index 3d45090..6037195 100644
--- a/pywikibot/tools.py
+++ b/pywikibot/tools.py
@@ -15,6 +15,9 @@
 import threading
 import time
 import types
+
+from warnings import warn
+
 from distutils.version import Version
 
 if sys.version_info[0] > 2:
@@ -24,11 +27,21 @@
     import Queue
 
 
-# These variables are functions debug(str) and warning(str)
-# which are initially the builtin print function.
-# They exist here as the deprecators in this module rely only on them.
-# pywikibot updates these function variables in bot.init_handlers()
-debug = warning = print
+def print_debug(msg, *args, **kwargs):
+    """Simple debug routine."""
+    print(msg)
+
+
+# This variable uses the builtin print function.
+# pywikibot updates it to use logging in bot.init_handlers()
+debug = print_debug
+
+
+class _NotImplementedWarning(RuntimeWarning):
+
+    """Feature that is no longer implemented."""
+
+    pass
 
 
 def empty_iterator():
@@ -83,6 +96,7 @@
 
 
 def concat_options(message, line_length, options):
+    """Concatenate options."""
     indent = len(message) + 2
     line_length -= indent
     option_msg = u''
@@ -128,6 +142,7 @@
     MEDIAWIKI_VERSION = 
re.compile(r'^(\d+(?:\.\d+)+)(wmf(\d+)|alpha|beta(\d+)|-?rc\.?(\d+))?$')
 
     def parse(self, vstring):
+        """Parse version string."""
         version_match = MediaWikiVersion.MEDIAWIKI_VERSION.match(vstring)
         if not version_match:
             raise ValueError('Invalid version number "{0}"'.format(vstring))
@@ -334,15 +349,16 @@
             time.sleep(2)
         super(ThreadList, self).append(thd)
         thd.start()
+        debug("thread started: %r" % thd, self._logger)
 
     def stop_all(self):
         """Stop all threads the pool."""
         if self:
-            debug(u'EARLY QUIT: Threads: %d' % len(self), ThreadList._logger)
+            debug(u'EARLY QUIT: Threads: %d' % len(self), self._logger)
         for thd in self:
             thd.stop()
             debug(u'EARLY QUIT: Queue size left in %s: %s'
-                  % (thd, thd.queue.qsize()), ThreadList._logger)
+                  % (thd, thd.queue.qsize()), self._logger)
 
 
 def intersect_generators(genlist):
@@ -361,8 +377,6 @@
     @param genlist: list of page generators
     @type genlist: list
     """
-    _logger = ""
-
     # Item is cached to check that it is found n_gen
     # times before being yielded.
     cache = collections.defaultdict(set)
@@ -375,7 +389,6 @@
     for source in genlist:
         threaded_gen = ThreadedGenerator(name=repr(source), target=source)
         thrlist.append(threaded_gen)
-        debug("INTERSECT: thread started: %r" % threaded_gen, _logger)
 
     while True:
         # Get items from queues in a round-robin way.
@@ -528,7 +541,7 @@
         return None
 
 
-def add_decorated_full_name(obj):
+def add_decorated_full_name(obj, stacklevel=1):
     """Extract full object name, including class, and store in __full_name__.
 
     This must be done on all decorators that are chained together, otherwise
@@ -536,13 +549,15 @@
 
     @param obj: A object being decorated
     @type obj: object
+    @param stacklevel: level to use
+    @type stacklevel: int
     """
     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
+    frame = sys._getframe(stacklevel + 1)
     class_name = frame.f_code.co_name
     if class_name and class_name != '<module>':
         obj.__full_name__ = (obj.__module__ + '.' +
@@ -551,6 +566,36 @@
     else:
         obj.__full_name__ = (obj.__module__ + '.' +
                              obj.__name__)
+
+
+def manage_wrapping(wrapper, obj):
+    """Add attributes to wrapper and wrapped functions."""
+    wrapper.__doc__ = obj.__doc__
+    wrapper.__name__ = obj.__name__
+    wrapper.__module__ = obj.__module__
+    wrapper.__signature__ = signature(obj)
+
+    if not hasattr(obj, '__full_name__'):
+        add_decorated_full_name(obj, 2)
+    wrapper.__full_name__ = obj.__full_name__
+
+    # Use the previous wrappers depth, if it exists
+    wrapper.__depth__ = getattr(obj, '__depth__', 0) + 1
+
+    # Obtain the wrapped object from the previous wrapper
+    wrapped = getattr(obj, '__wrapped__', obj)
+    wrapper.__wrapped__ = wrapped
+
+    # Increment the number of wrappers
+    if hasattr(wrapped, '__wrappers__'):
+        wrapped.__wrappers__ += 1
+    else:
+        wrapped.__wrappers__ = 1
+
+
+def get_wrapper_depth(wrapper):
+    """Return depth of wrapper function."""
+    return wrapper.__wrapped__.__wrappers__ + (1 - wrapper.__depth__)
 
 
 def add_full_name(obj):
@@ -641,19 +686,20 @@
             @rtype: any
             """
             name = obj.__full_name__
+            depth = get_wrapper_depth(wrapper) + 1
             if instead:
-                warning(u"%s is deprecated, use %s instead." % (name, instead))
+                warn(u"%s is deprecated, use %s instead." % (name, instead),
+                     DeprecationWarning, depth)
             else:
-                warning(u"%s is deprecated." % (name))
+                warn(u"%s is deprecated." % name,
+                     _NotImplementedWarning, depth)
             return obj(*args, **kwargs)
 
         if not __debug__:
             return obj
 
-        wrapper.__doc__ = obj.__doc__
-        wrapper.__name__ = obj.__name__
-        wrapper.__module__ = obj.__module__
-        wrapper.__signature__ = signature(obj)
+        manage_wrapping(wrapper, obj)
+
         return wrapper
 
     without_parameters = len(args) == 1 and len(kwargs) == 0 and 
callable(args[0])
@@ -688,8 +734,6 @@
         None it drops the value and prints a warning. If False it just drops
         the value.
     """
-    _logger = ""
-
     def decorator(obj):
         """Outer wrapper.
 
@@ -709,33 +753,44 @@
             @rtype: any
             """
             name = obj.__full_name__
+            depth = get_wrapper_depth(wrapper) + 1
             for old_arg, new_arg in arg_pairs.items():
+                output_args = {
+                    'name': name,
+                    'old_arg': old_arg,
+                    'new_arg': new_arg,
+                }
                 if old_arg in __kw:
                     if new_arg not in [True, False, None]:
                         if new_arg in __kw:
-                            warning(u"%(new_arg)s argument of %(name)s "
-                                    "replaces %(old_arg)s; cannot use both."
-                                    % locals())
+                            warn(u"%(new_arg)s argument of %(name)s "
+                                 u"replaces %(old_arg)s; cannot use both."
+                                 % output_args,
+                                 RuntimeWarning, depth)
                         else:
                             # If the value is positionally given this will
                             # cause a TypeError, which is intentional
-                            warning(u"%(old_arg)s argument of %(name)s "
-                                    "is deprecated; use %(new_arg)s instead."
-                                    % locals())
+                            warn(u"%(old_arg)s argument of %(name)s "
+                                 u"is deprecated; use %(new_arg)s instead."
+                                 % output_args,
+                                 DeprecationWarning, depth)
                             __kw[new_arg] = __kw[old_arg]
-                    elif new_arg is not False:
-                        debug(u"%(old_arg)s argument of %(name)s is "
-                              "deprecated." % locals(), _logger)
+                    else:
+                        if new_arg is False:
+                            cls = PendingDeprecationWarning
+                        else:
+                            cls = DeprecationWarning
+                        warn(u"%(old_arg)s argument of %(name)s is deprecated."
+                             % output_args,
+                             cls, depth)
                     del __kw[old_arg]
             return obj(*__args, **__kw)
 
         if not __debug__:
             return obj
 
-        wrapper.__doc__ = obj.__doc__
-        wrapper.__name__ = obj.__name__
-        wrapper.__module__ = obj.__module__
-        wrapper.__signature__ = signature(obj)
+        manage_wrapping(wrapper, obj)
+
         if wrapper.__signature__:
             # Build a new signature with deprecated args added.
             params = collections.OrderedDict()
@@ -749,9 +804,7 @@
                     else NotImplemented)
             wrapper.__signature__ = inspect.Signature()
             wrapper.__signature__._parameters = params
-        if not hasattr(obj, '__full_name__'):
-            add_decorated_full_name(obj)
-        wrapper.__full_name__ = obj.__full_name__
+
         return wrapper
     return decorator
 
@@ -792,6 +845,7 @@
             @rtype: any
             """
             name = obj.__full_name__
+            depth = get_wrapper_depth(wrapper) + 1
             args, varargs, kwargs, _ = inspect.getargspec(wrapper.__wrapped__)
             if varargs is not None and kwargs is not None:
                 raise ValueError(u'{1} may not have * or ** args.'.format(
@@ -807,19 +861,16 @@
             if deprecated:
                 # sort them according to arg_names
                 deprecated = [arg for arg in arg_names if arg in deprecated]
-                warning(u"The trailing arguments ('{0}') of {1} are "
-                        "deprecated. The value(s) provided for '{2}' have "
-                        "been dropped.".format("', '".join(arg_names), name,
-                                               "', '".join(deprecated)))
+                warn(u"The trailing arguments ('{0}') of {1} are deprecated. "
+                     u"The value(s) provided for '{2}' have been dropped.".
+                     format("', '".join(arg_names),
+                            name,
+                            "', '".join(deprecated)),
+                     DeprecationWarning, depth)
             return obj(*new_args, **new_kwargs)
 
-        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__
-        wrapper.__wrapped__ = getattr(obj, '__wrapped__', obj)
+        manage_wrapping(wrapper, obj)
+
         return wrapper
     return decorator
 
@@ -852,7 +903,7 @@
     @rtype: callable
     """
     def call(*a, **kw):
-        warning(warn_message)
+        warn(warn_message, DeprecationWarning, 2)
         return target(*a, **kw)
     if target_module is None:
         target_module = target.__module__
@@ -867,7 +918,7 @@
     if class_name:
         target_module += class_name + '.'
         source_module += class_name + '.'
-    warn_message = ('{source}{old} is DEPRECATED, use {target}{new} '
+    warn_message = ('{source}{old} is deprecated, use {target}{new} '
                     'instead.').format(new=target.__name__,
                                        old=old_name or target.__name__,
                                        target=target_module,
@@ -944,9 +995,9 @@
 
         if not warning_message:
             if replacement_name:
-                warning_message = u"{0}.{1} is DEPRECATED, use {2} instead."
+                warning_message = u"{0}.{1} is deprecated, use {2} instead."
             else:
-                warning_message = u"{0}.{1} is DEPRECATED."
+                warning_message = u"{0}.{1} is deprecated."
 
         self._deprecated[name] = replacement_name, replacement, warning_message
 
@@ -958,9 +1009,9 @@
         """Return the attribute with a deprecation warning if required."""
         if attr in self._deprecated:
             warning_message = self._deprecated[attr][2]
-            warning(warning_message.format(self._module.__name__,
-                                           attr,
-                                           self._deprecated[attr][0]))
+            warn(warning_message.format(self._module.__name__, attr,
+                                        self._deprecated[attr][0]),
+                 DeprecationWarning, 2)
             if self._deprecated[attr][1]:
                 return self._deprecated[attr][1]
         return getattr(self._module, attr)
diff --git a/pywikibot/userinterfaces/terminal_interface_base.py 
b/pywikibot/userinterfaces/terminal_interface_base.py
index c367b65..a900870 100755
--- a/pywikibot/userinterfaces/terminal_interface_base.py
+++ b/pywikibot/userinterfaces/terminal_interface_base.py
@@ -103,6 +103,9 @@
             TerminalFormatter(fmt="%(levelname)s: %(message)s%(newline)s"))
         root_logger.addHandler(warning_handler)
 
+        warnings_logger = logging.getLogger("py.warnings")
+        warnings_logger.addHandler(warning_handler)
+
     def printNonColorized(self, text, targetStream):
         """
         Write the text non colorized to the target stream.
@@ -405,6 +408,24 @@
 
     def emit(self, record):
         """Emit the record formatted to the output and return it."""
+        if record.name == 'py.warnings':
+            # Each warning appears twice
+            # the second time it has a 'message'
+            if 'message' in record.__dict__:
+                return
+
+            # Remove the last line, if it appears to be the warn() call
+            msg = record.args[0]
+            is_useless_source_output = any(
+                s in msg for s in
+                ('warn(', 'exceptions.', 'Warning)', 'Warning,'))
+
+            if is_useless_source_output:
+                record.args = ('\n'.join(record.args[0].splitlines()[0:-1]),)
+
+            if 'newline' not in record.__dict__:
+                record.__dict__['newline'] = '\n'
+
         text = self.format(record)
         return self.UI.output(text, targetStream=self.stream)
 
diff --git a/scripts/casechecker.py b/scripts/casechecker.py
index cc9350a..e77e856 100644
--- a/scripts/casechecker.py
+++ b/scripts/casechecker.py
@@ -139,7 +139,7 @@
 
     def __init__(self):
 
-        for arg in pywikibot.handleArgs():
+        for arg in pywikibot.handle_args():
             if arg.startswith('-from'):
                 if arg.startswith('-from:'):
                     self.apfrom = arg[6:]
diff --git a/scripts/delete.py b/scripts/delete.py
index 870fad3..5bc1904 100644
--- a/scripts/delete.py
+++ b/scripts/delete.py
@@ -33,7 +33,11 @@
 __version__ = '$Id$'
 #
 
+from warnings import warn
+
 import pywikibot
+
+from pywikibot import exceptions
 from pywikibot import i18n, pagegenerators, CurrentPageBot
 
 # This is required for the text that is shown when you run this script
@@ -110,8 +114,8 @@
             else:
                 summary = arg[len('-summary:'):]
         elif arg.startswith('-images'):
-            pywikibot.output('\n\03{lightred}-image option is deprecated. '
-                             'Please use -imageused instead.\03{default}\n')
+            warn('-image option is deprecated. Please use -imageused instead.',
+                 exceptions.ArgumentDeprecationWarning)
             local_args.append('-imageused' + arg[7:])
         elif arg.startswith('-undelete'):
             options['undelete'] = True
diff --git a/scripts/states_redirect.py b/scripts/states_redirect.py
index 879df31..745c571 100644
--- a/scripts/states_redirect.py
+++ b/scripts/states_redirect.py
@@ -120,10 +120,16 @@
                         pl.save(i18n.translate(self.site, msg))
 
 
-def main():
-    """Process command line arguments and invoke UsStatesBot."""
-    # Process global arguments to determine desired site
-    local_args = pywikibot.handleArgs()
+def main(*args):
+    """
+    Process command line arguments and invoke bot.
+
+    If args is an empty list, sys.argv is used.
+
+    @param args: command line arguments
+    @type args: list of unicode
+    """
+    local_args = pywikibot.handle_args(args)
     start = None
     force = False
 
diff --git a/tests/__init__.py b/tests/__init__.py
index 1ffcabc..dbc93d9 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -9,6 +9,7 @@
 
 import os
 import sys
+import warnings
 
 __all__ = ('httplib2', 'OrderedDict', '_cache_dir', 'TestRequest',
            'patch_request', 'unpatch_request')
@@ -204,6 +205,8 @@
 cache_misses = 0
 cache_hits = 0
 
+warnings.filterwarnings("always")
+
 
 class TestRequest(CachedRequest):
 
diff --git a/tests/aspects.py b/tests/aspects.py
index 41f5071..be21cf8 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -34,6 +34,7 @@
 import os
 import sys
 import time
+import warnings
 
 import pywikibot
 
@@ -1165,38 +1166,55 @@
 
     """Test cases for deprecation function in the tools module."""
 
-    deprecation_messages = []
+    def __init__(self, *args, **kwargs):
+        super(DeprecationTestCase, self).__init__(*args, **kwargs)
+        self.warning_log = []
 
-    @staticmethod
-    def _record_messages(msg, *args, **kwargs):
-        DeprecationTestCase.deprecation_messages.append(msg)
+        self.expect_warning_filename = inspect.getfile(self.__class__)
+        if self.expect_warning_filename.endswith((".pyc", ".pyo")):
+            self.expect_warning_filename = self.expect_warning_filename[:-1]
 
-    @staticmethod
-    def _reset_messages():
-        DeprecationTestCase.deprecation_messages = []
+        self._do_test_warning_filename = True
+
+        self.context_manager = warnings.catch_warnings(record=True)
+
+    def _reset_messages(self):
+        self._do_test_warning_filename = True
+        del self.warning_log[:]
+
+    @property
+    def deprecation_messages(self):
+        messages = [str(item.message) for item in self.warning_log]
+        return messages
 
     def assertDeprecation(self, msg):
-        self.assertIn(msg, DeprecationTestCase.deprecation_messages)
+        self.assertIn(msg, self.deprecation_messages)
+        if self._do_test_warning_filename:
+            self.assertDeprecationFile(self.expect_warning_filename)
 
     def assertNoDeprecation(self, msg=None):
         if msg:
-            self.assertNotIn(msg, DeprecationTestCase.deprecation_messages)
+            self.assertNotIn(msg, self.deprecation_messages)
         else:
-            self.assertEqual([], DeprecationTestCase.deprecation_messages)
+            self.assertEqual([], self.deprecation_messages)
+
+    def assertDeprecationClass(self, cls):
+        self.assertTrue(all(isinstance(item.message, cls)
+                            for item in self.warning_log))
+
+    def assertDeprecationFile(self, filename):
+        for item in self.warning_log:
+            self.assertEqual(item.filename, filename)
 
     def setUp(self):
-        self.tools_warning = pywikibot.tools.warning
-        self.tools_debug = pywikibot.tools.debug
-
-        pywikibot.tools.warning = DeprecationTestCase._record_messages
-        pywikibot.tools.debug = DeprecationTestCase._record_messages
-
         super(DeprecationTestCase, self).setUp()
 
-        DeprecationTestCase._reset_messages()
+        self.warning_log = self.context_manager.__enter__()
+        warnings.simplefilter("always")
+
+        self._reset_messages()
 
     def tearDown(self):
-        pywikibot.tools.warning = self.tools_warning
-        pywikibot.tools.debug = self.tools_warning
+        self.context_manager.__exit__()
 
         super(DeprecationTestCase, self).tearDown()
diff --git a/tests/data_ingestion_tests.py b/tests/data_ingestion_tests.py
index 156343e..909889c 100644
--- a/tests/data_ingestion_tests.py
+++ b/tests/data_ingestion_tests.py
@@ -43,8 +43,7 @@
 
     def test_findDuplicateImages(self):
         """Test finding duplicates on Wikimedia Commons."""
-        duplicates = self.obj.findDuplicateImages(
-            site=self.get_site('commons'))
+        duplicates = self.obj.findDuplicateImages()
         self.assertIn('MP sounds.png', [dup.replace("_", " ") for dup in 
duplicates])
 
     def test_getTitle(self):
diff --git a/tests/deprecation_tests.py b/tests/deprecation_tests.py
index 3f15506..699ffcb 100644
--- a/tests/deprecation_tests.py
+++ b/tests/deprecation_tests.py
@@ -160,7 +160,10 @@
 
     @deprecate_arg('bah', 'foo')
     @deprecate_arg('bah2', 'foo2')
-    def deprecated_instance_method_args(self, foo, foo2):
+    @deprecate_arg('bah3', 'foo3')
+    @deprecate_arg('bah4', 'foo4')
+    def deprecated_instance_method_args(self, foo, foo2, foo3=None, foo4=None):
+        """Method with many decorators to verify wrapping depth formula."""
         self.foo = foo
         self.foo2 = foo2
         return (foo, foo2)
@@ -229,7 +232,7 @@
         self.assertDeprecation(
             __name__ + '.deprecated_func is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = deprecated_func(1)
         self.assertEqual(rv, 1)
@@ -243,7 +246,7 @@
         self.assertDeprecation(
             __name__ + '.deprecated_func2 is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = deprecated_func2(1)
         self.assertEqual(rv, 1)
@@ -263,21 +266,21 @@
         self.assertDeprecation(
             __name__ + '.deprecated_func_bad_args is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = deprecated_func_bad_args('a')
         self.assertEqual(rv, 'a')
         self.assertDeprecation(
             __name__ + '.deprecated_func_bad_args is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = deprecated_func_bad_args(1)
         self.assertEqual(rv, 1)
         self.assertDeprecation(
             __name__ + '.deprecated_func_bad_args is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         f = DeprecatedMethodClass()
         rv = deprecated_func_bad_args(f)
@@ -294,7 +297,7 @@
         self.assertDeprecation(
             __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.instance_method('a')
         self.assertEqual(rv, 'a')
@@ -302,7 +305,7 @@
         self.assertDeprecation(
             __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.instance_method(1)
         self.assertEqual(rv, 1)
@@ -326,14 +329,14 @@
         self.assertDeprecation(
             __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = DeprecatedMethodClass.class_method('a')
         self.assertEqual(rv, 'a')
         self.assertDeprecation(
             __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = DeprecatedMethodClass.class_method(1)
         self.assertEqual(rv, 1)
@@ -352,7 +355,7 @@
         self.assertEqual(rv, 'a')
         self.assertDeprecation(__name__ + 
'.DeprecatedMethodClass.static_method is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = DeprecatedMethodClass.static_method(1)
         self.assertEqual(rv, 1)
@@ -364,7 +367,7 @@
         self.assertEqual(df.__doc__, 'Deprecated class.')
         self.assertDeprecation(__name__ + '.DeprecatedClassNoInit is 
deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         df = DeprecatedClass()
         self.assertEqual(df.foo, None)
@@ -390,7 +393,7 @@
             self.assertEqual(rv, 'b')
             self.assertDeprecation('bah argument of ' + __name__ + '.' + 
func.__name__ + ' is deprecated; use foo instead.')
 
-            DeprecatorTestCase._reset_messages()
+            self._reset_messages()
 
             rv = func(foo=1)
             self.assertEqual(rv, 1)
@@ -402,7 +405,7 @@
                 func,
                 'a', bah='b'
             )
-            DeprecatorTestCase._reset_messages()
+            self._reset_messages()
 
         tests(deprecated_func_arg)
         tests(deprecated_func_arg2)
@@ -414,23 +417,23 @@
 
         rv = deprecated_func_arg3(foo=1, silent=42)
         self.assertEqual(rv, 1)
-        self.assertNoDeprecation()
+        self.assertDeprecationClass(PendingDeprecationWarning)
 
         rv = deprecated_func_arg3(2)
         self.assertEqual(rv, 2)
-        self.assertNoDeprecation()
+        self.assertDeprecationClass(PendingDeprecationWarning)
 
         rv = deprecated_func_arg3(3, loud='3')
         self.assertEqual(rv, 3)
         self.assertDeprecation('loud argument of ' + __name__ + 
'.deprecated_func_arg3 is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = deprecated_func_arg3(4, old='4')
         self.assertEqual(rv, 4)
         self.assertDeprecation('old argument of ' + __name__ + 
'.deprecated_func_arg3 is deprecated.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
     def test_function_remove_last_args(self):
         """Test @remove_last_args on functions."""
@@ -616,7 +619,7 @@
             'bah argument of %s.DeprecatedMethodClass.deprecated_instance_'
             'method_arg is deprecated; use foo instead.' % __name__)
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_arg(foo=1)
         self.assertEqual(rv, 1)
@@ -640,7 +643,7 @@
             'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_args is deprecated; use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args(foo='b', bah2='c')
         self.assertEqual(rv, ('b', 'c'))
@@ -651,7 +654,7 @@
             'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_args is deprecated; use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args(foo2='c', bah='b')
         self.assertEqual(rv, ('b', 'c'))
@@ -662,7 +665,7 @@
             'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_args is deprecated; use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args(foo=1, foo2=2)
         self.assertEqual(rv, (1, 2))
@@ -687,7 +690,7 @@
             'deprecated_instance_method_args_multi is deprecated; '
             'use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args_multi(foo='b', bah2='c')
         self.assertEqual(rv, ('b', 'c'))
@@ -700,7 +703,7 @@
             'deprecated_instance_method_args_multi is deprecated; '
             'use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args_multi(foo2='c', bah='b')
         self.assertEqual(rv, ('b', 'c'))
@@ -713,7 +716,7 @@
             'deprecated_instance_method_args_multi is deprecated; '
             'use foo2 instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_args_multi(foo=1, foo2=2)
         self.assertEqual(rv, (1, 2))
@@ -732,7 +735,7 @@
             'bah argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_and_arg is deprecated; use foo 
instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_and_arg(bah='b')
         self.assertEqual(rv, 'b')
@@ -743,7 +746,7 @@
             'bah argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_and_arg is deprecated; use foo 
instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_and_arg(foo=1)
         self.assertEqual(rv, 1)
@@ -767,7 +770,7 @@
             'bah argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_and_arg2 is deprecated; use foo 
instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_and_arg2(bah='b')
         self.assertEqual(rv, 'b')
@@ -778,7 +781,7 @@
             'bah argument of ' + __name__ + '.DeprecatedMethodClass.'
             'deprecated_instance_method_and_arg2 is deprecated; use foo 
instead.')
 
-        DeprecatorTestCase._reset_messages()
+        self._reset_messages()
 
         rv = f.deprecated_instance_method_and_arg2(foo=1)
         self.assertEqual(rv, 1)
diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py
index 45daa10..5b33602 100644
--- a/tests/dry_site_tests.py
+++ b/tests/dry_site_tests.py
@@ -215,6 +215,10 @@
 
     def test_need_version_fail_with_deprecated(self):
         """Test order of combined version check and deprecation warning."""
+        # As assertRaisesRegex calls the method, unittest is the module
+        # invoking the method instead of this test module.
+        self._do_test_warning_filename = False
+
         # FIXME: The deprecation message should be:
         #   __name__ + '.TestNeedVersion.deprecated_unavailable_method
         # The outermost decorator is the version check, so no deprecation 
message.
@@ -238,7 +242,7 @@
         self.assertDeprecation(
             __name__ + '.TestNeedVersion.deprecated_available_method is 
deprecated.')
 
-        TestNeedVersion._reset_messages()
+        self._reset_messages()
 
         self.deprecated_available_method2()
         self.assertDeprecation(
diff --git a/tests/exceptions_tests.py b/tests/exceptions_tests.py
index f4d03f0..9f3e250 100644
--- a/tests/exceptions_tests.py
+++ b/tests/exceptions_tests.py
@@ -33,7 +33,7 @@
         cls = pywikibot.exceptions.UploadWarning
 
         self.assertDeprecation(
-            'pywikibot.exceptions.UploadWarning is DEPRECATED, '
+            'pywikibot.exceptions.UploadWarning is deprecated, '
             'use pywikibot.data.api.UploadWarning instead.')
 
         self._reset_messages()
diff --git a/tests/family_tests.py b/tests/family_tests.py
index 7258cc8..d32a3c4 100644
--- a/tests/family_tests.py
+++ b/tests/family_tests.py
@@ -102,21 +102,28 @@
         f = pywikibot.site.Family('osm')
         self.assertEqual(f.name, 'osm')
         self.assertDeprecation(
-            'pywikibot.site.Family is DEPRECATED, use 
pywikibot.family.Family.load instead.')
+            'pywikibot.site.Family is deprecated, use 
pywikibot.family.Family.load instead.')
+
+        # @deprecated warning occurs within redirect_func's call
+        # invoking the method instead of this test module.
+        self._do_test_warning_filename = False
 
         f = pywikibot.site.Family('i18n', fatal=False)
         self.assertEqual(f.name, 'i18n')
         self.assertDeprecation(
-            'pywikibot.site.Family is DEPRECATED, use 
pywikibot.family.Family.load instead.')
+            'pywikibot.site.Family is deprecated, use 
pywikibot.family.Family.load instead.')
         self.assertDeprecation('fatal argument of pywikibot.family.Family.load 
is deprecated.')
 
     def test_old_site_family_function_invalid(self):
         """Test that an invalid family raised UnknownFamily exception."""
+        # As assertRaises calls the method, unittest is the module
+        # invoking the method instead of this test module.
+        self._do_test_warning_filename = False
         self.assertRaises(UnknownFamily, pywikibot.site.Family, 'unknown',
                           fatal=False)
         self.assertRaises(UnknownFamily, pywikibot.site.Family, 'unknown')
         self.assertDeprecation(
-            'pywikibot.site.Family is DEPRECATED, use 
pywikibot.family.Family.load instead.')
+            'pywikibot.site.Family is deprecated, use 
pywikibot.family.Family.load instead.')
         self.assertDeprecation('fatal argument of pywikibot.family.Family.load 
is deprecated.')
 
 
diff --git a/tests/page_tests.py b/tests/page_tests.py
index 2aa20b7..f95bd6d 100644
--- a/tests/page_tests.py
+++ b/tests/page_tests.py
@@ -409,7 +409,7 @@
         self.assertIsInstance(mainpage.canBeEdited(), bool)
         self.assertIsInstance(mainpage.botMayEdit(), bool)
         self.assertIsInstance(mainpage.editTime(), pywikibot.Timestamp)
-        self.assertIsInstance(mainpage.previousRevision(), int)
+        self.assertIsInstance(mainpage.previous_revision_id, int)
         self.assertIsInstance(mainpage.permalink(), basestring)
 
     def test_talk_page(self):
diff --git a/tests/pagegenerators_tests.py b/tests/pagegenerators_tests.py
index 25e0875..8a6166f 100755
--- a/tests/pagegenerators_tests.py
+++ b/tests/pagegenerators_tests.py
@@ -23,6 +23,7 @@
 from tests.aspects import (
     unittest,
     TestCase,
+    DeprecationTestCase,
     WikidataTestCase,
     DefaultSiteTestCase,
     WikimediaDefaultSiteTestCase,
@@ -695,7 +696,8 @@
         self.assertNotEqual(pages, pages2)
 
 
-class TestLogeventsFactoryGenerator(DefaultSiteTestCase):
+class TestLogeventsFactoryGenerator(DefaultSiteTestCase,
+                                    DeprecationTestCase):
 
     """Test GeneratorFactory with pagegenerators.LogeventsPageGenerator."""
 
@@ -713,6 +715,8 @@
     def test_logevents_default(self):
         gf = pagegenerators.GeneratorFactory(site=self.site)
         self.assertTrue(gf.handleArg('-newuserslog'))
+        self.assertDeprecation('The usage of "-newuserslog" is deprecated.'
+                               ' Use -logevents "newusers,,500" instead')
         gen = gf.getCombinedGenerator()
         pages = set(gen)
         self.assertLessEqual(len(pages), 500)
diff --git a/tests/site_tests.py b/tests/site_tests.py
index 1a78021..d8daebf 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -21,7 +21,7 @@
 from pywikibot.data import api
 
 from tests.aspects import (
-    unittest, TestCase,
+    unittest, TestCase, DeprecationTestCase,
     DefaultSiteTestCase,
     WikimediaDefaultSiteTestCase,
     WikidataTestCase,
@@ -35,7 +35,7 @@
     unicode = str
 
 
-class TestSiteObjectDeprecatedFunctions(DefaultSiteTestCase):
+class TestSiteObjectDeprecatedFunctions(DefaultSiteTestCase, 
DeprecationTestCase):
 
     """Test cases for Site deprecated methods."""
 
@@ -63,6 +63,8 @@
                 "Action '[a-z]+' is not allowed for user .* on .* wiki.")
         else:
             self.assertEqual(token, mysite.token(mainpage, ttype))
+            self.assertDeprecation("pywikibot.site.APISite.token is deprecated"
+                                   ", use the 'tokens' property instead.")
 
 
 class TestBaseSiteProperties(TestCase):
diff --git a/tox.ini b/tox.ini
index 89f3ee8..e2e1451 100644
--- a/tox.ini
+++ b/tox.ini
@@ -55,6 +55,7 @@
     pywikibot/pagegenerators.py \
     pywikibot/plural.py \
     pywikibot/throttle.py \
+    pywikibot/tools.py \
     pywikibot/userinterfaces/__init__.py \
     pywikibot/userinterfaces/terminal_interface.py \
     pywikibot/weblib.py \

-- 
To view, visit https://gerrit.wikimedia.org/r/187106
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I95000736f3fc9ccb80fe32106fb6516abc62cdd6
Gerrit-PatchSet: 14
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <[email protected]>
Gerrit-Reviewer: John Vandenberg <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: Merlijn van Deen <[email protected]>
Gerrit-Reviewer: XZise <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to