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 <[email protected]>
Gerrit-Reviewer: John Vandenberg <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Merlijn van Deen <[email protected]>
Gerrit-Reviewer: XZise <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
Pywikibot-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/pywikibot-commits