John Vandenberg has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/172979

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, 606 insertions(+), 76 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core 
refs/changes/79/172979/1

diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 83ef419..6d007a2 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,290 @@
         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):
+
+    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, only_use_modules=None):
+        """
+        Constructor.
+
+        @param preloaded_modules: API modules to preload
+        @type preloaded_modules: set of string
+        @param only_use_modules: use the 'modules' only syntax for paraminfo
+        @type: only_use_modules: bool or None to only use default, which True
+            if the site is 1.25wm4+
+        """
+        self.site = site
+        self._prefixes = {}
+        self._with_limits = None
+        self._paraminfo = {}
+
+        self._query_modules = []  # filled in fetch()
+        self._limit = None
+
+        if preloaded_modules:
+            self.init_modules |= set(preloaded_modules)
+        self.__inited = False
+
+        self.only_use_modules = only_use_modules
+        if self.only_use_modules:
+            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.25wm4+
+        # must only use 'modules' parameter.
+        if self.only_use_modules is None:
+            self.only_use_modules = LV(self.site.version()) >= LV('1.25wmf4')
+
+        self.fetch(self.init_modules, _init=True)
+        main_modules_param = self.parameter('main', 'action')
+
+        assert(main_modules_param)
+        assert('type' in main_modules_param)
+
+        # FIXME: While deprecated in 1.25, paraminfo param 'querymodules'
+        # provides a list of all query modules. This will likely be removed
+        # from the API in the future.
+        # paraminfo for the query module contains the same info, split into
+        # multiple sets of data, and should be used as a fallback at least.
+        query_modules_param = self.parameter('paraminfo', 'querymodules')
+
+        assert(query_modules_param)
+        assert('type' in query_modules_param)
+        assert('limit' in query_modules_param)
+
+        self._action_modules = frozenset(main_modules_param['type'])
+        self._query_modules = frozenset(query_modules_param['type'])
+        self._limit = query_modules_param['limit']
+        self.__inited = True
+
+    def _emulate_pageset(self):
+        # pageset isnt a module in the new system, so it is emulated using
+        # a) param limit information from a limit param already fetched
+        # b) the query module's generator list
+        a_limits_param = self.parameter('paraminfo', 'modules')
+        limit = a_limits_param['limit']
+        lowlimit = a_limits_param['lowlimit']
+        highlimit = a_limits_param['lowlimit']
+
+        # TODO: Find out when 'generator' was added to 'pageset';
+        # sometime after 1.19 and before 1.23.
+        assert('query' in self._paraminfo)
+        generators_param = self.parameter('query', 'generator')
+        assert('type' in generators_param)
+
+        self._paraminfo['pageset'] = {
+            "classname": "ApiPageSet",
+            "prefix": "",
+            "readrights": "",
+            "helpurls": [],
+            "parameters": [
+                {
+                    "name": "titles",
+                    "multi": "",
+                    "limit": limit,
+                    "lowlimit": lowlimit,
+                    "highlimit": highlimit,
+                    "type": "string"
+                },
+                {
+                    "name": "pageids",
+                    "multi": "",
+                    "limit": limit,
+                    "lowlimit": lowlimit,
+                    "highlimit": highlimit,
+                    "type": "integer"
+                },
+                {
+                    "name": "revids",
+                    "multi": "",
+                    "limit": limit,
+                    "lowlimit": lowlimit,
+                    "highlimit": highlimit,
+                    "type": "integer"
+                },
+                {
+                    "name": "generator",
+                    "type": generators_param['type'],
+                },
+                {
+                    "name": "redirects",
+                    "default": "false",
+                    "type": "boolean"
+                },
+                {
+                    "name": "converttitles",
+                    "default": "false",
+                    "type": "boolean"
+                },
+            ]
+        }
+
+    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.only_use_modules 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 init modules.
+                # (at least so tests know an extra load was intentional)
+                if not self.__inited:
+                    # this sets the instance attribute as the
+                    # class attribute is a frozen set.
+                    self.init_modules = self.init_modules + set(['query'])
+
+            # TODO: investigate which version it became possible to disable
+            # verbose paraminfo, and which version verbose paraminfo was
+            # disabled by default.
+            # TODO: remove the verbose info from memory!? Lots of fiddly
+            # memory changes; maybe just extract all useful information
+            # and get rid of the normalised dict.
+            params = {
+                'expiry': config.API_config_expiry,
+                'site': self.site,
+                'action': 'paraminfo',
+            }
+
+            if self.only_use_modules:
+                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()
+
+            normalised_result = self.normalize_paraminfo(result)
+
+            self._paraminfo.update(normalised_result)
+
+        if self.only_use_modules 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
+        """
+        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 query_modules(self):
+        """Set of all query modules."""
+        if not self._query_modules:
+            self.fetch(set())
+        return self._query_modules
+
+    @property
+    def prefixes(self):
+        """Mapping of prefixes for all query modules which have a prefix."""
+        # FIX: this should get prefixes for modules not in query modules
+        if not self._prefixes:
+            self.fetch(self.query_modules)
+            self._prefixes = dict([(mod, self._paraminfo[mod]['prefix'])
+                                   for mod in self.query_modules
+                                   if self._paraminfo[mod]['prefix']])
+        return self._prefixes
+
+    @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 +1178,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 +1194,34 @@
             # Explicitly enable the simplified continuation
             kwargs['continue'] = ''
         self.request = Request(**kwargs)
-        self.prefix = None
+
+        limited_modules = (
+            set(self.modules) & self.site._paraminfo.query_modules_with_limits
+        )
+        if len(limited_modules) > 1:
+            raise ValueError('%s: expected only one limited query modules: %s'
+                             % (self.__class__.__name__,
+                                ', '.join(limited_modules)))
+
         self.api_limit = None
-        self.update_limit()  # sets self.prefix
+
+        if limited_modules:
+            self.limited_module = limited_modules.pop()
+            self.prefix = self.site._paraminfo.prefixes[self.limited_module]
+            self._update_limit()
+        else:
+            self.limited_module = None
+            self.prefix = None
+
         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 +1231,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 +1265,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 +1283,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 +1435,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..cb74afa 100644
--- a/tests/api_tests.py
+++ b/tests/api_tests.py
@@ -52,6 +52,155 @@
             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.init_modules)
+        self.assertEqual(len(pi), 0)
+        pi._init()
+
+        self.assertIn('main', pi)
+        self.assertIn('paraminfo', pi)
+        self.assertIn('pageset', pi)
+
+        if pi.only_use_modules:
+            self.assertIn('query', pi.init_modules)
+            self.assertIn('query', pi)
+        else:
+            self.assertNotIn('query', pi.init_modules)
+            self.assertNotIn('query', pi)
+
+        self.assertEqual(len(pi),
+                         len(pi.init_modules))
+
+        # TODO: Find out when 'generator' was added to 'pageset';
+        # sometime after 1.19 and before 1.23.
+        #
+        # 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)
+
+        # TODO: Find out when 'generator' was added to 'pageset';
+        # sometime after 1.19 and before 1.23.
+
+        # 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.init_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.init_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.init_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.init_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_prefixes(self):
+        site = self.get_site()
+        pi = api.ParamInfo(site)
+        self.assertIn('revisions', pi.prefixes)
+        self.assertNotIn('tokens', 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: newchange
Gerrit-Change-Id: I84a0769f19abf8740106659426109c62e8438e65
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <[email protected]>

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

Reply via email to