XZise has submitted this change and it was merged.
Change subject: Fix CachedRequest not loading cached requests
......................................................................
Fix CachedRequest not loading cached requests
Use unicode for Request params created a new method _add_defaults.
954825e
It needs to be called before trying to load a cache entry, otherwise
it attempts to load the wrong key.
Change-Id: Ibfdbebd3e903c7d8405bc7591e37aa4167ab0f90
---
M pywikibot/data/api.py
M tests/api_tests.py
2 files changed, 46 insertions(+), 7 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index d3625ee..8fc41ad 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -737,6 +737,7 @@
def _load_cache(self):
"""Return whether the cache can be used."""
+ self._add_defaults()
try:
with open(self._cachefile_path(), 'rb') as f:
uniquedescr, self._data, self._cachetime = pickle.load(f)
diff --git a/tests/api_tests.py b/tests/api_tests.py
index 9f00a2b..9d4de2f 100644
--- a/tests/api_tests.py
+++ b/tests/api_tests.py
@@ -131,22 +131,60 @@
cached = False
- def testResults(self):
+ def test_normal_use(self):
mysite = self.get_site()
mainpage = self.get_mainpage()
- # Run the cached query twice to ensure the
- # data returned is equal
+ # Run the cached query three times to ensure the
+ # data returned is equal, and the last two have
+ # the same cache time.
params = {'action': 'query',
'prop': 'info',
'titles': mainpage.title(),
}
- req = api.CachedRequest(datetime.timedelta(minutes=10),
- site=mysite, **params)
- data = req.submit()
+ req1 = api.CachedRequest(datetime.timedelta(minutes=10),
+ site=mysite, **params)
+ data1 = req1.submit()
req2 = api.CachedRequest(datetime.timedelta(minutes=10),
site=mysite, **params)
data2 = req2.submit()
- self.assertEqual(data, data2)
+ req3 = api.CachedRequest(datetime.timedelta(minutes=10),
+ site=mysite, **params)
+ data3 = req3.submit()
+ self.assertEqual(data1, data2)
+ self.assertEqual(data2, data3)
+ self.assertIsNotNone(req2._cachetime)
+ self.assertIsNotNone(req3._cachetime)
+ self.assertEqual(req2._cachetime, req3._cachetime)
+
+ def test_internals(self):
+ mysite = self.get_site()
+ # Run tests on a missing page unique to this test run so it can
+ # not be cached the first request, but will be cached after.
+ now = datetime.datetime.now()
+ params = {'action': 'query',
+ 'prop': 'info',
+ 'titles': 'TestCachedRequest_test_internals ' + str(now),
+ }
+ req = api.CachedRequest(datetime.timedelta(minutes=10),
+ site=mysite, **params)
+ rv = req._load_cache()
+ self.assertFalse(rv)
+ self.assertIsNone(req._data)
+ self.assertIsNone(req._cachetime)
+
+ data = req.submit()
+
+ self.assertIsNotNone(req._data)
+ self.assertIsNone(req._cachetime)
+
+ rv = req._load_cache()
+
+ self.assertTrue(rv)
+ self.assertIsNotNone(req._data)
+ self.assertIsNotNone(req._cachetime)
+ self.assertGreater(req._cachetime, now)
+ self.assertEqual(req._data, data)
+
if __name__ == '__main__':
try:
--
To view, visit https://gerrit.wikimedia.org/r/166367
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ibfdbebd3e903c7d8405bc7591e37aa4167ab0f90
Gerrit-PatchSet: 2
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [FEAT] Revisit inputChoice
......................................................................
[FEAT] Revisit inputChoice
This introduces input_choice and which is more rebust and more
flexible than the current version.
- Both full answer and hotkey are in the same list (and should have
the same length)
- Automatically add Quit/q if requested and throw the
QuitKeyboardInterrupt when selected
- Verify that both answers and hotkeys are unique (case insensitve)
- Return either the index number of the answer or the hotkey.
Especially when the answers are more generic (like select from the
list) this is more helpful.
- Support multiple character shortcut. The previous system allowed
them but displayed them inproperly.
- Also accept the full answer
Change-Id: Ib8b46da9e687aa3fb950962d9ca7170638e7a144
---
M pywikibot/__init__.py
M pywikibot/bot.py
M pywikibot/userinterfaces/terminal_interface_base.py
3 files changed, 134 insertions(+), 28 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 183a927..6177851 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -29,7 +29,7 @@
from pywikibot import config2 as config
from pywikibot.bot import (
output, warning, error, critical, debug, stdout, exception,
- input, inputChoice, handleArgs, showHelp, ui, log,
+ input, input_choice, inputChoice, handleArgs, showHelp, ui, log,
calledModuleName, Bot, WikidataBot, QuitKeyboardInterrupt,
)
from pywikibot.exceptions import (
@@ -65,7 +65,7 @@
'ItemPage', 'PropertyPage', 'Claim', 'TimeStripper',
'html2unicode', 'url2unicode', 'unicode2html',
'stdout', 'output', 'warning', 'error', 'critical', 'debug',
- 'exception',
+ 'exception', 'input_choice',
'input', 'inputChoice', 'handleArgs', 'showHelp', 'ui', 'log',
'calledModuleName', 'Bot', 'WikidataBot',
'Error', 'InvalidTitle', 'BadTitle', 'NoPage', 'SectionError',
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 8fbf9ad..3321c76 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -33,6 +33,7 @@
import pywikibot
from pywikibot import config
from pywikibot import version
+from pywikibot.tools import deprecated
if sys.version_info[0] > 2:
unicode = str
@@ -507,8 +508,42 @@
return data
+def input_choice(question, answers, default=None, return_shortcut=True,
+ automatic_quit=True):
+ """
+ Ask the user the question and return one of the valid answers.
+
+ @param answers: The valid answers each containing a full length answer and
+ a shortcut. Each value must be unique.
+ @type answers: Iterable containing an iterable of length two
+ @param default: The result if no answer was entered. It must not be in the
+ valid answers and can be disabled by setting it to None. If it should
+ be linked with the valid answers it must be its shortcut.
+ @type default: basestring
+ @param return_shortcut: Whether the shortcut or the index of the answer is
+ returned.
+ @type return_shortcut: bool
+ @param automatic_quit: Adds the option 'Quit' ('q') and throw a
+ L{QuitKeyboardInterrupt} if selected (default).
+ @type automatic_quit: bool
+ @return: The selected answer shortcut or index. Is -1 if the default is
+ selected, it does not return the shortcut and the default is not a
+ valid shortcut.
+ @rtype: int (if not return shortcut), basestring (otherwise)
+ """
+ # make sure logging system has been initialized
+ if not _handlers_initialized:
+ init_handlers()
+
+ return ui.input_choice(question, answers, default, return_shortcut,
+ automatic_quit)
+
+
+@deprecated('input_choice')
def inputChoice(question, answers, hotkeys, default=None):
"""Ask the user a question with several options, return the user's choice.
+
+ DEPRECATED: Use L{input_choice} instead!
The user's input will be case-insensitive, so the hotkeys should be
distinctive case-insensitively.
@@ -529,8 +564,9 @@
if not _handlers_initialized:
init_handlers()
- data = ui.inputChoice(question, answers, hotkeys, default).lower()
- return data
+ return ui.input_choice(question=question, options=zip(answers, hotkeys),
+ default=default, return_shortcut=True,
+ automatic_quit=False)
# Command line parsing and help
diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py
index 89214a5..317cb8e 100755
--- a/pywikibot/userinterfaces/terminal_interface_base.py
+++ b/pywikibot/userinterfaces/terminal_interface_base.py
@@ -13,6 +13,7 @@
import pywikibot
from pywikibot import config
from pywikibot.bot import VERBOSE, INFO, STDOUT, INPUT, WARNING
+from pywikibot.tools import deprecated
transliterator = transliteration.transliterator(config.console_encoding)
@@ -204,34 +205,103 @@
text = text.decode(self.encoding)
return text
+ def input_choice(self, question, options, default=None, return_shortcut=True,
+ automatic_quit=True):
+ """
+ Ask the user and returns a value from the options.
+
+ @param question: The question, without trailing whitespace.
+ @type question: basestring
+ @param options: All available options. Each entry contains the full
+ length answer and a shortcut of only one character. The shortcut
+ must not appear in the answer.
+ @type options: iterable containing iterables of length 2
+ @param default: The default answer if no was entered. None to require
+ an answer.
+ @type default: basestring
+ @param return_shortcut: Whether the shortcut or the index in the option
+ should be returned.
+ @type return_shortcut: bool
+ @param automatic_quit: Adds the option 'Quit' ('q') and throw a
+ L{QuitKeyboardInterrupt} if selected. If it's an integer it
+ doesn't add the option but throw the exception when the option was
+ selected.
+ @type automatic_quit: bool or int
+ @return: If return_shortcut the shortcut of options or the value of
+ default (if it's not None). Otherwise the index of the answer in
+ options. If default is not a shortcut, it'll return -1.
+ @rtype: int (if not return_shortcut), lowercased basestring (otherwise)
+ """
+ if len(options) == 0:
+ raise ValueError("No options are given.")
+ options = list(options)
+ if automatic_quit is True:
+ options += [('Quit', 'q')]
+ quit_index = len(options) - 1
+ elif automatic_quit is not False:
+ quit_index = automatic_quit
+ else:
+ quit_index = None
+ if default:
+ default = default.lower()
+ valid = {}
+ default_index = -1
+ formatted_options = []
+ for i, option in enumerate(options):
+ if len(option) != 2:
+ raise ValueError('Option #{0} does not consist of an option '
+ 'and shortcut.'.format(i))
+ option, shortcut = option
+ if option.lower() in valid:
+ raise ValueError(
+ 'Multiple identical options ({0}).'.format(option))
+ shortcut = shortcut.lower()
+ if shortcut in valid:
+ raise ValueError(
+ 'Multiple identical shortcuts ({0}).'.format(shortcut))
+ valid[option.lower()] = i
+ valid[shortcut] = i
+ index = option.lower().find(shortcut)
+ if shortcut == default:
+ default_index = i
+ shortcut = shortcut.upper()
+ if index >= 0:
+ option = '{0}[{1}]{2}'.format(option[:index], shortcut,
+ option[index + len(shortcut):])
+ else:
+ option = '{0} [{1}]'.format(option, shortcut)
+ formatted_options += [option]
+ question = '{0} ({1})'.format(question, ', '.join(formatted_options))
+ answer = None
+ while answer is None:
+ answer = self.input(question)
+ if default and not answer: # nothing entered
+ answer = default_index
+ else:
+ answer = valid.get(answer.lower(), None)
+ if quit_index == answer:
+ raise pywikibot.QuitKeyboardInterrupt()
+ elif not return_shortcut:
+ return answer
+ elif answer < 0:
+ return default
+ else:
+ return options[answer][1].lower()
+
+ @deprecated('input_choice')
def inputChoice(self, question, options, hotkeys, default=None):
"""
Ask the user a question with a predefined list of acceptable answers.
+
+ DEPRECATED: Use L{input_choice} instead!
+
+ Directly calls L{input_choice} with the options and hotkeys zipped
+ into a tuple list. It always returns the hotkeys and throws no
+ L{QuitKeyboardInterrupt} if quit was selected.
"""
- options = options[:] # we don't want to edit the passed parameter
- for i in range(len(options)):
- option = options[i]
- hotkey = hotkeys[i]
- # try to mark a part of the option name as the hotkey
- m = re.search('[%s%s]' % (hotkey.lower(), hotkey.upper()), option)
- if hotkey == default:
- caseHotkey = hotkey.upper()
- else:
- caseHotkey = hotkey
- if m:
- pos = m.start()
- options[i] = '%s[%s]%s' % (option[:pos], caseHotkey,
- option[pos + 1:])
- else:
- options[i] = '%s [%s]' % (option, caseHotkey)
- # loop until the user entered a valid choice
- while True:
- prompt = '%s (%s)' % (question, ', '.join(options))
- answer = self.input(prompt)
- if default and answer == '': # empty string entered
- return default
- elif answer.lower() in hotkeys or answer.upper() in hotkeys:
- return answer
+ return self.input_choice(question=question, options=zip(options, hotkeys),
+ default=default, return_shortcut=True,
+ automatic_quit=False)
def editText(self, text, jumpIndex=None, highlight=None):
"""Return the text as edited by the user.
--
To view, visit https://gerrit.wikimedia.org/r/156057
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib8b46da9e687aa3fb950962d9ca7170638e7a144
Gerrit-PatchSet: 5
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
XZise has submitted this change and it was merged.
Change subject: Replace sys.exit() with UnknownFamily exception
......................................................................
Replace sys.exit() with UnknownFamily exception
The Family loader has a default enabled 'fatal' parameter which
invokes sys.exit() if the family name is not recognised.
Replace this will a new exception UnknownFamily which is a subclass
of new exception SiteDefinitionError (alias NoSuchSite).
Change-Id: I27727a86207c8e6639945c64871393bc254a11c2
---
M pywikibot/__init__.py
M pywikibot/exceptions.py
M pywikibot/family.py
M pywikibot/page.py
M pywikibot/site.py
M scripts/featured.py
M scripts/interwiki.py
M scripts/login.py
M tests/aspects.py
M tests/dry_site_tests.py
M tests/family_tests.py
M tox.ini
12 files changed, 112 insertions(+), 47 deletions(-)
Approvals:
John Vandenberg: Looks good to me, but someone else must approve
XZise: Looks good to me, approved
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 183a927..aeb7123 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -34,7 +34,8 @@
)
from pywikibot.exceptions import (
Error, InvalidTitle, BadTitle, NoPage, SectionError,
- NoSuchSite, NoUsername, UserBlocked,
+ SiteDefinitionError, NoSuchSite, UnknownSite, UnknownFamily,
+ NoUsername, UserBlocked,
PageRelatedError, IsRedirectPage, IsNotRedirectPage,
PageSaveRelatedError, PageNotSaved, OtherPageSaveError,
LockedPage, CascadeLockedPage, LockedNoPage,
@@ -69,7 +70,8 @@
'input', 'inputChoice', 'handleArgs', 'showHelp', 'ui', 'log',
'calledModuleName', 'Bot', 'WikidataBot',
'Error', 'InvalidTitle', 'BadTitle', 'NoPage', 'SectionError',
- 'NoSuchSite', 'NoUsername', 'UserBlocked',
+ 'SiteDefinitionError', 'NoSuchSite', 'UnknownSite', 'UnknownFamily',
+ 'NoUsername', 'UserBlocked',
'PageRelatedError', 'IsRedirectPage', 'IsNotRedirectPage',
'PageSaveRelatedError', 'PageNotSaved', 'OtherPageSaveError',
'LockedPage', 'CascadeLockedPage', 'LockedNoPage',
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index f99c0b6..ad0c566 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -7,12 +7,15 @@
- UserBlockedY: our username or IP has been blocked
- AutoblockUser: requested action on a virtual autoblock user not valid
- UserActionRefuse
- - NoSuchSite: Site does not exist
- BadTitle: Server responded with BadTitle
- InvalidTitle: Invalid page title
- PageNotFound: Page not found in list
- CaptchaError: Captcha is asked and config.solve_captcha == False
- Server504Error: Server timed out with HTTP 504 code
+
+SiteDefinitionError: Site loading problem
+ - UnknownSite: Site does not exist in Family
+ - UnknownFamily: Family is not registered
PageRelatedError: any exception which is caused by an operation on a Page.
- NoPage: Page does not exist
@@ -155,13 +158,33 @@
pass
-class NoSuchSite(Error):
+class SiteDefinitionError(Error):
"""Site does not exist"""
pass
+# The name 'NoSuchSite' was used for all site related issues,
+# and it used message "Site does not exist".
+# These are retain for backwards compatibility with scripts.
+NoSuchSite = SiteDefinitionError
+
+
+class UnknownSite(SiteDefinitionError):
+
+ """Site does not exist in Family"""
+
+ pass
+
+
+class UnknownFamily(SiteDefinitionError):
+
+ """Family is not registered"""
+
+ pass
+
+
class IsRedirectPage(PageRelatedError):
"""Page is a redirect page"""
diff --git a/pywikibot/family.py b/pywikibot/family.py
index 253f384..2beb963 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -21,8 +21,8 @@
import pywikibot
from pywikibot import config2 as config
-from pywikibot.tools import deprecated
-from pywikibot.exceptions import Error
+from pywikibot.tools import deprecated, deprecate_arg
+from pywikibot.exceptions import UnknownFamily, Error
logger = logging.getLogger("pywiki.wiki.family")
@@ -847,16 +847,14 @@
_families = {}
@staticmethod
- def load(fam=None, fatal=True):
+ @deprecate_arg('fatal', None)
+ def load(fam=None):
"""Import the named family.
@param fam: family name (if omitted, uses the configured default)
@type fam: str
- @param fatal: if True, the bot will stop running if the given family is
- unknown. If False, it will only raise a ValueError exception.
- @param fatal: bool
@return: a Family instance configured for the named family.
-
+ @raises UnknownFamily: family not known
"""
if fam is None:
fam = config.family
@@ -878,14 +876,7 @@
warnings.simplefilter("ignore", RuntimeWarning)
myfamily = imp.load_source(fam, config.family_files[fam])
except (ImportError, KeyError):
- if fatal:
- pywikibot.error(u"""\
- Error importing the %s family. This probably means the family
- does not exist. Also check your configuration file."""
- % fam, exc_info=True)
- sys.exit(1)
- else:
- raise Error("Family %s does not exist" % fam)
+ raise UnknownFamily("Family %s does not exist" % fam)
Family._families[fam] = myfamily.Family()
return Family._families[fam]
@@ -1146,7 +1137,7 @@
If other is not a Family() object, try to create one.
"""
if not isinstance(other, Family):
- other = self.load(other, fatal=False)
+ other = self.load(other)
try:
return self.name == other.name
except AttributeError:
@@ -1155,7 +1146,7 @@
def __ne__(self, other):
try:
return not self.__eq__(other)
- except Error:
+ except UnknownFamily:
return False
def __hash__(self):
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 6bf04b0..6b1c10c 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -44,7 +44,10 @@
from pywikibot import config
from pywikibot.family import Family
from pywikibot.site import Namespace
-from pywikibot.exceptions import AutoblockUser, UserActionRefuse, NoSuchSite
+from pywikibot.exceptions import (
+ AutoblockUser, UserActionRefuse,
+ SiteDefinitionError
+)
from pywikibot.tools import ComparableMixin, deprecated, deprecate_arg
from pywikibot import textlib
@@ -4059,8 +4062,8 @@
newsite = self._site.interwiki(prefix)
except KeyError:
break # text before : doesn't match any known prefix
- except NoSuchSite:
- raise pywikibot.Error(
+ except SiteDefinitionError:
+ raise SiteDefinitionError(
u'{0} is not a local page on {1}, and the interwiki prefix '
'{2} is not supported by PyWikiBot!'.format(
self._text, self._site, prefix))
diff --git a/pywikibot/site.py b/pywikibot/site.py
index ac78b07..c20a46e 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -46,7 +46,8 @@
CascadeLockedPage,
LockedNoPage,
NoPage,
- NoSuchSite,
+ UnknownSite,
+ SiteDefinitionError,
NoUsername,
SpamfilterError,
UserBlocked,
@@ -408,7 +409,7 @@
"""
self.__code = code.lower()
if isinstance(fam, basestring) or fam is None:
- self.__family = pywikibot.family.Family.load(fam, fatal=False)
+ self.__family = pywikibot.family.Family.load(fam)
else:
self.__family = fam
@@ -429,8 +430,8 @@
and oldcode == pywikibot.config.mylang:
pywikibot.config.mylang = self.__code
else:
- raise NoSuchSite("Language %s does not exist in family %s"
- % (self.__code, self.__family.name))
+ raise UnknownSite("Language %s does not exist in family %s"
+ % (self.__code, self.__family.name))
self.nocapitalize = self.code in self.family.nocapitalize
if not self.nocapitalize:
@@ -549,8 +550,8 @@
"""
Return the site for a corresponding interwiki prefix.
- @raise NoSuchSite: if the url given in the interwiki table doesn't
- match any of the existing families.
+ @raise SiteDefinitionError: if the url given in the interwiki table
+ doesn't match any of the existing families.
@raise KeyError: if the prefix is not an interwiki prefix.
"""
# _iw_sites is a local cache to return a APISite instance depending
@@ -575,7 +576,7 @@
if site[0]:
return site[0]
else:
- raise NoSuchSite(
+ raise SiteDefinitionError(
"No family/site found for prefix '{0}'".format(prefix))
def local_interwiki(self, prefix):
@@ -586,8 +587,8 @@
link. So if that link also contains an interwiki link it does follow
it as long as it's a local link.
- @raise NoSuchSite: if the url given in the interwiki table doesn't
- match any of the existing families.
+ @raise SiteDefinitionError: if the url given in the interwiki table
+ doesn't match any of the existing families.
@raise KeyError: if the prefix is not an interwiki prefix.
"""
# Request if necessary
@@ -888,8 +889,8 @@
def decorator(fn):
def callee(self, *args, **kwargs):
if self.obsolete:
- raise NoSuchSite("Language %s in family %s is obsolete"
- % (self.code, self.family.name))
+ raise UnknownSite("Language %s in family %s is obsolete"
+ % (self.code, self.family.name))
grp = kwargs.pop('as_group', group)
if grp == 'user':
self.login(False)
diff --git a/scripts/featured.py b/scripts/featured.py
index c2ff42b..59a4d4b 100644
--- a/scripts/featured.py
+++ b/scripts/featured.py
@@ -260,7 +260,7 @@
for key in sorted(dp.sitelinks.keys()):
try:
site = self.site.fromDBName(key)
- except pywikibot.NoSuchSite:
+ except pywikibot.SiteDefinitionError:
pywikibot.output('"%s" is not a valid site. Skipping...'
% key)
else:
diff --git a/scripts/interwiki.py b/scripts/interwiki.py
index 56679d1..f415cb7 100755
--- a/scripts/interwiki.py
+++ b/scripts/interwiki.py
@@ -1370,7 +1370,7 @@
self.originPage = page
try:
iw = page.langlinks()
- except pywikibot.NoSuchSite:
+ except pywikibot.UnknownSite:
if not globalvar.quiet:
pywikibot.output(u"NOTE: site %s does not exist."
% page.site)
@@ -1841,7 +1841,7 @@
'from': page})
except KeyError:
pass
- except pywikibot.NoSuchSite:
+ except pywikibot.SiteDefinitionError:
pass
except pywikibot.InvalidTitle:
pass
diff --git a/scripts/login.py b/scripts/login.py
index c4ad6c3..adb7a12 100755
--- a/scripts/login.py
+++ b/scripts/login.py
@@ -58,7 +58,7 @@
import pywikibot
from os.path import join
from pywikibot import config
-from pywikibot.exceptions import NoSuchSite
+from pywikibot.exceptions import SiteDefinitionError
def main(*args):
@@ -109,7 +109,7 @@
pywikibot.output(u"Logged out of %(site)s." % locals())
else:
pywikibot.output(u"Not logged in on %(site)s." % locals())
- except NoSuchSite:
+ except SiteDefinitionError:
pywikibot.output(u'%s.%s is not a valid site, please remove it'
u' from your config' % (lang, familyName))
if __name__ == "__main__":
diff --git a/tests/aspects.py b/tests/aspects.py
index 08db133..2815e96 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -666,7 +666,7 @@
deprecation_messages = []
@staticmethod
- def _record_messages(msg):
+ def _record_messages(msg, *args, **kwargs):
DeprecationTestCase.deprecation_messages.append(msg)
@staticmethod
diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py
index 524c9cd..0cd724c 100644
--- a/tests/dry_site_tests.py
+++ b/tests/dry_site_tests.py
@@ -12,6 +12,7 @@
from pywikibot.tools import deprecated
from pywikibot.site import must_be, need_version
from pywikibot.comms.http import user_agent
+from pywikibot.exceptions import UnknownSite
from tests.utils import DummySiteinfo
from tests.aspects import unittest, TestCase, DeprecationTestCase
@@ -171,7 +172,7 @@
self.obsolete = True
args = (1, 2, 'a', 'b')
kwargs = {'i': 'j', 'k': 'l'}
- self.assertRaises(pywikibot.NoSuchSite, self.call_this_user_req_function, args, kwargs)
+ self.assertRaises(UnknownSite, self.call_this_user_req_function, args, kwargs)
class TestNeedVersion(DeprecationTestCase):
diff --git a/tests/family_tests.py b/tests/family_tests.py
index be281aa..b8a8c32 100644
--- a/tests/family_tests.py
+++ b/tests/family_tests.py
@@ -8,11 +8,13 @@
__version__ = '$Id$'
from pywikibot.family import Family
-from pywikibot.exceptions import Error
+from pywikibot.exceptions import UnknownFamily
+import pywikibot.site
from tests.aspects import (
unittest,
TestCase,
+ DeprecationTestCase,
)
@@ -21,6 +23,15 @@
"""Test cases for Family methods."""
net = False
+
+ def test_family_load_valid(self):
+ """Test that a family can be loaded via Family.load."""
+ f = Family.load('anarchopedia')
+ self.assertEqual(f.name, 'anarchopedia')
+
+ def test_family_load_invalid(self):
+ """Test that an invalid family raised UnknownFamily exception."""
+ self.assertRaises(UnknownFamily, Family.load, 'unknown')
def test_eq_different_families_by_name(self):
"""Test that two Family with same name are equal."""
@@ -42,23 +53,55 @@
def test_eq_family_with_string_repr_same_family(self):
"""Test that Family and string with same name are equal."""
- family = Family.load('wikipedia', fatal=False)
+ family = Family.load('wikipedia')
other = 'wikipedia'
self.assertEqual(family, other)
self.assertFalse(family != other)
def test_ne_family_with_string_repr_different_family(self):
"""Test that Family and string with different name are not equal."""
- family = Family.load('wikipedia', fatal=False)
+ family = Family.load('wikipedia')
other = 'wikisource'
self.assertNotEqual(family, other)
self.assertFalse(family == other)
def test_eq_family_with_string_repr_not_existing_family(self):
"""Test that Family and string with different name are not equal."""
- family = Family.load('wikipedia', fatal=False)
+ family = Family.load('wikipedia')
other = 'unknown'
- self.assertRaises(Error, family.__eq__, other)
+ self.assertRaises(UnknownFamily, family.__eq__, other)
+
+
+class TestOldFamilyMethod(DeprecationTestCase):
+
+ """Test cases for old site.Family method."""
+
+ net = False
+
+ def test_old_site_family_function(self):
+ """Test deprecated Family function with valid families."""
+ f = pywikibot.site.Family('species')
+ self.assertEqual(f.name, 'species')
+ f = pywikibot.site.Family('osm')
+ self.assertEqual(f.name, 'osm')
+ self.assertDeprecation(
+ 'pywikibot.site.Family is DEPRECATED, use pywikibot.family.Family.load instead.')
+
+ 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.')
+ 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."""
+ 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.')
+ self.assertDeprecation('fatal argument of pywikibot.family.Family.load is deprecated.')
+
if __name__ == '__main__':
try:
diff --git a/tox.ini b/tox.ini
index ebce604..42d799a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -62,6 +62,7 @@
./tests/api_tests.py \
./tests/dry_api_tests.py \
./tests/dry_site_tests.py \
+ ./tests/family_tests.py \
./scripts/maintenance/cache.py \
./tests/upload_tests.py
--
To view, visit https://gerrit.wikimedia.org/r/165713
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I27727a86207c8e6639945c64871393bc254a11c2
Gerrit-PatchSet: 5
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
XZise has submitted this change and it was merged.
Change subject: Cleanup script tests
......................................................................
Cleanup script tests
- Do not wait 5 seconds if the expected result appears sooner
in the first few lines.
- Perform more asserts on script stderr, stdout and returncode,
and update the list of expected failures for bugs needing fixes.
- Print script stderr if there isnt any expected script output.
- Change test name and description from test_<name>_execution and
'Test running <name>' to test_<name>_help and
'Test running <name> -help', and *no_args tests to *simulate.
- Fail on deprecation warnings
Change-Id: I9ea77cd73ba0d4fa61531fe4b4e1a6e27d2cf178
---
M tests/script_tests.py
1 file changed, 111 insertions(+), 32 deletions(-)
Approvals:
XZise: Looks good to me, approved
diff --git a/tests/script_tests.py b/tests/script_tests.py
index fadc699..5763c37 100644
--- a/tests/script_tests.py
+++ b/tests/script_tests.py
@@ -5,6 +5,7 @@
#
# Distributed under the terms of the MIT license.
#
+from __future__ import print_function
__version__ = '$Id$'
import os
@@ -156,14 +157,27 @@
enable_autorun_tests = (
os.environ.get('PYWIKIBOT2_TEST_AUTORUN', '0') == '1')
- tests = (['test__login_execution'] +
- ['test_' + name + '_execution'
+ if deadlock_script_list:
+ print('Skipping deadlock scripts:\n %s'
+ % ', '.join(deadlock_script_list))
+
+ if unrunnable_script_list:
+ print('Skipping execution of unrunnable scripts:\n %r'
+ % unrunnable_script_list)
+
+ if not enable_autorun_tests:
+ print('Skipping execution of auto-run scripts '
+ '(set PYWIKIBOT2_TEST_AUTORUN=1 to enable):\n %r'
+ % auto_run_script_list)
+
+ tests = (['test__login_help'] +
+ ['test_' + name + '_help'
for name in sorted(script_list)
if name != 'login'
and name not in deadlock_script_list] +
- ['test__login_no_args'])
+ ['test__login_simulate'])
- tests += ['test_' + name + '_no_args'
+ tests += ['test_' + name + '_simulate'
for name in sorted(script_list)
if name != 'login'
and name not in deadlock_script_list
@@ -186,7 +200,7 @@
return collector(loader)
-def execute(command, data_in=None, timeout=0):
+def execute(command, data_in=None, timeout=0, error=None):
"""Execute a command and capture outputs."""
def decode(stream):
if sys.version_info[0] > 2:
@@ -201,21 +215,38 @@
options['stdin'] = subprocess.PIPE
p = subprocess.Popen(command, **options)
+
if data_in is not None:
if sys.version_info[0] > 2:
data_in = data_in.encode(config.console_encoding)
p.stdin.write(data_in)
p.stdin.flush() # _communicate() otherwise has a broken pipe
+
+ stderr_lines = b''
waited = 0
- while waited < timeout and p.poll() is None:
+ while (error or (waited < timeout)) and p.poll() is None:
+ # In order to kill 'shell' and others early, read only a single
+ # line per second, and kill the process as soon as the expected
+ # output has been seen.
+ # Additional lines will be collected later with p.communicate()
+ if error:
+ line = p.stderr.readline()
+ stderr_lines += line
+ if error in decode(line):
+ break
time.sleep(1)
waited += 1
- if timeout and p.poll() is None:
+
+ if (timeout or error) and p.poll() is None:
p.kill()
+
+ if p.poll() is not None:
+ stderr_lines += p.stderr.read()
+
data_out = p.communicate()
return {'exit_code': p.returncode,
'stdout': decode(data_out[0]),
- 'stderr': decode(data_out[1])}
+ 'stderr': decode(stderr_lines + data_out[1])}
class TestScriptMeta(MetaTestCaseClass):
@@ -224,7 +255,7 @@
def __new__(cls, name, bases, dct):
"""Create the new class."""
- def test_execution(script_name, args=None, expected_results=None):
+ def test_execution(script_name, args=[], expected_results=None):
def testScript(self):
cmd = [sys.executable, pwb_path, script_name]
@@ -234,27 +265,71 @@
data_in = script_input.get(script_name)
timeout = 0
- if script_name in auto_run_script_list:
+ if '-help' not in args and script_name in auto_run_script_list:
timeout = 5
- result = execute(cmd, data_in, timeout=timeout)
if expected_results and script_name in expected_results:
- if expected_results[script_name] is not None:
- self.assertIn(expected_results[script_name],
- result['stderr'])
- elif (args and '-help' in args) or \
+ error = expected_results[script_name]
+ else:
+ error = None
+
+ result = execute(cmd, data_in, timeout=timeout, error=error)
+
+ stderr = result['stderr'].split('\n')
+ stderr_sleep = [l for l in stderr
+ if l.startswith('Sleeping for ')]
+ stderr_other = [l for l in stderr
+ if not l.startswith('Sleeping for ')]
+ if stderr_sleep:
+ print(u'\n'.join(stderr_sleep))
+
+ if result['exit_code'] == -9:
+ print(' killed', end=' ')
+
+ if '-help' in args or error or \
script_name not in auto_run_script_list:
- stderr = [l for l in result['stderr'].split('\n')
- if not l.startswith('Sleeping for ')]
- pywikibot.output('\n'.join(
- [l for l in result['stderr'].split('\n')
- if l.startswith('Sleeping for ')]))
- self.assertEqual('\n'.join(stderr), '')
- self.assertIn('Global arguments available for all',
- result['stdout'])
- self.assertEqual(result['exit_code'], 0)
+
+ if error:
+ self.assertIn(error, result['stderr'])
+
+ self.assertIn(result['exit_code'], [0, -9])
+ else:
+ if stderr_other == ['']:
+ stderr_other = None
+ self.assertIsNone(stderr_other)
+ self.assertIn('Global arguments available for all',
+ result['stdout'])
+
+ self.assertEqual(result['exit_code'], 0)
+ else:
+ # auto-run
+ self.assertIn(result['exit_code'], [0, -9])
+
+ if (not result['stdout'] and not result['stderr']):
+ print(' auto-run script unresponsive after %d seconds'
+ % timeout, end=' ')
+ elif 'SIMULATION: edit action blocked' in result['stderr']:
+ print(' auto-run script simulated edit blocked',
+ end=' ')
+ else:
+ print(' auto-run script stderr within %d seconds: %r'
+ % (timeout, result['stderr']), end=' ')
+
self.assertNotIn('Traceback (most recent call last)',
result['stderr'])
+ self.assertNotIn('deprecated', result['stderr'].lower())
+
+ # If stdout doesnt include global help..
+ if 'Global arguments available for all' not in result['stdout']:
+ # Specifically look for deprecated
+ self.assertNotIn('deprecated', result['stdout'].lower())
+ # But also complain if there is any stdout
+ if result['stdout'] == '':
+ result['stdout'] = None
+ self.assertIsNone(result['stdout'])
+
+ sys.stdout.flush()
+
return testScript
for script_name in script_list:
@@ -264,9 +339,9 @@
# unrunnable script tests are disabled by default in load_tests()
if script_name == 'login':
- test_name = 'test__' + script_name + '_execution'
+ test_name = 'test__' + script_name + '_help'
else:
- test_name = 'test_' + script_name + '_execution'
+ test_name = 'test_' + script_name + '_help'
dct[test_name] = test_execution(script_name, ['-help'])
if script_name in ['shell', 'version',
'data_ingestion', # bug 68611
@@ -275,7 +350,7 @@
'script_wui', # Failing on travis-ci
]:
dct[test_name] = unittest.expectedFailure(dct[test_name])
- dct[test_name].__doc__ = 'Test running ' + script_name + '.'
+ dct[test_name].__doc__ = 'Test running ' + script_name + ' -help'
dct[test_name].__name__ = test_name
# Ideally all scripts should execute -help without
@@ -289,14 +364,18 @@
dct[test_name].__test__ = False
if script_name == 'login':
- test_name = 'test__' + script_name + '_no_args'
+ test_name = 'test__' + script_name + '_simulate'
else:
- test_name = 'test_' + script_name + '_no_args'
+ test_name = 'test_' + script_name + '_simulate'
dct[test_name] = test_execution(script_name, ['-simulate'],
no_args_expected_results)
- if script_name in ['checkimages', # bug 68613
+ if script_name in ['catall', # stdout user interaction
+ 'checkimages', # bug 68613
'data_ingestion', # bug 68611
'flickrripper', # bug 68606 (and deps)
+ 'lonelypages', # custom return codes
+ 'nowcommons', # deprecation warning
+ 'replicate_wiki', # custom return codes
'script_wui', # Error on any user except DrTrigonBot
'upload', # raises custom ValueError
] or (
@@ -305,10 +384,10 @@
(config.family == 'wikipedia' and config.mylang != 'en' and script_name == 'misspelling')):
dct[test_name] = unittest.expectedFailure(dct[test_name])
dct[test_name].__doc__ = \
- 'Test running ' + script_name + ' without arguments.'
+ 'Test running ' + script_name + ' -simulate.'
dct[test_name].__name__ = test_name
- # Disable test bt default in nosetests
+ # Disable test by default in nosetests
if script_name in unrunnable_script_list + deadlock_script_list:
dct[test_name].__test__ = False
--
To view, visit https://gerrit.wikimedia.org/r/155638
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I9ea77cd73ba0d4fa61531fe4b4e1a6e27d2cf178
Gerrit-PatchSet: 5
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: @deprecated support for any object
......................................................................
@deprecated support for any object
Decorator @deprecated and @deprecate_arg currently assume they are run
on a bound method. This changeset fixes constraints in the previous
implementation.
Also always report which module the deprecated method is in, as the
user may not recognise names like 'BaseSite' if they are using many
packages, and pywikibot might be used indirectly by another package.
To achieve this, a new constraint is that all decorators must
call pywikibot.tools.add_decorated_full_name(), even if they dont
use the full name, so that is available for as other decorators
in the chain.
Output 'deprecated' instead of 'DEPRECATED'.
Change-Id: I2fefef7e62dbd0595b2b771f61e34c569c7b3773
---
M pywikibot/site.py
M pywikibot/tools.py
M tests/deprecation_tests.py
M tests/dry_api_tests.py
M tests/dry_site_tests.py
M tox.ini
6 files changed, 388 insertions(+), 81 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
XZise: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 43b781c..ac78b07 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -29,7 +29,7 @@
import pywikibot.family
from pywikibot.tools import (
itergroup, deprecated, deprecate_arg, UnicodeMixin, ComparableMixin,
- redirect_func,
+ redirect_func, add_decorated_full_name,
)
from pywikibot.tools import MediaWikiVersion as LV
from pywikibot.throttle import Throttle
@@ -900,6 +900,10 @@
return fn(self, *args, **kwargs)
callee.__name__ = fn.__name__
callee.__doc__ = fn.__doc__
+ callee.__module__ = callee.__module__
+ if not hasattr(fn, '__full_name__'):
+ add_decorated_full_name(fn)
+ callee.__full_name__ = fn.__full_name__
return callee
return decorator
@@ -922,6 +926,11 @@
return fn(self, *args, **kwargs)
callee.__name__ = fn.__name__
callee.__doc__ = fn.__doc__
+ callee.__module__ = fn.__module__
+ if not hasattr(fn, '__full_name__'):
+ add_decorated_full_name(fn)
+ callee.__full_name__ = fn.__full_name__
+
return callee
return decorator
diff --git a/pywikibot/tools.py b/pywikibot/tools.py
index 22e2203..dde154a 100644
--- a/pywikibot/tools.py
+++ b/pywikibot/tools.py
@@ -11,6 +11,7 @@
import sys
import threading
import time
+import inspect
import re
from collections import Mapping
from distutils.version import Version
@@ -319,54 +320,207 @@
EMPTY_DEFAULT = EmptyDefault()
+# Decorators
+#
+# Decorator functions without parameters are _invoked_ differently from
+# decorator functions with function syntax. For example, @deprecated causes
+# a different invocation to @deprecated().
-def deprecated(instead=None):
- """Decorator to output a method deprecation warning.
+# The former is invoked with the decorated function as args[0].
+# The latter is invoked with the decorator arguments as *args & **kwargs,
+# and it must return a callable which will be invoked with the decorated
+# function as args[0].
+
+# The follow deprecators may support both syntax, e.g. @deprecated and
+# @deprecated() both work. In order to achieve that, the code inspects
+# args[0] to see if it callable. Therefore, a decorator must not accept
+# only one arg, and that arg be a callable, as it will be detected as
+# a deprecator without any arguments.
+
+
+def add_decorated_full_name(obj):
+ """Extract full object name, including class, and store in __full_name__.
+
+ This must be done on all decorators that are chained together, otherwise
+ the second decorator will have the wrong full name.
+
+ @param obj: A object being decorated
+ @type obj: object
+ """
+ if hasattr(obj, '__full_name__'):
+ return
+ # The current frame is add_decorated_full_name
+ # The next frame is the decorator
+ # The next frame is the object being decorated
+ frame = inspect.currentframe().f_back.f_back
+ class_name = frame.f_code.co_name
+ if class_name and class_name != '<module>':
+ obj.__full_name__ = (obj.__module__ + '.' +
+ class_name + '.' +
+ obj.__name__)
+ else:
+ obj.__full_name__ = (obj.__module__ + '.' +
+ obj.__name__)
+
+
+def add_full_name(obj):
+ """
+ A decorator to add __full_name__ to the function being decorated.
+
+ This should be done for all decorators used in pywikibot, as any
+ decorator that does not add __full_name__ will prevent other
+ decorators in the same chain from being able to obtain it.
+
+ This can be used to monkey-patch decorators in other modules.
+ e.g.
+ <xyz>.foo = add_full_name(<xyz>.foo)
+
+ @param obj: The function to decorate
+ @type obj: callable
+ @return: decorating function
+ @rtype: function
+ """
+ def outer_wrapper(*outer_args, **outer_kwargs):
+ """Outer wrapper.
+
+ The outer wrapper may be the replacement function if the decorated
+ decorator was called without arguments, or the replacement decorator
+ if the decorated decorator was called without arguments.
+
+ @param outer_args: args
+ @type outer_args: list
+ @param outer_kwargs: kwargs
+ @type: outer_kwargs: dict
+ """
+ def inner_wrapper(*args, **kwargs):
+ """Replacement function.
+
+ If the decorator supported arguments, they are in outer_args,
+ and this wrapper is used to process the args which belong to
+ the function that the decorated decorator was decorating.
+
+ @param args: args passed to the decorated function.
+ @param kwargs: kwargs passed to the decorated function.
+ """
+ add_decorated_full_name(args[0])
+ return obj(*outer_args, **outer_kwargs)(*args, **kwargs)
+
+ inner_wrapper.__doc__ = obj.__doc__
+ inner_wrapper.__name__ = obj.__name__
+ inner_wrapper.__module__ = obj.__module__
+
+ # The decorator being decorated may have args, so both
+ # syntax need to be supported.
+ if (len(outer_args) == 1 and len(outer_kwargs) == 0 and
+ callable(outer_args[0])):
+ add_decorated_full_name(outer_args[0])
+ return obj(outer_args[0])
+ else:
+ return inner_wrapper
+
+ return outer_wrapper
+
+
+@add_full_name
+def deprecated(*args, **kwargs):
+ """Decorator to output a deprecation warning.
@param instead: if provided, will be used to specify the replacement
@type instead: string
"""
- def decorator(method):
+ def decorator(obj):
+ """Outer wrapper.
+
+ The outer wrapper is used to create the decorating wrapper.
+
+ @param obj: function being wrapped
+ @type obj: object
+ """
def wrapper(*args, **kwargs):
- funcname = method.__name__
- classname = args[0].__class__.__name__
+ """Replacement function.
+
+ @param args: args passed to the decorated function.
+ @type args: list
+ @param kwargs: kwargs passed to the decorated function.
+ @type kwargs: dict
+ @return: the value returned by the decorated function
+ @rtype: any
+ """
+ name = obj.__full_name__
if instead:
- warning(u"%s.%s is DEPRECATED, use %s instead."
- % (classname, funcname, instead))
+ warning(u"%s is deprecated, use %s instead." % (name, instead))
else:
- warning(u"%s.%s is DEPRECATED." % (classname, funcname))
- return method(*args, **kwargs)
- wrapper.__name__ = method.__name__
+ warning(u"%s is deprecated." % (name))
+ return obj(*args, **kwargs)
+
+ wrapper.__doc__ = obj.__doc__
+ wrapper.__name__ = obj.__name__
+ wrapper.__module__ = obj.__module__
return wrapper
- return decorator
+
+ without_parameters = len(args) == 1 and len(kwargs) == 0 and callable(args[0])
+ if 'instead' in kwargs:
+ instead = kwargs['instead']
+ elif not without_parameters and len(args) == 1:
+ instead = args[0]
+ else:
+ instead = False
+
+ # When called as @deprecated, return a replacement function
+ if without_parameters:
+ return decorator(args[0])
+ # Otherwise return a decorator, which returns a replacement function
+ else:
+ return decorator
def deprecate_arg(old_arg, new_arg):
"""Decorator to declare old_arg deprecated and replace it with new_arg."""
_logger = ""
- def decorator(method):
+ def decorator(obj):
+ """Outer wrapper.
+
+ The outer wrapper is used to create the decorating wrapper.
+
+ @param obj: function being wrapped
+ @type obj: object
+ """
def wrapper(*__args, **__kw):
- meth_name = method.__name__
+ """Replacement function.
+
+ @param __args: args passed to the decorated function
+ @type __args: list
+ @param __kwargs: kwargs passed to the decorated function
+ @type __kwargs: dict
+ @return: the value returned by the decorated function
+ @rtype: any
+ """
+ name = obj.__full_name__
if old_arg in __kw:
if new_arg:
if new_arg in __kw:
warning(
-u"%(new_arg)s argument of %(meth_name)s replaces %(old_arg)s; cannot use both."
+u"%(new_arg)s argument of %(name)s replaces %(old_arg)s; cannot use both."
% locals())
else:
warning(
-u"%(old_arg)s argument of %(meth_name)s is deprecated; use %(new_arg)s instead."
+u"%(old_arg)s argument of %(name)s is deprecated; use %(new_arg)s instead."
% locals())
__kw[new_arg] = __kw[old_arg]
else:
debug(
-u"%(old_arg)s argument of %(meth_name)s is deprecated."
+u"%(old_arg)s argument of %(name)s is deprecated."
% locals(), _logger)
del __kw[old_arg]
- return method(*__args, **__kw)
- wrapper.__doc__ = method.__doc__
- wrapper.__name__ = method.__name__
+ return obj(*__args, **__kw)
+
+ wrapper.__doc__ = obj.__doc__
+ wrapper.__name__ = obj.__name__
+ wrapper.__module__ = obj.__module__
+ if not hasattr(obj, '__full_name__'):
+ add_decorated_full_name(obj)
+ wrapper.__full_name__ = obj.__full_name__
return wrapper
return decorator
diff --git a/tests/deprecation_tests.py b/tests/deprecation_tests.py
index 6880bdd..3f5894a 100644
--- a/tests/deprecation_tests.py
+++ b/tests/deprecation_tests.py
@@ -7,8 +7,69 @@
#
__version__ = '$Id$'
-from pywikibot.tools import deprecated, deprecate_arg
-from tests.aspects import unittest, DeprecationTestCase
+from pywikibot.tools import deprecated, deprecate_arg, add_full_name
+from tests.aspects import unittest, DeprecationTestCase, TestCase
+
+
+@add_full_name
+def noop(foo=None):
+ """Dummy decorator."""
+ def decorator(obj):
+ def wrapper(*args, **kwargs):
+ raise Exception(obj.__full_name__)
+ return obj(*args, **kwargs)
+ return wrapper
+ return decorator
+
+
+@add_full_name
+def noop2():
+ """Dummy decorator."""
+ def decorator(obj):
+ def wrapper(*args, **kwargs):
+ raise Exception(obj.__full_name__)
+ return obj(*args, **kwargs)
+ return wrapper
+ return decorator
+
+
+@noop()
+def decorated_func():
+ """Test dummy decorator."""
+ pass
+
+
+@noop(foo='bar')
+def decorated_func2():
+ """Test dummy decorator."""
+ pass
+
+
+@noop('baz')
+def decorated_func3():
+ """Test dummy decorator."""
+ pass
+
+
+class DecoratorFullNameTestCase(TestCase):
+
+ """Class with methods deprecated."""
+
+ net = False
+
+ def test_add_full_name_decorator(self):
+ self.assertRaisesRegex(
+ Exception,
+ __name__ + '.decorated_func',
+ decorated_func)
+ self.assertRaisesRegex(
+ Exception,
+ __name__ + '.decorated_func2',
+ decorated_func2)
+ self.assertRaisesRegex(
+ Exception,
+ __name__ + '.decorated_func3',
+ decorated_func3)
@deprecated()
@@ -19,6 +80,12 @@
@deprecated
def deprecated_func2(foo=None):
+ """Deprecated function."""
+ return foo
+
+
+@deprecated(instead='baz')
+def deprecated_func_instead(foo=None):
"""Deprecated function."""
return foo
@@ -110,55 +177,75 @@
net = False
- @unittest.expectedFailure
def test_deprecated_function_zero_arg(self):
"""Test @deprecated with functions, with zero arguments."""
rv = deprecated_func()
self.assertEqual(rv, None)
- self.assertDeprecation('deprecated_func is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func is deprecated.')
def test_deprecated_function(self):
"""Test @deprecated with functions."""
rv = deprecated_func('a')
self.assertEqual(rv, 'a')
- self.assertDeprecation('str.deprecated_func is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func(1)
self.assertEqual(rv, 1)
- self.assertDeprecation('int.deprecated_func is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func is deprecated.')
- @unittest.expectedFailure
def test_deprecated_function2(self):
"""Test @deprecated with functions."""
rv = deprecated_func2('a')
self.assertEqual(rv, 'a')
- self.assertDeprecation('str.deprecated_func is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func2 is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func2(1)
self.assertEqual(rv, 1)
- self.assertDeprecation('int.deprecated_func is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func2 is deprecated.')
+
+ def test_deprecated_function_instead(self):
+ """Test @deprecated with functions, using instead."""
+ rv = deprecated_func_instead('a')
+ self.assertEqual(rv, 'a')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func_instead is deprecated, use baz instead.')
def test_deprecated_function_bad_args(self):
- """Test weakness in @deprecated."""
rv = deprecated_func_bad_args(None)
self.assertEqual(rv, None)
- self.assertDeprecation('NoneType.deprecated_func_bad_args is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func_bad_args is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func_bad_args('a')
self.assertEqual(rv, 'a')
- self.assertDeprecation('str.deprecated_func_bad_args is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func_bad_args is deprecated.')
DeprecatorTestCase._reset_messages()
rv = deprecated_func_bad_args(1)
self.assertEqual(rv, 1)
- self.assertDeprecation('int.deprecated_func_bad_args is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.deprecated_func_bad_args is deprecated.')
+
+ DeprecatorTestCase._reset_messages()
+
+ f = DeprecatedMethodClass()
+ rv = deprecated_func_bad_args(f)
+ self.assertEqual(rv, f)
+ self.assertDeprecation(
+ __name__ + '.deprecated_func_bad_args is deprecated.')
def test_deprecated_instance_method(self):
f = DeprecatedMethodClass()
@@ -166,93 +253,93 @@
rv = f.instance_method()
self.assertEqual(rv, None)
self.assertEqual(f.foo, None)
- self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = f.instance_method('a')
self.assertEqual(rv, 'a')
self.assertEqual(f.foo, 'a')
- self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = f.instance_method(1)
self.assertEqual(rv, 1)
self.assertEqual(f.foo, 1)
- self.assertDeprecation('DeprecatedMethodClass.instance_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.instance_method is deprecated.')
- @unittest.expectedFailure
+ #(a)unittest.expectedFailure
def test_deprecated_instance_method2(self):
f = DeprecatedMethodClass()
rv = f.instance_method2()
self.assertEqual(rv, None)
self.assertEqual(f.foo, None)
- self.assertDeprecation('DeprecatedMethodClass.instance_method2 is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.instance_method2 is deprecated.')
def test_deprecated_class_method(self):
"""Test @deprecated with class methods."""
rv = DeprecatedMethodClass.class_method()
self.assertEqual(rv, None)
- self.assertDeprecation('type.class_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.class_method('a')
self.assertEqual(rv, 'a')
- self.assertDeprecation('type.class_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.class_method(1)
self.assertEqual(rv, 1)
- self.assertDeprecation('type.class_method is DEPRECATED.')
+ self.assertDeprecation(
+ __name__ + '.DeprecatedMethodClass.class_method is deprecated.')
- @unittest.expectedFailure
def test_deprecated_static_method_zero_args(self):
"""Test @deprecated with static methods, with zero arguments."""
rv = DeprecatedMethodClass.static_method()
- # raises: IndexError: tuple index out of range
- # because args[0] is used even if args is empty
self.assertEqual(rv, None)
- self.assertDeprecation('DeprecatedMethodClass.static_method is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
def test_deprecated_static_method(self):
"""Test @deprecated with static methods."""
rv = DeprecatedMethodClass.static_method('a')
self.assertEqual(rv, 'a')
- self.assertDeprecation('str.static_method is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
DeprecatorTestCase._reset_messages()
rv = DeprecatedMethodClass.static_method(1)
self.assertEqual(rv, 1)
- self.assertDeprecation('int.static_method is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedMethodClass.static_method is deprecated.')
- @unittest.expectedFailure
def test_deprecate_class_zero_arg(self):
"""Test @deprecated with classes, without arguments."""
df = DeprecatedClassNoInit()
- # raises: IndexError: tuple index out of range
- # because args[0] is used even if args is empty
self.assertEqual(df.__doc__, 'Deprecated class.')
- self.assertDeprecation('DeprecatedClassNoInit is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedClassNoInit is deprecated.')
DeprecatorTestCase._reset_messages()
df = DeprecatedClass()
self.assertEqual(df.foo, None)
- self.assertDeprecation('DeprecatedClass is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedClass is deprecated.')
def test_deprecate_class(self):
"""Test @deprecated with classes."""
df = DeprecatedClass('a')
self.assertEqual(df.foo, 'a')
- self.assertDeprecation('str.DeprecatedClass is DEPRECATED.')
+ self.assertDeprecation(__name__ + '.DeprecatedClass is deprecated.')
- def test_deprecated_function_arg(self):
- """Test @deprecate_arg with function arguments."""
+ def test_deprecate_function_arg(self):
rv = deprecated_func_arg()
self.assertEqual(rv, None)
self.assertNoDeprecation()
@@ -263,7 +350,7 @@
rv = deprecated_func_arg(bah='b')
self.assertEqual(rv, 'b')
- self.assertDeprecation('bah argument of deprecated_func_arg is deprecated; use foo instead.')
+ self.assertDeprecation('bah argument of ' + __name__ + '.deprecated_func_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -293,7 +380,7 @@
self.assertEqual(rv, 'b')
self.assertEqual(f.foo, 'b')
self.assertDeprecation(
- 'bah argument of deprecated_instance_method_arg is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -313,27 +400,27 @@
rv = f.deprecated_instance_method_args(bah='b', bah2='c')
self.assertEqual(rv, ('b', 'c'))
self.assertDeprecation(
- 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.')
self.assertDeprecation(
- 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.')
+ 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
rv = f.deprecated_instance_method_args(foo='b', bah2='c')
self.assertEqual(rv, ('b', 'c'))
self.assertNoDeprecation(
- 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.')
self.assertDeprecation(
- 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.')
+ 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
rv = f.deprecated_instance_method_args(foo2='c', bah='b')
self.assertEqual(rv, ('b', 'c'))
self.assertDeprecation(
- 'bah argument of deprecated_instance_method_args is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo instead.')
self.assertNoDeprecation(
- 'bah2 argument of deprecated_instance_method_args is deprecated; use foo2 instead.')
+ 'bah2 argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_args is deprecated; use foo2 instead.')
DeprecatorTestCase._reset_messages()
@@ -349,9 +436,9 @@
self.assertEqual(rv, 'a')
self.assertEqual(f.foo, 'a')
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.')
self.assertNoDeprecation(
- 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -359,9 +446,9 @@
self.assertEqual(rv, 'b')
self.assertEqual(f.foo, 'b')
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.')
self.assertDeprecation(
- 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -369,21 +456,21 @@
self.assertEqual(rv, 1)
self.assertEqual(f.foo, 1)
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated.')
self.assertNoDeprecation(
- 'bah argument of deprecated_instance_method_and_arg is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg is deprecated; use foo instead.')
def test_deprecated_instance_method_and_arg2(self):
- """Test @deprecate_arg and @deprecated with instance methods."""
+ """Test @deprecated and @deprecate_arg with instance methods."""
f = DeprecatedMethodClass()
rv = f.deprecated_instance_method_and_arg2('a')
self.assertEqual(rv, 'a')
self.assertEqual(f.foo, 'a')
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.')
self.assertNoDeprecation(
- 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -391,9 +478,9 @@
self.assertEqual(rv, 'b')
self.assertEqual(f.foo, 'b')
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.')
self.assertDeprecation(
- 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
DeprecatorTestCase._reset_messages()
@@ -401,9 +488,9 @@
self.assertEqual(rv, 1)
self.assertEqual(f.foo, 1)
self.assertDeprecation(
- 'DeprecatedMethodClass.deprecated_instance_method_and_arg2 is DEPRECATED.')
+ __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated.')
self.assertNoDeprecation(
- 'bah argument of deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
+ 'bah argument of ' + __name__ + '.DeprecatedMethodClass.deprecated_instance_method_and_arg2 is deprecated; use foo instead.')
if __name__ == '__main__':
diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py
index ec26fd9..39645d9 100644
--- a/tests/dry_api_tests.py
+++ b/tests/dry_api_tests.py
@@ -21,13 +21,11 @@
#
import os
-import sys
import datetime
import pywikibot
from pywikibot.data.api import (
Request,
- MIMEMultipart,
CachedRequest,
QueryGenerator,
)
diff --git a/tests/dry_site_tests.py b/tests/dry_site_tests.py
index 56a44dd..524c9cd 100644
--- a/tests/dry_site_tests.py
+++ b/tests/dry_site_tests.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+"""Tests against a fake Site object."""
#
# (C) Pywikibot team, 2012-2014
#
@@ -8,14 +9,18 @@
#
import pywikibot
+from pywikibot.tools import deprecated
from pywikibot.site import must_be, need_version
from pywikibot.comms.http import user_agent
from tests.utils import DummySiteinfo
-from tests.aspects import unittest, TestCase
+from tests.aspects import unittest, TestCase, DeprecationTestCase
class DrySite(pywikibot.site.APISite):
+
+ """Fake APISite object."""
+
_loginstatus = pywikibot.site.LoginStatus.NOT_ATTEMPTED
@property
@@ -28,6 +33,8 @@
class TestDrySite(TestCase):
+
+ """Tests against a fake Site object."""
net = False
@@ -167,7 +174,7 @@
self.assertRaises(pywikibot.NoSuchSite, self.call_this_user_req_function, args, kwargs)
-class TestNeedVersion(TestCase):
+class TestNeedVersion(DeprecationTestCase):
"""Test cases for the need_version decorator."""
@@ -191,11 +198,62 @@
def older(self):
return True
- def testNeedVersion(self):
+ @need_version("1.14")
+ @deprecated
+ def deprecated_unavailable_method(self):
+ return True
+
+ @deprecated
+ @need_version("1.14")
+ def deprecated_unavailable_method2(self):
+ return True
+
+ @need_version("1.12")
+ @deprecated
+ def deprecated_available_method(self):
+ return True
+
+ @deprecated
+ @need_version("1.12")
+ def deprecated_available_method2(self):
+ return True
+
+ def test_need_version(self):
self.assertRaises(NotImplementedError, self.too_new)
self.assertTrue(self.old_enough())
self.assertTrue(self.older())
+ def test_need_version_fail_with_deprecated(self):
+ """Test order of combined version check and deprecation warning."""
+ # The outermost decorator is the version check, so no deprecation message.
+ self.assertRaisesRegex(
+ NotImplementedError,
+ #__name__ + '.TestNeedVersion.deprecated_unavailable_method',
+ 'deprecated_unavailable_method',
+ self.deprecated_unavailable_method)
+ self.assertNoDeprecation()
+
+ # The deprecator is first, but the version check still raises exception.
+ self.assertRaisesRegex(
+ NotImplementedError,
+ #__name__ + '.TestNeedVersion.deprecated_unavailable_method2',
+ 'deprecated_unavailable_method2',
+ self.deprecated_unavailable_method2)
+ self.assertDeprecation(
+ __name__ + '.TestNeedVersion.deprecated_unavailable_method2 is deprecated.')
+
+ def test_need_version_success_with_deprecated(self):
+ """Test order of combined version check and deprecation warning."""
+ self.deprecated_available_method()
+ self.assertDeprecation(
+ __name__ + '.TestNeedVersion.deprecated_available_method is deprecated.')
+
+ TestNeedVersion._reset_messages()
+
+ self.deprecated_available_method2()
+ self.assertDeprecation(
+ __name__ + '.TestNeedVersion.deprecated_available_method2 is deprecated.')
+
if __name__ == '__main__':
unittest.main()
diff --git a/tox.ini b/tox.ini
index aa1aa33..ebce604 100644
--- a/tox.ini
+++ b/tox.ini
@@ -61,6 +61,7 @@
./tests/deprecation_tests.py \
./tests/api_tests.py \
./tests/dry_api_tests.py \
+ ./tests/dry_site_tests.py \
./scripts/maintenance/cache.py \
./tests/upload_tests.py
--
To view, visit https://gerrit.wikimedia.org/r/166291
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I2fefef7e62dbd0595b2b771f61e34c569c7b3773
Gerrit-PatchSet: 4
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [IMPROV] Test static generator directly
......................................................................
[IMPROV] Test static generator directly
With the introduction of the static method Request._build_mime_request
in c7a8dc0199829a303d70a2973ac2eeddacc8ddcc it is possible to test it
directly and not do the same transformations again which could introduce
copy and paste errors.
Change-Id: Id40ab25cdcb31d19ed2d859374a969a33d81c87b
---
M tests/dry_api_tests.py
1 file changed, 4 insertions(+), 15 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py
index d8622cb..778df4c 100644
--- a/tests/dry_api_tests.py
+++ b/tests/dry_api_tests.py
@@ -23,7 +23,6 @@
import os
import sys
import datetime
-from email.mime.multipart import MIMEMultipart
import pywikibot
from pywikibot.data.api import Request, CachedRequest, QueryGenerator
@@ -208,20 +207,10 @@
local_filename = os.path.join(_data_dir, 'MP_sounds.png')
with open(local_filename, 'rb') as f:
file_content = f.read()
- submsg = Request._generate_MIME_part(
- 'file', file_content, ('image', 'png'),
- {'filename': local_filename})
-
- container = MIMEMultipart(_subtype='form-data')
- container.attach(submsg)
- if sys.version_info[0] > 2:
- body = container.as_bytes()
- marker = b"\n\n"
- else:
- body = container.as_string()
- marker = "\n\n"
- eoh = body.find(marker)
- body = body[eoh + len(marker):]
+ body = Request._build_mime_request({}, {
+ 'file': (file_content, ('image', 'png'),
+ {'filename': local_filename})
+ })[1]
self.assertNotEqual(body.find(file_content), -1)
--
To view, visit https://gerrit.wikimedia.org/r/166528
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Id40ab25cdcb31d19ed2d859374a969a33d81c87b
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: jenkins-bot <>