jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/845037 )
Change subject: [cleanup] remove pre mw 1.27 code in ParamInfo ......................................................................
[cleanup] remove pre mw 1.27 code in ParamInfo
- deprecate modules_only_mode parameter of ParamInfo - deprecate ParamInfo.paraminfo_keys - remove ParamInfo._emulate_pageset method - simplify normalize_paraminfo and change it to a staticmethod - combine module_paths with _module_set as suggested - remove _prefix_submodules and add this one-liner to submodules - update several tests
Bug: T306637 Change-Id: Iedae9616aa19db5f4a63f1558f4d76e675182b21 --- M tests/paraminfo_tests.py M pywikibot/data/api/_paraminfo.py M tests/dry_api_tests.py M tests/api_tests.py 4 files changed, 249 insertions(+), 427 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/data/api/_paraminfo.py b/pywikibot/data/api/_paraminfo.py index 4112919..35b8e1b 100644 --- a/pywikibot/data/api/_paraminfo.py +++ b/pywikibot/data/api/_paraminfo.py @@ -9,7 +9,8 @@
import pywikibot from pywikibot import config -from pywikibot.backports import Dict, batched, removeprefix +from pywikibot.backports import Dict, FrozenSet, Iterable, Set, batched +from pywikibot.tools import classproperty, deprecated, remove_last_args
__all__ = ['ParamInfo'] @@ -17,35 +18,27 @@
class ParamInfo(Sized, Container):
- """ - API parameter information data object. + """API parameter information data object.
Provides cache aware fetching of parameter information.
- It does not support the format modules. + .. seealso:: :api:`Parameter information` """
- paraminfo_keys = frozenset(['modules', 'querymodules', 'formatmodules', - 'mainmodule', 'pagesetmodule']) - - root_modules = frozenset(['main', 'pageset']) - + root_modules = frozenset(['main']) init_modules = frozenset(['main', 'paraminfo']) + param_modules = ('list', 'meta', 'prop')
- def __init__( - self, - site, - preloaded_modules=None, - modules_only_mode=None - ) -> None: - """ - Initializer. + @remove_last_args(['modules_only_mode']) + def __init__(self, + site, + preloaded_modules: Optional[Set[str]] = None) -> None: + """Initializer. + + .. deprecated:: 8.4 + the *modules_only_mode* parameter
: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.25wmf4+ """ self.site = site
@@ -61,21 +54,14 @@ self._modules = {} # filled in _init() (and enlarged in fetch) self._limit = None
- self.preloaded_modules = self.init_modules + self._preloaded_modules = self.init_modules if preloaded_modules: - self.preloaded_modules |= set(preloaded_modules) + self._preloaded_modules |= set(preloaded_modules)
- self.modules_only_mode = modules_only_mode - if self.modules_only_mode: - self.paraminfo_keys = frozenset(['modules']) - - def _add_submodules(self, name, modules) -> None: - """Add the modules to the internal cache or check if equal.""" - # The current implementation here doesn't support submodules inside of - # submodules, because that would require to fetch all modules when only - # the names of them were requested + def _add_submodules(self, name: str, + modules: Union[Set[str], Dict[str, str]]) -> None: + """Add the modules to the internal cache.""" assert '+' not in name - modules = frozenset(modules) if name == 'main': # The main module behaves differently as it has no prefix if self._action_modules: @@ -83,89 +69,53 @@ else: self._action_modules = modules elif name in self._modules: - assert modules == self._modules[name] + # update required to updates from dict and set + self._modules[name].update(modules) else: self._modules[name] = modules
def _init(self): assert ('query' in self._modules) is ('main' in self._paraminfo) + + # Skip if ParamInfo is already initialized if 'query' in self._modules: return - mw_ver = self.site.mw_version
- # 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 = mw_ver >= '1.25wmf4' - if self.modules_only_mode: - self.paraminfo_keys = frozenset(['modules']) + # Assume that it will be desirable to prefetch 'query' + self._preloaded_modules |= {'query'}
- # Assume that by v1.26, it will be desirable to prefetch 'query' - self.preloaded_modules |= {'query'} - - self._fetch(self.preloaded_modules) + self._fetch(self._preloaded_modules)
main_modules_param = self.parameter('main', 'action') assert main_modules_param assert 'type' in main_modules_param assert isinstance(main_modules_param['type'], list) assert self._action_modules == set(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') - - if 'limit' not in query_modules_param: - raise RuntimeError('"limit" not found in query modules') - self._limit = query_modules_param['limit'] - - if query_modules_param and 'type' in query_modules_param: - # 'type' is the list of modules - self._add_submodules('query', query_modules_param['type']) - - if 'query' not in self._modules: - assert 'query' not in self._paraminfo - self._fetch({'query'}) assert 'query' in self._modules - - def _emulate_pageset(self) -> None: - """Emulate the pageset module, which existed until MW 1.24.""" - # pageset isn't 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', - 'path': 'pageset', - 'classname': 'ApiPageSet', - 'prefix': '', - 'readrights': '', - 'helpurls': [], - 'parameters': self._paraminfo['query']['parameters'] - } + # Retrieve all query submodules + self._limit = 50 + for param in self.param_modules: + query_modules_param = self.parameter('query', param) + self._limit = min(query_modules_param['limit'], self._limit) + self._add_submodules('query', query_modules_param['submodules'])
@staticmethod - def _modules_to_set(modules) -> set: - """Return modules as a set. - - :type modules: iterable or str - """ + def _modules_to_set(modules: Union[Iterable, str]) -> Set[str]: + """Return modules as a set.""" if isinstance(modules, str): return set(modules.split('|')) return set(modules)
- def fetch(self, modules) -> None: - """ - Fetch paraminfo for multiple modules. + def fetch(self, modules: Union[Iterable, str]) -> None: + """Fetch paraminfo for multiple modules.
- No exception is raised when paraminfo for a module does not exist. - Use __getitem__ to cause an exception if a module does not exist. + No exception is raised when paraminfo for a module does not + exist. ``paraminfo[module]`` to cause an exception if a module + does not exist.
:param modules: API modules to load - :type modules: iterable or str """ if 'main' not in self._paraminfo: # The first request should be 'paraminfo', so that @@ -217,38 +167,12 @@ # 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. - # Also, when self.modules_only_mode is disabled, both modules and - # querymodules may each be filled with self._limit items, doubling the - # number of modules that may be processed in a single batch. for module_batch in module_generator(): - if self.modules_only_mode and 'pageset' in module_batch: - pywikibot.debug('paraminfo fetch: removed pageset') - module_batch.remove('pageset') - # If this occurred during initialisation, - # also record it in the preloaded_modules. - # (at least so tests know an extra load was intentional) - if 'query' not in self._paraminfo: - pywikibot.debug('paraminfo batch: added query') - module_batch.append('query') - self.preloaded_modules |= {'query'} - params = { 'action': 'paraminfo', + 'modules': module_batch, }
- if self.modules_only_mode: - params['modules'] = module_batch - else: - params['modules'] = [mod for mod in module_batch - if not mod.startswith('query+') - and mod not in self.root_modules] - params['querymodules'] = [removeprefix(mod, 'query+') - for mod in module_batch - if mod.startswith('query+')] - - for mod in set(module_batch) & self.root_modules: - params[mod + 'module'] = 1 - # Request need ParamInfo to determine use_get request = self.site._request(expiry=config.API_config_expiry, use_get=True, @@ -277,8 +201,8 @@ normalized_result = {missing_modules[0]: normalized_result} elif len(module_batch) > 1 and missing_modules: # Rerequest the missing ones separately - pywikibot.log('Inconsistency in batch "{}"; rerequest ' - 'separately'.format(missing_modules)) + pywikibot.log(f'Inconsistency in batch "{missing_modules}";' + ' rerequest separately') failed_modules.extend(missing_modules)
# Remove all modules which weren't requested, we can't be sure that @@ -291,9 +215,6 @@ for mod in normalized_result.values(): self._generate_submodules(mod['path'])
- if 'pageset' in modules and 'pageset' not in self._paraminfo: - self._emulate_pageset() - def _generate_submodules(self, module) -> None: """Check and generate submodules for the given module.""" parameters = self._paraminfo[module].get('parameters', []) @@ -306,26 +227,27 @@ or 'submodules' not in param: continue
- for submodule in param['submodules'].values(): + for child, submodule in param['submodules'].items(): if '+' in submodule: - parent, child = submodule.rsplit('+', 1) + parent = submodule.rsplit('+', 1)[0] else: - parent, child = 'main', submodule + parent = 'main' if parent == module: submodules.add(child)
if submodules: self._add_submodules(module, submodules) + if module == 'query': - # Previously also modules from generator were used as query - # modules, but verify that those are just a subset of the - # prop/list/meta modules. There is no sanity check as this - # needs to be revisited if query has no generator parameter + # Verify that submodules from generator are just a subset of the + # prop/list/meta modules. for param in parameters: if param['name'] == 'generator': break else: - param = {} + raise RuntimeError( + "'query' module has no 'generator' parameter") + assert param['name'] == 'generator' \ and submodules >= set(param['type'])
@@ -343,8 +265,7 @@ for mod in modules}
def normalize_modules(self, modules) -> set: - """ - Convert the modules into module paths. + """Convert the modules into module paths.
Add query+ to any query module name not also in action modules.
@@ -353,72 +274,42 @@ self._init() return self._normalize_modules(modules)
- @classmethod - def normalize_paraminfo(cls, data): - """ - Convert both old and new API JSON into a new-ish data structure. + @staticmethod + def normalize_paraminfo(data: Dict[str, Any]) -> Dict[str, Any]: + """Convert API JSON into a new data structure with path as key.
For duplicate paths, the value will be False. + + .. versionchanged:: 8.4 + ``normalize_paraminfo`` became a staticmethod. """ result_data = {} - for paraminfo_key, modules_data in data['paraminfo'].items(): - if not modules_data: + modules_data = data['paraminfo'].get('modules', []) + for mod_data in modules_data: + if 'missing' in mod_data: continue
- if paraminfo_key[:-len('module')] in cls.root_modules: - modules_data = [modules_data] - elif not paraminfo_key.endswith('modules'): - continue - - for mod_data in modules_data: - if 'missing' in mod_data: - continue - - name = mod_data.get('name') - php_class = mod_data.get('classname') - - if not name and php_class: - name = removeprefix(php_class, 'Api').lower() - if name not in ('main', 'pageset'): - pywikibot.warning( - f'Unknown paraminfo module "{php_class}"') - name = '<unknown>:' + php_class - - mod_data['name'] = name - - if 'path' not in mod_data: - # query modules often contain 'ApiQuery' and have a suffix. - # 'ApiQuery' alone is the action 'query' - if ('querytype' in mod_data - or php_class and len(php_class) > 8 - and 'ApiQuery' in php_class): - mod_data['path'] = 'query+' + name - else: - mod_data['path'] = name - - path = mod_data['path'] - - if path in result_data: - # Only warn first time - if result_data[path] is not False: - pywikibot.warning(f'Path "{path}" is ambiguous.') - else: - pywikibot.log(f'Found another path "{path}"') - result_data[path] = False - else: - result_data[path] = mod_data + path = mod_data['path'] + if path not in result_data: + result_data[path] = mod_data + elif result_data[path] is not False: + # Only warn first time + result_data[path] = False + pywikibot.warning(f'Path "{path}" is ambiguous.') + else: + pywikibot.log(f'Found another path "{path}"')
return result_data
def __getitem__(self, key): - """ - Return a paraminfo module for the module path, caching it. + """Return a paraminfo module for the module path, caching it.
- Use the module path, such as 'query+x', to obtain the paraminfo for - submodule 'x' in the query module. + Use the module path, such as 'query+x', to obtain the paraminfo + for submodule 'x' in the query module.
- If the key does not include a '+' and is not present in the top level - of the API, it will fallback to looking for the key 'query+x'. + If the key does not include a '+' and is not present in the top + level of the API, it will fallback to looking for the key + 'query+x'. """ self.fetch({key}) if key in self._paraminfo: @@ -444,8 +335,7 @@ module: str, param_name: str ) -> Optional[Dict[str, Any]]: - """ - Get details about one modules parameter. + """Get details about one modules parameter.
Returns None if the parameter does not exist.
@@ -453,13 +343,6 @@ :param param_name: parameter name in the module :return: metadata that describes how the parameter may be used """ - # TODO: the 'description' field of each parameter is not in the default - # output of v1.25, and can't 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. try: module = self[module] except KeyError: @@ -471,8 +354,7 @@ pywikibot.warning(f"module '{module}' has no parameters") return None
- param_data = [param for param in params - if param['name'] == param_name] + param_data = [param for param in params if param['name'] == param_name]
if not param_data: return None @@ -485,17 +367,12 @@ @property def module_paths(self): """Set of all modules using their paths.""" - return self._module_set(True) - - # As soon as modules() is removed, module_paths and _module_set can be - # combined, so don't add any code between these two methods. - def _module_set(self, path): # Load the submodules of all action modules available self.fetch(self.action_modules) modules = set(self.action_modules) for parent_module in self._modules: - submodules = self.submodules(parent_module, path) - assert not submodules & modules or not path + submodules = self.submodules(parent_module, path=True) + assert not submodules & modules modules |= submodules return modules
@@ -510,9 +387,8 @@ """Set of all query module names without query+ path prefix.""" return self.submodules('query')
- def submodules(self, name: str, path: bool = False) -> set: - """ - Set of all submodules. + def submodules(self, name: str, path: bool = False) -> Set[str]: + """Set of all submodules.
:param name: The name of the parent module. :param path: Whether the path and not the name is returned. @@ -522,39 +398,35 @@ self.fetch([name]) submodules = self._modules[name] if path: - submodules = self._prefix_submodules(submodules, name) + # prefix submodules + submodules = {f'{name}+{mod}' for mod in submodules} return submodules
- @staticmethod - def _prefix_submodules(modules, prefix): - """Prefix submodules with path.""" - return {f'{prefix}+{mod}' for mod in modules} - @property - def prefix_map(self): - """ - Mapping of module to its prefix for all modules with a prefix. + def prefix_map(self) -> Dict[str, str]: + """Mapping of module to its prefix for all modules with a prefix.
This loads paraminfo for all modules. """ if not self._prefix_map: - self._prefix_map = {module: prefix - for module, prefix - in self.attributes('prefix').items() - if prefix} + self._prefix_map = { + module: prefix + for module, prefix in self.attributes('prefix').items() + if prefix + } return self._prefix_map.copy()
- def attributes(self, attribute: str, modules: Optional[set] = None): - """ - Mapping of modules with an attribute to the attribute value. + def attributes(self, attribute: str, + modules: Optional[set] = None) -> Dict[str, Any]: + """Mapping of modules with an attribute to the attribute value.
- It will include all modules which have that attribute set, also if that - attribute is empty or set to False. + It will include all modules which have that attribute set, also + if that attribute is empty or set to False.
:param attribute: attribute name - :param modules: modules to include. If None (default), it'll load all - modules including all submodules using the paths. - :rtype: dict using modules as keys + :param modules: modules to include. If None (default), it'll + load all modules including all submodules using the paths. + :return: dict using modules as keys """ if modules is None: modules = self.module_paths @@ -562,3 +434,21 @@
return {mod: self[mod][attribute] for mod in modules if attribute in self[mod]} + + @classproperty + @deprecated(since='8.4.0') + def paraminfo_keys(cls) -> FrozenSet[str]: + """Return module types. + + .. deprecated:: 8.4.0 + """ + return frozenset(['modules']) + + @property + @deprecated(since='8.4.0') + def preloaded_modules(self) -> Union[FrozenSet[str], Set[str]]: + """Return set of preloaded modules. + + .. deprecated:: 8.4.0 + """ + return self._preloaded_modules diff --git a/tests/api_tests.py b/tests/api_tests.py index edbae80..ea03a0e 100755 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -18,11 +18,7 @@ from pywikibot.exceptions import APIError, NoUsernameError from pywikibot.throttle import Throttle from pywikibot.tools import suppress_warnings -from tests.aspects import ( - DefaultDrySiteTestCase, - DefaultSiteTestCase, - TestCase, -) +from tests.aspects import DefaultDrySiteTestCase, DefaultSiteTestCase, TestCase from tests.utils import FakeLoginManager
@@ -89,7 +85,7 @@
self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo) - self.assertLength(pi, pi.preloaded_modules) + self.assertLength(pi, pi._preloaded_modules)
self.assertIn('info', pi.query_modules) self.assertIn('login', pi._action_modules) @@ -103,7 +99,7 @@ self.assertIn('query', pi._paraminfo)
def test_init_pageset(self): - """Test initializing with only the pageset.""" + """Test initializing with deprecated pageset.""" site = self.get_site() self.assertNotIn('query', api.ParamInfo.init_modules) pi = api.ParamInfo(site, {'pageset'}) @@ -113,36 +109,30 @@
self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo) - self.assertIn('pageset', pi._paraminfo) - - if 'query' in pi.preloaded_modules: - self.assertIn('query', pi._paraminfo) - self.assertLength(pi, 4) - else: - self.assertNotIn('query', pi._paraminfo) - self.assertLength(pi, 3) - - self.assertLength(pi, pi.preloaded_modules) - - generators_param = pi.parameter('pageset', 'generator') - self.assertGreater(len(generators_param['type']), 1) + self.assertNotIn('pageset', pi._paraminfo) + self.assertIn('query', pi._paraminfo) + self.assertLength(pi, 3) + self.assertLength(pi._preloaded_modules, 4) + with self.assertRaisesRegex(ValueError, + "paraminfo for 'pageset' not loaded"): + pi.parameter('pageset', 'generator')
def test_generators(self): """Test requesting the generator parameter.""" site = self.get_site() - pi = api.ParamInfo(site, {'pageset', 'query'}) + pi = api.ParamInfo(site, {'query'}) self.assertIsEmpty(pi) pi._init()
self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo) - self.assertIn('pageset', pi._paraminfo) self.assertIn('query', pi._paraminfo)
- pageset_generators_param = pi.parameter('pageset', 'generator') query_generators_param = pi.parameter('query', 'generator') - - self.assertEqual(pageset_generators_param, query_generators_param) + self.assertIn('submodules', query_generators_param) + self.assertEqual(query_generators_param['submoduleparamprefix'], 'g') + for submodule, query in query_generators_param['submodules'].items(): + self.assertEqual('query+' + submodule, query)
def test_with_module_info(self): """Test requesting the module info.""" @@ -154,7 +144,7 @@
self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo) - self.assertLength(pi, 1 + len(pi.preloaded_modules)) + self.assertLength(pi, 1 + len(pi._preloaded_modules))
self.assertEqual(pi['info']['prefix'], 'in')
@@ -178,7 +168,7 @@
self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo) - self.assertLength(pi, 1 + len(pi.preloaded_modules)) + self.assertLength(pi, 1 + len(pi._preloaded_modules))
self.assertEqual(pi['revisions']['prefix'], 'rv')
@@ -204,7 +194,7 @@ self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo)
- self.assertLength(pi, 2 + len(pi.preloaded_modules)) + self.assertLength(pi, 2 + len(pi._preloaded_modules))
def test_with_invalid_module(self): """Test requesting different kind of invalid modules.""" @@ -225,7 +215,7 @@ self.assertIn('main', pi._paraminfo) self.assertIn('paraminfo', pi._paraminfo)
- self.assertLength(pi, pi.preloaded_modules) + self.assertLength(pi, pi._preloaded_modules)
def test_submodules(self): """Test another module apart from query having submodules.""" @@ -233,7 +223,7 @@ self.assertFalse(pi._modules) pi.fetch(['query']) self.assertIn('query', pi._modules) - self.assertIsInstance(pi._modules['query'], frozenset) + self.assertIsInstance(pi._modules['query'], set) self.assertIn('revisions', pi._modules['query']) self.assertEqual(pi.submodules('query'), pi.query_modules) for mod in pi.submodules('query', True): @@ -244,7 +234,7 @@ with patch.object(pywikibot, 'warning') as w, \ self.assertRaises(KeyError): pi.__getitem__('query+foobar') - # The warning message may be different with older MW versions. + self.assertIn('API warning (paraminfo): ', w.call_args[0][0])
with self.assertRaises(KeyError): @@ -277,33 +267,6 @@ self.assertEqual(mod, pi[mod]['path']) self.assertEqual(value, '')
- @patch.object(pywikibot, 'warning') # ignore several warnings - def test_old_mode(self, *args): - """Test the old mode explicitly.""" - site = self.get_site() - pi = api.ParamInfo(site, modules_only_mode=False) - pi.fetch(['info']) - self.assertIn('query+info', pi._paraminfo) - - self.assertIn('main', pi._paraminfo) - self.assertIn('paraminfo', pi._paraminfo) - - self.assertLength(pi, 1 + len(pi.preloaded_modules)) - - self.assertIn('query+revisions', pi.prefix_map) - - def test_new_mode(self): - """Test the new modules-only mode explicitly.""" - site = self.get_site() - pi = api.ParamInfo(site, modules_only_mode=True) - pi.fetch(['info']) - self.assertIn('query+info', pi._paraminfo) - self.assertIn('main', pi._paraminfo) - self.assertIn('paraminfo', pi._paraminfo) - - self.assertLength(pi, 1 + len(pi.preloaded_modules)) - self.assertIn('query+revisions', pi.prefix_map) -
class TestOtherSubmodule(TestCase):
@@ -322,7 +285,7 @@ self.assertIn('flow', pi._modules) other_modules = set() for modules in pi._modules.values(): - self.assertIsInstance(modules, frozenset) + self.assertIsInstance(modules, set) other_modules |= modules
other_modules -= pi.action_modules diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py index dd0ec6e..7ad0dd3 100755 --- a/tests/dry_api_tests.py +++ b/tests/dry_api_tests.py @@ -312,62 +312,72 @@
"""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' - } - - edit_action_param_data = { - 'name': 'edit', - 'path': 'edit' + # https://en.wikipedia.org/w/api.php?action=paraminfo&modules=query+info%7... + paraminfodata = { + 'paraminfo': { + 'modules': [ + { + 'name': 'info', + 'classname': 'ApiQueryInfo', + 'path': 'query+info', + 'group': 'prop', + 'prefix': 'in', + 'parameters': [ + { + 'index': 1, + 'name': 'prop', + 'type': [ + 'displaytitle' + 'notificationtimestamp', + 'protection', + 'subjectid', + 'talkid', + 'url', + 'watched', + 'watchers', + 'preload', + 'readable', + ], + 'multi': '', + 'lowlimit': 50, + 'highlimit': 500, + 'limit': 50, + 'deprecatedvalues': [ + 'preload', + 'readable' + ] + } + ] + }, + { + 'name': 'tokens', + 'classname': 'ApiQueryTokens', + 'path': 'query+tokens', + 'group': 'meta', + 'prefix': '', + 'parameters': [ + { + 'index': 1, + 'name': 'type', + 'type': [ + 'csrf', + 'deleteglobalaccount', + 'login', + 'patrol', + 'rollback', + 'userrights', + 'watch' + ], + 'default': 'csrf', + 'multi': '', + 'limit': 50, + 'lowlimit': 50, + 'highlimit': 500 + } + ] + } + ] + } }
def setUp(self): @@ -380,86 +390,43 @@ site._paraminfo._paraminfo[mod] = {} site._paraminfo._action_modules = frozenset(['edit']) site._paraminfo._modules = {'query': frozenset(['info'])} + data = site._paraminfo.normalize_paraminfo(self.paraminfodata) + site._paraminfo._paraminfo.update(data)
- def test_new_format(self): + def test_format(self): """Test using a dummy formatted in the new modules-only mode.""" 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, - self.edit_action_param_data, - ] - } - }) - - pi._paraminfo.update(data) - self.assertIn('edit', pi._paraminfo) self.assertIn('query+info', pi._paraminfo) + self.assertIn('query+tokens', pi._paraminfo) self.assertIn('edit', pi) self.assertIn('info', pi) - - def test_old_format(self): - """Test using a dummy formatted in the old mode.""" - 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': [self.edit_action_param_data], - } - }) - - pi._paraminfo.update(data) - self.assertIn('edit', pi._paraminfo) - self.assertIn('query+info', pi._paraminfo) - self.assertIn('edit', pi) - self.assertIn('info', pi) + self.assertIn('tokens', pi)
def test_attribute(self): """Test using __getitem__.""" pi = self.get_site()._paraminfo - # Reset it to the complete set of possible keys defined in the class - pi.paraminfo_keys = ParamInfo.paraminfo_keys + self.assertEqual(pi._paraminfo['query+info']['group'], 'prop') + self.assertEqual(pi['query+info']['prefix'], 'in')
- data = pi.normalize_paraminfo({ - 'paraminfo': { - 'querymodules': [self.prop_info_param_data], - } - }) - - pi._paraminfo.update(data) - - self.assertEqual(pi._paraminfo['query+info']['querytype'], 'prop') - self.assertEqual(pi['info']['prefix'], 'in') - - def test_parameter(self): - """Test parameter() method.""" + def test_info_parameter(self): + """Test parameter() method with 'info' module.""" 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') + param = pi.parameter('info', 'prop') self.assertIsInstance(param, dict) - - self.assertEqual(param['name'], 'token') - self.assertIn('deprecated', param) - + self.assertEqual(param['name'], 'prop') + self.assertIn('deprecatedvalues', param) self.assertIsInstance(param['type'], list) - self.assertIn('email', param['type']) + self.assertIn('preload', param['type']) + + def test_tokens_parameter(self): + """Test parameter() method with 'tokens' module.""" + pi = self.get_site()._paraminfo + param = pi.parameter('tokens', 'type') + self.assertIsInstance(param, dict) + self.assertEqual(param['name'], 'type') + self.assertIn('default', param) + self.assertIsInstance(param['type'], list) + self.assertIn('login', param['type'])
class QueryGenTests(DefaultDrySiteTestCase): diff --git a/tests/paraminfo_tests.py b/tests/paraminfo_tests.py index f5877f4..608b2ec 100755 --- a/tests/paraminfo_tests.py +++ b/tests/paraminfo_tests.py @@ -83,23 +83,14 @@
def test_watchlist_show_flags(self): """Test watchlist show flags.""" - types = ['minor', 'bot', 'anon', 'patrolled'] - if self.site.mw_version >= '1.24': - types.append('unread') - + types = ['minor', 'bot', 'anon', 'patrolled', 'unread'] known = types + [f'!{item}' for item in types]
self._check_param_subset(self.site, 'query+watchlist', 'show', known)
def test_watchlist_type(self): """Test watchlist type.""" - known = ['edit', 'external', 'new', 'log'] - - mw_ver = self.site.mw_version - - if mw_ver.version >= (1, 27) \ - and (mw_ver >= '1.27.0-wmf.4' or mw_ver.suffix == 'alpha'): - known.append('categorize') + known = ['categorize', 'edit', 'external', 'log', 'new']
self._check_param_values(self.site, 'query+watchlist', 'type', known)
@@ -148,20 +139,13 @@
def test_content_model(self): """Test content model.""" - base = [ - 'wikitext', - 'javascript', - 'css', - 'text', - ] + base = ['css', 'javascript', 'json', 'text', 'wikitext'] wmf = [ 'MassMessageListContent', 'SecurePoll', 'Scribunto', 'JsonSchema', ] - if self.site.mw_version >= '1.24': - base.append('json')
self._check_param_subset(self.site, 'edit', 'contentmodel', base) self._check_param_subset(self.site, 'parse', 'contentmodel', base)