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
