jenkins-bot has submitted this change and it was merged.
Change subject: Dont load site at startup to log site version
......................................................................
Dont load site at startup to log site version
Move the logging of the site version into Bot() to prevent
site access issues occurring before main() or Bot() have
requested a Site() object.
Bot now log the site version of each site that is fetched via
the Bot.site property
Bot.__init__ sets self.generator if it is in kwargs.
Bot.run updates Bot.site if Bot.site was not set prior to run()
being invoked.
Remove the run() method from delete.py, so it uses the new
treat()-only system where Bot.run() manages the site property.
Bug: 69895
Change-Id: If8a71b78b639288c7cf4329b1292c88d889ce1fa
---
M pywikibot/bot.py
M scripts/delete.py
2 files changed, 70 insertions(+), 23 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 99c6b8f..1b5bfec 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -258,10 +258,11 @@
This may help the user to track errors or report bugs.
"""
- # if site not available it's too early to print a header (work-a-round)
- try:
- site = pywikibot.Site()
- except AttributeError:
+ # If a http thread is not available, it's too early to print a header
+ # that includes version information, which may need to query a server.
+ # The http module can't be imported due to circular dependencies.
+ http = sys.modules.get('pywikibot.comms.http', None)
+ if not http or not hasattr(http, 'threads') or not len(http.threads):
return
log(u'=== Pywikibot framework v2.0 -- Logging header ===')
@@ -321,8 +322,6 @@
if config.log_pywiki_repo_version:
log(u'PYWIKI REPO VERSION: %s' % unicode(version.getversion_onlinerepo()))
-
- log(u'SITE VERSION: %s' % site.version())
log(u'=== ' * 14)
@@ -888,7 +887,12 @@
@param kwargs: bot options
@type kwargs: dict
"""
+ if 'generator' in kwargs:
+ self.generator = kwargs.pop('generator')
+
self.setOptions(**kwargs)
+ self._site = None
+ self._sites = set()
def setOptions(self, **kwargs):
"""
@@ -1049,13 +1053,63 @@
raise NotImplementedError('Method %s.treat() not implemented.'
% self.__class__.__name__)
+ @property
+ def site(self):
+ """Site that the bot is using."""
+ if not self._site:
+ warning('Bot.site was not set before being retrieved.')
+ self.site = pywikibot.Site()
+ warning('Using the default site: %s' % self.site)
+ return self._site
+
+ @site.setter
+ def site(self, site):
+ """
+ Set the Site that the bot is using.
+
+ When Bot.run() is managing the generator and site property, this is
+ set each time a page is on a site different from the previous page.
+ """
+ if site not in self._sites:
+ log(u'LOADING SITE %s VERSION: %s'
+ % (site, unicode(site.version())))
+
+ self._sites.add(site)
+ if len(self._sites) == 2:
+ log('%s uses multiple sites' % self.__class__.__name__)
+ if self._site and self._site != site:
+ log('%s: changing site from %s to %s'
+ % (self.__class__.__name__, self._site, site))
+ self._site = site
+
def run(self):
"""Process all pages in generator."""
if not hasattr(self, 'generator'):
raise NotImplementedError('Variable %s.generator not set.'
% self.__class__.__name__)
+
+ # This check is to remove the possibility that the superclass changing
+ # self.site causes bugs in subclasses.
+ # If the subclass has set self.site before run(), it may be that the
+ # bot processes pages on sites other than self.site, and therefore
+ # this method cant alter self.site. To use this functionality, don't
+ # set self.site in __init__, and use page.site in treat().
+ auto_update_site = not self._site
+ if not auto_update_site:
+ warning(
+ '%s.__init__ set the Bot.site property; this is only needed '
+ 'when the Bot accesses many sites.' % self.__class__.__name__)
+ else:
+ log('Bot is managing the %s.site property in run()'
+ % self.__class__.__name__)
+
try:
for page in self.generator:
+ # When in auto update mode, set the site when it changes,
+ # so subclasses can hook onto changes to site.
+ if (auto_update_site and
+ (not self._site or page.site != self.site)):
+ self.site = page.site
self.treat(page)
except QuitKeyboardInterrupt:
pywikibot.output('\nUser quit %s bot run...' %
diff --git a/scripts/delete.py b/scripts/delete.py
index 5307598..fe4f1b6 100644
--- a/scripts/delete.py
+++ b/scripts/delete.py
@@ -59,27 +59,20 @@
self.availableOptions.update({
'undelete': False,
})
- super(DeletionRobot, self).__init__(**kwargs)
+ super(DeletionRobot, self).__init__(generator=generator, **kwargs)
- self.generator = generator
self.summary = summary
- def run(self):
- """
- Run bot.
-
- Loop through everything in the page generator and delete it.
- """
- for page in self.generator:
- self.current_page = page
-
- if self.getOption('undelete'):
- page.undelete(self.summary)
+ def treat(self, page):
+ """Delete one page from the generator."""
+ self.current_page = page
+ if self.getOption('undelete'):
+ page.undelete(self.summary)
+ else:
+ if page.exists():
+ page.delete(self.summary, not self.getOption('always'))
else:
- if page.exists():
- page.delete(self.summary, not self.getOption('always'))
- else:
- pywikibot.output(u'Skipping: %s does not exist.' % page)
+ pywikibot.output(u'Skipping: %s does not exist.' % page)
def main(*args):
--
To view, visit https://gerrit.wikimedia.org/r/166583
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: If8a71b78b639288c7cf4329b1292c88d889ce1fa
Gerrit-PatchSet: 7
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: Nullzero <nullzero.free(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] ParamInfo: Don't modify module set
......................................................................
[FIX] ParamInfo: Don't modify module set
The module set has been modified which led to the effect that when
someone was fetching all entries and then iterating over the entries of
the same set, that all already cached entries were missing from the set.
The HTTP GET patch (c26b9dcb4242add0145a883c6b6973a45a665ed0) therefore
was thinking it could use a GET request, because the entry which
should've shown it that a POST request is required was deleted.
Change-Id: I467ff7a8fe243df0abd7f322ecaa8460a91e4017
---
M pywikibot/data/api.py
1 file changed, 1 insertion(+), 1 deletion(-)
Approvals:
Merlijn van Deen: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 3eb69a1..8aa97b4 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -274,7 +274,7 @@
else:
modules = set(modules)
- modules -= set(self._paraminfo.keys())
+ modules = modules - set(self._paraminfo.keys())
if not modules:
return
--
To view, visit https://gerrit.wikimedia.org/r/173631
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I467ff7a8fe243df0abd7f322ecaa8460a91e4017
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 <>
jenkins-bot has submitted this change and it was merged.
Change subject: Page.latest_revision property
......................................................................
Page.latest_revision property
Also pep8-ified a few revision related properties.
Change-Id: I3e0caa081e9f2e0f06d2d814199531c8af739644
---
M pywikibot/page.py
M pywikibot/pagegenerators.py
2 files changed, 27 insertions(+), 17 deletions(-)
Approvals:
Nullzero: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py
index cc094bf..68bfd5c 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -338,7 +338,7 @@
if not get_redirect:
raise
- return self._revisions[self._revid].text
+ return self.latest_revision.text
def _getInternals(self, sysop):
"""Helper function for get().
@@ -396,11 +396,24 @@
self.title(asUrl=True),
(oldid if oldid is not None else self.latestRevision()))
- def latestRevision(self):
+ @property
+ def latest_revision_id(self):
"""Return the current revision id for this page."""
if not hasattr(self, '_revid'):
self.site.loadrevisions(self)
return self._revid
+
+ def latestRevision(self):
+ """Return the current revision id for this page."""
+ return self.latest_revision_id
+
+ @property
+ def latest_revision(self):
+ """Return the current revision for this page."""
+ rev = self.latest_revision_id
+ if rev not in self._revisions:
+ self.site.loadrevisions(self)
+ return self._revisions[rev]
@property
def text(self):
@@ -491,20 +504,14 @@
@return: unicode
"""
- rev = self.latestRevision()
- if rev not in self._revisions:
- self.site.loadrevisions(self)
- return self._revisions[rev].user
+ return self.latest_revision.user
def isIpEdit(self):
"""Return True if last editor was unregistered.
@return: bool
"""
- rev = self.latestRevision()
- if rev not in self._revisions:
- self.site.loadrevisions(self)
- return self._revisions[rev].anon
+ return self.latest_revision.anon
def lastNonBotUser(self):
"""Return name or IP address of last human/non-bot user to edit page.
@@ -529,17 +536,16 @@
return self._lastNonBotUser
+ @deprecated_args(datetime=None)
def editTime(self):
"""Return timestamp of last revision to page.
- @return: pywikibot.Timestamp
+ @rtype: pywikibot.Timestamp
"""
- rev = self.latestRevision()
- if rev not in self._revisions:
- self.site.loadrevisions(self)
- return self._revisions[rev].timestamp
+ return self.latest_revision.timestamp
- def previousRevision(self):
+ @property
+ def previous_revision_id(self):
"""Return the revision id for the previous revision of this Page.
If the page has only one revision, it shall return -1.
@@ -553,6 +559,9 @@
else:
return min(x.revid for x in history)
+ def previousRevision(self):
+ return self.previous_revision_id
+
def exists(self):
"""Return True if page exists on the wiki, even if it's a redirect.
@@ -563,6 +572,7 @@
"""
return self.site.page_exists(self)
+ @property
def oldest_revision(self):
return self.getVersionHistory(reverseOrder=True, total=1)[0]
diff --git a/pywikibot/pagegenerators.py b/pywikibot/pagegenerators.py
index cc37416..f2e8360 100644
--- a/pywikibot/pagegenerators.py
+++ b/pywikibot/pagegenerators.py
@@ -1149,7 +1149,7 @@
continue
if do_first_edit:
- first_edit = page.oldest_revision().timestamp
+ first_edit = page.oldest_revision.timestamp
if first_edit < first_edit_start:
if show_filtered:
--
To view, visit https://gerrit.wikimedia.org/r/172511
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I3e0caa081e9f2e0f06d2d814199531c8af739644
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: Nullzero <nullzero.free(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [FEAT] Support HTTP GET requests
......................................................................
[FEAT] Support HTTP GET requests
This uses a HTTP GET request if possible instead of an HTTP POST if not
one of the following conditions is applying:
- The action is one of the actions which requires a POST request (e.g.
action=upload). If the action is query, the query modules requires a
POST request.
- The config variable (maximum_GET_length) is lower than the actual
param string. The family can overwrite this maximum length. If it is 0
(it accepts also ints < 0, but those don't make sense), it'll never
use a GET request.
- If after the first request the server returns a 414 it'll switch to
the POST request mode and retry it.
- If the site is not using SSL, but could be configured.
The Request instance itself has an attribute 'use_get' which can
overwrite this behaviour. If it's True the first try will always be a
GET request (even if the action doesn't support it), if it's False it'll
never try it with a GET request and if it's None, it'll check the action
and if requires the querymodule.
The value of 'True' is defined to allow calling action=paraminfo inside
the request, which would itself call action=paraminfo if it didn't know
that this doesn't require a GET request. It also ignores 'meta'
parameters as those are all GETable and because ParamInfo requries
meta=siteinfo and meta=userinfo.
Change-Id: Ib045fecedd9638f4b3bcbc40bc7b37ebfea63c42
---
M pywikibot/comms/http.py
M pywikibot/config2.py
M pywikibot/data/api.py
M pywikibot/exceptions.py
M pywikibot/family.py
5 files changed, 73 insertions(+), 4 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/comms/http.py b/pywikibot/comms/http.py
index 660e597..f3e93c8 100644
--- a/pywikibot/comms/http.py
+++ b/pywikibot/comms/http.py
@@ -66,7 +66,9 @@
from urllib2 import quote
from pywikibot import config
-from pywikibot.exceptions import FatalServerError, Server504Error
+from pywikibot.exceptions import (
+ FatalServerError, Server504Error, Server414Error
+)
from pywikibot.comms import threadedhttp
from pywikibot.tools import deprecate_arg
import pywikibot.version
@@ -260,6 +262,9 @@
if request.data[0].status == 504:
raise Server504Error("Server %s timed out" % site.hostname())
+ if request.data[0].status == 414:
+ raise Server414Error('Too long GET request')
+
# HTTP status 207 is also a success status for Webdav FINDPROP,
# used by the version module.
if request.data[0].status not in (200, 207):
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index e24ebca..0020d5d 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -89,6 +89,14 @@
# number of days to cache namespaces, api configuration, etc.
API_config_expiry = 30
+# The maximum number of bytes which uses a GET request, if not positive
+# it'll always use POST requests
+maximum_GET_length = 255
+# Some networks modify GET requests when they are not encrypted, to avoid
+# bug reports related to that disable those. If we are confident that bug
+# related to this are really because of the network this could be changed.
+enable_GET_without_SSL = False
+
# Solve captchas in the webbrowser. Setting this to False will result in the
# exception CaptchaError being thrown if a captcha is encountered.
solve_captcha = True
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 01d36d9..3eb69a1 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -26,7 +26,9 @@
import pywikibot
from pywikibot import config, login
from pywikibot.tools import MediaWikiVersion as LV, deprecated, itergroup
-from pywikibot.exceptions import Server504Error, FatalServerError, Error
+from pywikibot.exceptions import (
+ Server504Error, Server414Error, FatalServerError, Error
+)
import sys
@@ -297,6 +299,7 @@
params = {
'expiry': config.API_config_expiry,
+ 'use_get': True, # Request need ParamInfo to determine use_get
'site': self.site,
'action': 'paraminfo',
}
@@ -534,6 +537,9 @@
@kwarg retry_wait: (optional) Minimum time to wait after an error,
defaults to 5 seconds (doubles each retry until max of 120 is
reached)
+ @kwarg use_get: (optional) Use HTTP GET request if possible. If False
+ it uses a POST request. If None, it'll try to determine via
+ action=paraminfo if the action requires a POST.
@kwarg format: (optional) Defaults to "json"
"""
try:
@@ -549,6 +555,7 @@
else:
self.mime = kwargs.pop('mime', False)
self.throttle = kwargs.pop('throttle', True)
+ self.use_get = kwargs.pop('use_get', None)
self.max_retries = kwargs.pop("max_retries", pywikibot.config.max_retries)
self.retry_wait = kwargs.pop("retry_wait", pywikibot.config.retry_wait)
self._params = {}
@@ -877,6 +884,27 @@
"""
self._add_defaults()
+ if (not config.enable_GET_without_SSL and
+ self.site.protocol() != 'https'):
+ use_get = False
+ elif self.use_get is None:
+ if self.action == 'query':
+ # for queries check the query module
+ modules = set()
+ for mod_type_name in ('list', 'prop', 'generator'):
+ modules.update(self._params.get(mod_type_name, []))
+ else:
+ modules = set([self.action])
+ if modules:
+ self.site._paraminfo.fetch(modules)
+ use_get = all(['mustbeposted' not in self.site._paraminfo[mod]
+ for mod in modules])
+ else:
+ # If modules is empty, just 'meta' was given, which doesn't
+ # require POSTs, and is required for ParamInfo
+ use_get = True
+ else:
+ use_get = self.use_get
while True:
paramstring = self._http_param_string()
simulate = self._simulate(self.action)
@@ -892,17 +920,35 @@
if self.mime:
(headers, body) = Request._build_mime_request(
self._encoded_items(), self.mime_params)
+ use_get = False # MIME requests require HTTP POST
else:
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
- body = paramstring
+ if (not self.site.maximum_GET_length() or
+ self.site.maximum_GET_length() < len(paramstring)):
+ use_get = False
+ if use_get:
+ uri = '{0}?{1}'.format(uri, paramstring)
+ body = None # default in httplib2
+ else:
+ body = paramstring
rawdata = http.request(
- self.site, uri, method="POST",
+ self.site, uri, method='GET' if use_get else 'POST',
headers=headers, body=body)
except Server504Error:
pywikibot.log(u"Caught HTTP 504 error; retrying")
self.wait()
continue
+ except Server414Error:
+ if use_get:
+ pywikibot.log('Caught HTTP 414 error; retrying')
+ use_get = False
+ self.wait()
+ continue
+ else:
+ pywikibot.warning('Caught HTTP 414 error, although not '
+ 'using GET.')
+ raise
except FatalServerError:
# This error is not going to be fixed by just waiting
pywikibot.error(traceback.format_exc())
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index 298ef6c..b5013df 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -338,6 +338,13 @@
pass
+class Server414Error(Error):
+
+ """Server returned with HTTP 414 code."""
+
+ pass
+
+
class BadTitle(Error):
"""Server responded with BadTitle."""
diff --git a/pywikibot/family.py b/pywikibot/family.py
index f2f13e3..9daf228 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -1086,6 +1086,9 @@
else:
return code
+ def maximum_GET_length(self, code):
+ return config.maximum_GET_length
+
def dbName(self, code):
# returns the name of the MySQL database
return '%s%s' % (code, self.name)
--
To view, visit https://gerrit.wikimedia.org/r/173055
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib045fecedd9638f4b3bcbc40bc7b37ebfea63c42
Gerrit-PatchSet: 6
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: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: ParamInfo class
......................................................................
ParamInfo class
Manages data from the paraminfo which was previous managed within
QueryGenerator and stored in APISite._modules.
This provides preloading of an initial set of params, so the cache
key is constant, and bulk loading of all query params, again so that
the cache key is constant irrespective of order of queries used.
Change-Id: I84a0769f19abf8740106659426109c62e8438e65
---
M pywikibot/data/api.py
M pywikibot/site.py
M tests/api_tests.py
M tests/dry_api_tests.py
4 files changed, 682 insertions(+), 76 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 83ef419..01d36d9 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -7,7 +7,7 @@
#
__version__ = '$Id$'
-from collections import MutableMapping
+from collections import Container, MutableMapping
from pywikibot.comms import http
from email.mime.nonmultipart import MIMENonMultipart
import datetime
@@ -25,7 +25,7 @@
import pywikibot
from pywikibot import config, login
-from pywikibot.tools import MediaWikiVersion as LV, deprecated
+from pywikibot.tools import MediaWikiVersion as LV, deprecated, itergroup
from pywikibot.exceptions import Server504Error, FatalServerError, Error
import sys
@@ -121,6 +121,321 @@
self.mediawiki_exception_class_name = mediawiki_exception_class_name
code = 'internal_api_error_' + mediawiki_exception_class_name
super(APIMWException, self).__init__(code, info, **kwargs)
+
+
+class ParamInfo(Container):
+
+ """
+ API parameter information data object.
+
+ Provides cache aware fetching of parameter information.
+
+ TODO: establish a data structure in the class which prefills
+ the param information available for a site given its
+ version, using the API information available for each
+ API version.
+
+ TODO: module aliases: in 1.25wmf
+ list=deletedrevs becomes list=alldeletedrevisions
+ prop=deletedrevs becomes prop=deletedrevisions
+
+ TODO: share API parameter information between sites using
+ similar versions of the API, especially all sites in the
+ same family.
+ """
+
+ paraminfo_keys = frozenset(['modules', 'querymodules', 'formatmodules',
+ 'mainmodule', 'pagesetmodule'])
+
+ root_modules = frozenset(['main', 'pageset'])
+ root_module_keys = frozenset(['mainmodule', 'pagesetmodule'])
+
+ init_modules = frozenset(['main', 'paraminfo'])
+
+ def __init__(self, site, preloaded_modules=None, modules_only_mode=None):
+ """
+ Constructor.
+
+ @param preloaded_modules: API modules to preload
+ @type preloaded_modules: set of string
+ @param modules_only_mode: use the 'modules' only syntax for API request
+ @type: modules_only_mode: bool or None to only use default, which True
+ if the site is 1.25wm4+
+ """
+ self.site = site
+
+ # Keys are module names, values are the raw responses from the server.
+ self._paraminfo = {}
+
+ # Cached data.
+ self._prefixes = {}
+ self._with_limits = None
+
+ self._action_modules = None
+ self._query_modules = [] # filled in _init()
+ self._limit = None
+
+ self.preloaded_modules = self.init_modules
+ if preloaded_modules:
+ self.preloaded_modules |= set(preloaded_modules)
+ self.__inited = False
+
+ self.modules_only_mode = modules_only_mode
+ if self.modules_only_mode:
+ self.paraminfo_keys = frozenset(['modules'])
+
+ def _init(self):
+ # The paraminfo api deprecated the old request syntax of
+ # querymodules='info'; to avoid warnings sites with 1.25wmf4+
+ # must only use 'modules' parameter.
+ if self.modules_only_mode is None:
+ self.modules_only_mode = LV(self.site.version()) >= LV('1.25wmf4')
+ if self.modules_only_mode:
+ self.paraminfo_keys = frozenset(['modules'])
+ # Assume that by v1.26, it will be desirable to prefetch 'query'
+ if LV(self.site.version()) > LV('1.26'):
+ self.preloaded_modules |= set(['query'])
+
+ self.fetch(self.preloaded_modules, _init=True)
+ main_modules_param = self.parameter('main', 'action')
+
+ assert(main_modules_param)
+ assert('type' in main_modules_param)
+ self._action_modules = frozenset(main_modules_param['type'])
+
+ # While deprecated with warning in 1.25, paraminfo param 'querymodules'
+ # provides a list of all query modules. This will likely be removed
+ # from the API in the future, in which case the fallback is the use
+ # the same data available in the paraminfo for query.
+ query_modules_param = self.parameter('paraminfo', 'querymodules')
+
+ assert('limit' in query_modules_param)
+ self._limit = query_modules_param['limit']
+
+ if query_modules_param:
+ assert('type' in query_modules_param)
+ self._query_modules = frozenset(query_modules_param['type'])
+ else:
+ if 'query' not in self._paraminfo:
+ self.fetch(set(['query']), _init=True)
+
+ prop_param = self.parameter('query', 'prop')
+ list_param = self.parameter('query', 'list')
+ generator_param = self.parameter('query', 'generator')
+
+ assert(prop_param)
+ assert(list_param)
+ assert(generator_param)
+ assert('type' in prop_param)
+ assert('type' in list_param)
+ assert('type' in generator_param)
+
+ self._query_modules = frozenset(
+ prop_param['type'] + list_param['type'] +
+ generator_param['type']
+ )
+
+ self.__inited = True
+
+ def _emulate_pageset(self):
+ # pageset isnt a module in the new system, so it is emulated, with
+ # the paraminfo from the query module.
+ assert('query' in self._paraminfo)
+
+ self._paraminfo['pageset'] = {
+ 'name': 'pageset',
+ 'classname': 'ApiPageSet',
+ 'prefix': '',
+ 'readrights': '',
+ 'helpurls': [],
+ 'parameters': self._paraminfo['query']['parameters']
+ }
+
+ def fetch(self, modules, _init=False):
+ """
+ Fetch paraminfo for multiple modules.
+
+ @param modules: API modules to load
+ @type modules: set
+ @rtype: NoneType
+ """
+ # The first request should be 'paraminfo', so that
+ # query modules can be prefixed with 'query+'
+ # If _init is True, dont call _init().
+ if 'paraminfo' not in self._paraminfo and not _init:
+ self._init()
+
+ # Users will supply the wrong type, and expect it to work.
+ if not isinstance(modules, set):
+ if isinstance(modules, basestring):
+ modules = set(modules.split('|'))
+ else:
+ modules = set(modules)
+
+ modules -= set(self._paraminfo.keys())
+ if not modules:
+ return
+
+ assert(self._query_modules or _init)
+
+ # This can be further optimised, by grouping them in more stable
+ # subsets, which are unlikely to change. i.e. first request core
+ # modules which have been a stable part of the API for a long time.
+ # Also detecting extension based modules may help.
+ for module_batch in itergroup(sorted(modules), self._limit):
+ if self.modules_only_mode and 'pageset' in module_batch:
+ pywikibot.debug('paraminfo fetch: removed pageset', _logger)
+ module_batch.remove('pageset')
+ if 'query' not in self._paraminfo:
+ pywikibot.debug('paraminfo batch: added query', _logger)
+ module_batch.append('query')
+ # If this occurred during initialisation,
+ # also record it in the preloaded_modules.
+ # (at least so tests know an extra load was intentional)
+ if not self.__inited:
+ self.preloaded_modules |= set(['query'])
+
+ params = {
+ 'expiry': config.API_config_expiry,
+ 'site': self.site,
+ 'action': 'paraminfo',
+ }
+
+ if self.modules_only_mode:
+ params['modules'] = ['query+' + mod
+ if mod in self._query_modules
+ else mod
+ for mod in module_batch]
+ else:
+ params['modules'] = [mod for mod in module_batch
+ if mod not in self._query_modules
+ and mod not in self.root_modules]
+ params['querymodules'] = [mod for mod in module_batch
+ if mod in self._query_modules]
+
+ for mod in set(module_batch) & self.root_modules:
+ params[mod + 'module'] = 1
+
+ request = CachedRequest(**params)
+ result = request.submit()
+
+ normalized_result = self.normalize_paraminfo(result)
+
+ self._paraminfo.update(normalized_result)
+
+ if self.modules_only_mode and 'pageset' in modules:
+ self._emulate_pageset()
+
+ @classmethod
+ def normalize_paraminfo(cls, data):
+ """Convert both old and new API JSON into a new-ish data structure."""
+ return dict([(mod_data['name'] if 'name' in mod_data else
+ 'main' if mod_data['classname'] == 'ApiMain' else
+ 'pageset' if mod_data['classname'] == 'ApiPageSet' else
+ '<unknown>:' + mod_data['classname'],
+ mod_data)
+ for modules_data in
+ [[modules_data] if paraminfo_key in cls.root_module_keys
+ else modules_data
+ for paraminfo_key, modules_data
+ in data['paraminfo'].items()
+ if modules_data and paraminfo_key in cls.paraminfo_keys]
+ for mod_data in modules_data])
+
+ def __getitem__(self, key):
+ """Return a paraminfo property, caching it."""
+ self.fetch(set([key]))
+ return self._paraminfo[key]
+
+ def __contains__(self, key):
+ """Return whether the value is cached."""
+ return key in self._paraminfo
+
+ def __len__(self):
+ """Obtain length of the iterable."""
+ return len(self._paraminfo)
+
+ def parameter(self, module, param_name):
+ """
+ Get details about one modules parameter.
+
+ @param module: API module name
+ @type module: str
+ @param param_name: parameter name in the module
+ @type param_name: str
+ @return: metadata that describes how the parameter may be used
+ @rtype: dict
+ """
+ # TODO: the 'description' field of each parameter is not in the default
+ # output of v1.25, and cant removed from previous API versions.
+ # There should be an option to remove this verbose data from the cached
+ # version, for earlier versions of the API, and/or extract any useful
+ # data and discard the entire received paraminfo structure. There are
+ # also params which are common to many modules, such as those provided
+ # by the ApiPageSet php class: titles, pageids, redirects, etc.
+ self.fetch(set([module]))
+ param_data = [param for param in self._paraminfo[module]['parameters']
+ if param['name'] == param_name]
+ return param_data[0] if len(param_data) else None
+
+ @property
+ def modules(self):
+ """Set of all modules."""
+ if not self.__inited:
+ self._init()
+ return self._action_modules | self._query_modules
+
+ @property
+ def action_modules(self):
+ """Set of all action modules."""
+ if not self.__inited:
+ self._init()
+ return self._action_modules
+
+ @property
+ def query_modules(self):
+ """Set of all query modules."""
+ if not self.__inited:
+ self._init()
+ return self._query_modules
+
+ @property
+ def prefixes(self):
+ """
+ Mapping of module to its prefix for all modules with a prefix.
+
+ This loads paraminfo for all modules.
+ """
+ if not self._prefixes:
+ self._prefixes = self.module_attribute_map('prefix')
+ return self._prefixes
+
+ def module_attribute_map(self, attribute, modules=None):
+ """
+ Mapping of modules with an attribute to the attribute value.
+
+ @param attribute: attribute name
+ @type attribute: basestring
+ @param modules: modules to include (default: all modules)
+ @type modules: set
+ @rtype: dict
+ """
+ if modules is None:
+ modules = self.modules
+ self.fetch(modules)
+ return dict([(mod, self._paraminfo[mod][attribute])
+ for mod in modules
+ if self._paraminfo[mod][attribute]])
+
+ @property
+ def query_modules_with_limits(self):
+ """Set of all query modules which have limits."""
+ if not self._with_limits:
+ self.fetch(self.query_modules)
+ self._with_limits = frozenset(
+ [mod for mod in self.query_modules
+ if self.parameter(mod, 'limit')])
+ return self._with_limits
class TimeoutError(Error):
@@ -894,7 +1209,7 @@
# make sure request type is valid, and get limit key if any
for modtype in ("generator", "list", "prop", "meta"):
if modtype in kwargs:
- self.module = kwargs[modtype]
+ self.modules = kwargs[modtype].split('|')
break
else:
raise Error("%s: No query module name found in arguments."
@@ -910,17 +1225,41 @@
# Explicitly enable the simplified continuation
kwargs['continue'] = ''
self.request = Request(**kwargs)
- self.prefix = None
+
+ # This forces all paraminfo for all query modules to be bulk loaded.
+ limited_modules = (
+ set(self.modules) & self.site._paraminfo.query_modules_with_limits
+ )
+ if not limited_modules:
+ self.limited_module = None
+ elif len(limited_modules) == 1:
+ self.limited_module = limited_modules.pop()
+ else:
+ # Select the first limited module in the request.
+ for module in self.modules:
+ if module in self.site._paraminfo.query_modules_with_limits:
+ self.limited_module = module
+ break
+ pywikibot.log('%s: multiple requested query modules support limits'
+ "; using the first such module '%s' of %r"
+ % (self.__class__.__name__, self.limited_module,
+ self.modules))
+
self.api_limit = None
- self.update_limit() # sets self.prefix
+
+ if self.limited_module:
+ self.prefix = self.site._paraminfo[self.limited_module]['prefix']
+ self._update_limit()
+
if self.api_limit is not None and "generator" in kwargs:
self.prefix = "g" + self.prefix
+
self.limit = None
self.query_limit = self.api_limit
if "generator" in kwargs:
self.resultkey = "pages" # name of the "query" subelement key
else: # to look for when iterating
- self.resultkey = self.module
+ self.resultkey = self.modules[0]
# usually the (query-)continue key is the same as the querymodule,
# but not always
@@ -930,54 +1269,7 @@
# "langlinks":{"llcontinue":"12188973|pt"},
# "templates":{"tlcontinue":"310820|828|Namespace_detect"}}
# self.continuekey is a list
- self.continuekey = self.module.split('|')
-
- @property
- def __modules(self):
- """
- Cache paraminfo in this request's Site object.
-
- Hold the query data for paraminfo on
- querymodule=self.module at self.site.
-
- """
- if not hasattr(self.site, "_modules"):
- setattr(self.site, "_modules", dict())
- return self.site._modules
-
- @__modules.deleter
- def __modules(self):
- """Delete the instance cache - maybe we don't need it."""
- if hasattr(self.site, "_modules"):
- del self.site._modules
-
- @property
- def _modules(self):
- """Query api on self.site for paraminfo on self.module."""
- modules = self.module.split('|')
- if not set(modules) <= set(self.__modules.keys()):
- if LV(self.site.version()) < LV('1.25wmf4'):
- key = 'querymodules'
- value = self.module
- else:
- key = 'modules'
- value = ['query+' + module for module in modules]
- paramreq = CachedRequest(expiry=config.API_config_expiry,
- site=self.site, action="paraminfo",
- **{key: value})
- data = paramreq.submit()
- assert "paraminfo" in data
- assert key in data["paraminfo"]
- assert len(data["paraminfo"][key]) == len(modules)
- for paraminfo in data["paraminfo"][key]:
- assert paraminfo["name"] in self.module
- if "missing" in paraminfo:
- raise Error("Invalid query module name '%s'." % self.module)
- self.__modules[paraminfo["name"]] = paraminfo
- _modules = {}
- for m in modules:
- _modules[m] = self.__modules[m]
- return _modules
+ self.continuekey = self.modules
def set_query_increment(self, value):
"""Set the maximum number of items to be retrieved per API query.
@@ -1011,22 +1303,17 @@
"""
self.limit = int(value)
- def update_limit(self):
+ def _update_limit(self):
"""Set query limit for self.module based on api response."""
- for mod in self.module.split('|'):
- for param in self._modules[mod].get("parameters", []):
- if param["name"] == "limit":
- if self.site.logged_in() and self.site.has_right('apihighlimits'):
- self.api_limit = int(param["highmax"])
- else:
- self.api_limit = int(param["max"])
- if self.prefix is None:
- self.prefix = self._modules[mod]["prefix"]
- pywikibot.debug(u"%s: Set query_limit to %i."
- % (self.__class__.__name__,
- self.api_limit),
- _logger)
- return
+ param = self.site._paraminfo.parameter(self.limited_module, 'limit')
+ if self.site.logged_in() and self.site.has_right('apihighlimits'):
+ self.api_limit = int(param["highmax"])
+ else:
+ self.api_limit = int(param["max"])
+ pywikibot.debug(u"%s: Set query_limit to %i."
+ % (self.__class__.__name__,
+ self.api_limit),
+ _logger)
def set_namespace(self, namespaces):
"""Set a namespace filter on this query.
@@ -1034,15 +1321,15 @@
@param namespaces: Either an int or a list of ints
"""
+ assert(self.limited_module) # some modules do not have a prefix
if isinstance(namespaces, list):
namespaces = "|".join(str(n) for n in namespaces)
else:
namespaces = str(namespaces)
- for mod in self.module.split('|'):
- for param in self._modules[mod].get("parameters", []):
- if param["name"] == "namespace":
- self.request[self.prefix + "namespace"] = namespaces
- return
+
+ param = self.site._paraminfo.parameter(self.limited_module, 'namespace')
+ if param:
+ self.request[self.prefix + "namespace"] = namespaces
def _query_continue(self):
if all(key not in self.data[self.continue_name]
@@ -1186,7 +1473,7 @@
# self.resultkey not in data in last request.submit()
# only "(query-)continue" was retrieved.
previous_result_had_data = False
- if self.module == "random" and self.limit:
+ if self.modules[0] == "random" and self.limit:
# "random" module does not return "(query-)continue"
# now we loop for a new random query
del self.data # a new request is needed
diff --git a/pywikibot/site.py b/pywikibot/site.py
index ba1b00c..66a2e95 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -1416,6 +1416,7 @@
self._msgcache = {}
self._loginstatus = LoginStatus.NOT_ATTEMPTED
self._siteinfo = Siteinfo(self)
+ self._paraminfo = api.ParamInfo(self)
self.tokens = TokenWallet(self)
def __getstate__(self):
diff --git a/tests/api_tests.py b/tests/api_tests.py
index a41e74d..5d6a07b 100644
--- a/tests/api_tests.py
+++ b/tests/api_tests.py
@@ -10,6 +10,8 @@
import datetime
import pywikibot
import pywikibot.data.api as api
+from pywikibot.tools import MediaWikiVersion
+
from tests.aspects import (
unittest,
TestCase,
@@ -52,6 +54,191 @@
self.assertEqual(len(item), 2, item)
+class TestParamInfo(DefaultSiteTestCase):
+
+ """Test ParamInfo."""
+
+ def test_init(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertEqual(len(pi), 0)
+ pi._init()
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ len(pi.init_modules))
+
+ self.assertIn('info', pi._query_modules)
+
+ def test_init_pageset(self):
+ site = self.get_site()
+ self.assertNotIn('query', api.ParamInfo.init_modules)
+ pi = api.ParamInfo(site, set(['pageset']))
+ self.assertNotIn('query', api.ParamInfo.init_modules)
+ self.assertNotIn('query', pi.preloaded_modules)
+ self.assertEqual(len(pi), 0)
+ pi._init()
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertIn('pageset', pi)
+
+ if pi.modules_only_mode:
+ self.assertIn('query', pi.preloaded_modules)
+ self.assertIn('query', pi)
+ self.assertEqual(len(pi), 4)
+ else:
+ self.assertNotIn('query', pi.preloaded_modules)
+ self.assertNotIn('query', pi)
+ self.assertEqual(len(pi), 3)
+
+ self.assertEqual(len(pi),
+ len(pi.preloaded_modules))
+
+ if MediaWikiVersion(site.version()) >= MediaWikiVersion("1.21"):
+ # 'generator' was added to 'pageset' in 1.21
+ generators_param = pi.parameter('pageset', 'generator')
+ self.assertGreater(len(generators_param['type']), 1)
+
+ def test_generators(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site, set(['pageset', 'query']))
+ self.assertEqual(len(pi), 0)
+ pi._init()
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertIn('pageset', pi)
+ self.assertIn('query', pi)
+
+ if MediaWikiVersion(site.version()) >= MediaWikiVersion("1.21"):
+ # 'generator' was added to 'pageset' in 1.21
+ pageset_generators_param = pi.parameter('pageset', 'generator')
+ query_generators_param = pi.parameter('query', 'generator')
+
+ self.assertEqual(pageset_generators_param, query_generators_param)
+
+ def test_with_module_info(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertEqual(len(pi), 0)
+ pi.fetch(['info'])
+ self.assertIn('info', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ 1 + len(pi.preloaded_modules))
+
+ self.assertEqual(pi['info']['prefix'], 'in')
+
+ param = pi.parameter('info', 'prop')
+ self.assertIsInstance(param, dict)
+
+ self.assertEqual(param['name'], 'prop')
+ self.assertNotIn('deprecated', param)
+
+ self.assertIsInstance(param['type'], list)
+ self.assertIn('protection', param['type'])
+
+ def test_with_module_revisions(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertEqual(len(pi), 0)
+ pi.fetch(['revisions'])
+ self.assertIn('revisions', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ 1 + len(pi.preloaded_modules))
+
+ self.assertEqual(pi['revisions']['prefix'], 'rv')
+
+ param = pi.parameter('revisions', 'prop')
+ self.assertIsInstance(param, dict)
+
+ self.assertEqual(param['name'], 'prop')
+ self.assertNotIn('deprecated', param)
+
+ self.assertIsInstance(param['type'], list)
+ self.assertIn('user', param['type'])
+
+ def test_multiple_modules(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertEqual(len(pi), 0)
+ pi.fetch(['info', 'revisions'])
+ self.assertIn('info', pi)
+ self.assertIn('revisions', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ 2 + len(pi.preloaded_modules))
+
+ def test_with_invalid_module(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertEqual(len(pi), 0)
+ pi.fetch('foobar')
+ self.assertNotIn('foobar', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ len(pi.preloaded_modules))
+
+ def test_query_modules_with_limits(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertIn('revisions', pi.query_modules_with_limits)
+ self.assertNotIn('info', pi.query_modules_with_limits)
+
+ def test_modules(self):
+ """Test v1.8 modules exist."""
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertIn('revisions', pi.modules)
+ self.assertIn('help', pi.modules)
+ self.assertIn('allpages', pi.modules)
+
+ def test_prefixes(self):
+ """Test v1.8 module prefixes exist."""
+ site = self.get_site()
+ pi = api.ParamInfo(site)
+ self.assertIn('revisions', pi.prefixes)
+ self.assertIn('login', pi.prefixes)
+ self.assertIn('allpages', pi.prefixes)
+
+ def test_old_mode(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site, modules_only_mode=False)
+ pi.fetch(['info'])
+ self.assertIn('info', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ 1 + len(pi.preloaded_modules))
+
+ self.assertIn('revisions', pi.prefixes)
+
+ def test_new_mode(self):
+ site = self.get_site()
+ pi = api.ParamInfo(site, modules_only_mode=True)
+ pi.fetch(['info'])
+ self.assertIn('info', pi)
+
+ self.assertIn('main', pi)
+ self.assertIn('paraminfo', pi)
+ self.assertEqual(len(pi),
+ 1 + len(pi.preloaded_modules))
+
+ self.assertIn('revisions', pi.prefixes)
+
+
class TestPageGenerator(TestCase):
"""API PageGenerator object test class."""
diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py
index fb6d68f..3b3afe4 100644
--- a/tests/dry_api_tests.py
+++ b/tests/dry_api_tests.py
@@ -13,8 +13,9 @@
import pywikibot
from pywikibot.data.api import (
- Request,
CachedRequest,
+ ParamInfo,
+ Request,
QueryGenerator,
)
from pywikibot.family import Family
@@ -218,6 +219,136 @@
self.assertEqual(req.mime, True)
+class ParamInfoDictTests(DefaultDrySiteTestCase):
+
+ """Test extracting data from the ParamInfo."""
+
+ prop_info_param_data = { # data from 1.25
+ "name": "info",
+ "classname": "ApiQueryInfo",
+ "path": "query+info",
+ "group": "prop",
+ "prefix": "in",
+ "parameters": [
+ {
+ "name": "prop",
+ "multi": "",
+ "limit": 500,
+ "lowlimit": 50,
+ "highlimit": 500,
+ "type": [
+ "protection",
+ "talkid",
+ "watched",
+ "watchers",
+ "notificationtimestamp",
+ "subjectid",
+ "url",
+ "readable",
+ "preload",
+ "displaytitle"
+ ]
+ },
+ {
+ "name": "token",
+ "deprecated": "",
+ "multi": "",
+ "limit": 500,
+ "lowlimit": 50,
+ "highlimit": 500,
+ "type": [
+ "edit",
+ "delete",
+ "protect",
+ "move",
+ "block",
+ "unblock",
+ "email",
+ "import",
+ "watch"
+ ]
+ },
+ {
+ "name": "continue",
+ "type": "string"
+ }
+ ],
+ "querytype": "prop"
+ }
+
+ def test_new_format(self):
+ pi = self.get_site()._paraminfo
+ # Set it to the new limited set of keys.
+ pi.paraminfo_keys = frozenset(['modules'])
+
+ data = pi.normalize_paraminfo({
+ 'paraminfo': {
+ 'modules': [
+ self.prop_info_param_data,
+ {'name': 'edit'}
+ ]
+ }
+ })
+
+ pi._paraminfo.update(data)
+ self.assertIn('info', pi._paraminfo)
+ self.assertIn('edit', pi._paraminfo)
+
+ def test_old_format(self):
+ pi = self.get_site()._paraminfo
+ # Reset it to the complete set of possible keys defined in the class
+ pi.paraminfo_keys = ParamInfo.paraminfo_keys
+
+ data = pi.normalize_paraminfo({
+ 'paraminfo': {
+ 'querymodules': [self.prop_info_param_data],
+ 'modules': [{'name': 'edit'}]
+ }
+ })
+
+ pi._paraminfo.update(data)
+ self.assertIn('info', pi._paraminfo)
+ self.assertIn('edit', pi._paraminfo)
+
+ def test_attribute(self):
+ pi = self.get_site()._paraminfo
+ # Reset it to the complete set of possible keys defined in the class
+ pi.paraminfo_keys = ParamInfo.paraminfo_keys
+
+ data = pi.normalize_paraminfo({
+ 'paraminfo': {
+ 'querymodules': [self.prop_info_param_data],
+ }
+ })
+
+ pi._paraminfo.update(data)
+
+ self.assertEqual(pi._paraminfo['info']['prefix'], 'in')
+ self.assertEqual(pi._paraminfo['info']['querytype'], 'prop')
+
+ def test_parameter(self):
+ pi = self.get_site()._paraminfo
+ # Reset it to the complete set of possible keys defined in the class
+ pi.paraminfo_keys = ParamInfo.paraminfo_keys
+
+ data = pi.normalize_paraminfo({
+ 'paraminfo': {
+ 'querymodules': [self.prop_info_param_data],
+ }
+ })
+
+ pi._paraminfo.update(data)
+
+ param = pi.parameter('info', 'token')
+ self.assertIsInstance(param, dict)
+
+ self.assertEqual(param['name'], 'token')
+ self.assertIn('deprecated', param)
+
+ self.assertIsInstance(param['type'], list)
+ self.assertIn('email', param['type'])
+
+
class QueryGenTests(DefaultDrySiteTestCase):
"""Test QueryGenerator with a real site."""
--
To view, visit https://gerrit.wikimedia.org/r/172979
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I84a0769f19abf8740106659426109c62e8438e65
Gerrit-PatchSet: 6
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: Legoktm <legoktm.wikipedia(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: allpages.py: fix namespace param type
......................................................................
allpages.py: fix namespace param type
If namespace is a Namespace object, str(namespace) returns the name of
the namespace and not the index.
In the API query, the param will not be recognised as valid.
Bug: 73214
Change-Id: Ib69522bf8fc62cdd8815f45bfa7e9fb5c88f864e
---
M pywikibot/site.py
1 file changed, 2 insertions(+), 2 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 7f3e1ea..1d40648 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3079,7 +3079,7 @@
filterredir = False
apgen = self._generator(api.PageGenerator, type_arg="allpages",
- gapnamespace=str(namespace),
+ gapnamespace=int(namespace),
gapfrom=start, step=step, total=total,
g_content=content)
if prefix:
@@ -3138,7 +3138,7 @@
if not isinstance(namespace, (int, Namespace)):
raise Error("alllinks: only one namespace permitted.")
algen = self._generator(api.ListGenerator, type_arg="alllinks",
- alnamespace=str(namespace), alfrom=start,
+ alnamespace=int(namespace), alfrom=start,
step=step, total=total)
if prefix:
algen.request["alprefix"] = prefix
--
To view, visit https://gerrit.wikimedia.org/r/173465
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib69522bf8fc62cdd8815f45bfa7e9fb5c88f864e
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Mpaa <mpaa.wiki(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: jenkins-bot <>