jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1187941?usp=email )
Change subject: IMPR: Retrieve siteinfo with formatversion 2
......................................................................
IMPR: Retrieve siteinfo with formatversion 2
- retrieve siteinfo with formatversion 2
- add '*' keys to 'namespaces', 'languages', 'namespacealiases'
and 'skins' for backward compatibility
- 'thumblimits', 'imagelimits' and 'magiclinks' entries of the
'general' property are normalized to lists for easier use and to
match the format used in formatversion 1
- update Requests._handle_warnings to catch formatversion 2 warnings
- update Requests.submit to handle formatversion 2 errors
- update siteinfo usage
- update DummySiteinfo and DrySite
- update backports
- import Dict and List from backports
- cast types in Siteinfo._post_process function
- update documentation
Bug: T404301
Change-Id: I29a515d60330c6889a2cb6ceec4df58a417e5c72
---
M pywikibot/backports.py
M pywikibot/data/api/_requests.py
M pywikibot/site/_apisite.py
M pywikibot/site/_datasite.py
M pywikibot/site/_namespace.py
M pywikibot/site/_siteinfo.py
M tests/utils.py
7 files changed, 125 insertions(+), 97 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/backports.py b/pywikibot/backports.py
index a3b64f7..25b174b 100644
--- a/pywikibot/backports.py
+++ b/pywikibot/backports.py
@@ -53,6 +53,7 @@
Generator,
Iterable,
Iterator,
+ List,
Mapping,
Match,
Pattern,
@@ -70,6 +71,7 @@
)
from re import Match, Pattern
Dict = dict # type: ignore[misc]
+ List = list # type: ignore[misc]
if PYTHON_VERSION < (3, 9, 2):
diff --git a/pywikibot/data/api/_requests.py b/pywikibot/data/api/_requests.py
index a49bc53..035f65c 100644
--- a/pywikibot/data/api/_requests.py
+++ b/pywikibot/data/api/_requests.py
@@ -843,6 +843,10 @@
.. versionchanged:: 7.2
Return True to retry the current request and False to resume.
+ .. versionchanged:: 10.5
+ Handle warnings of formatversion 2.
+
+ .. seealso:: :api:`Errors and warnings`
:meta public:
"""
@@ -853,7 +857,9 @@
for mod, warning in result['warnings'].items():
if mod == 'info':
continue
- if '*' in warning:
+ if 'warnings' in warning: # formatversion 2
+ text = warning['warnings']
+ elif '*' in warning: # formatversion 1
text = warning['*']
elif 'html' in warning:
# bug T51978
@@ -1066,9 +1072,13 @@
assert key not in error
error[key] = result[key]
- if '*' in error:
- # help text returned
- error['help'] = error.pop('*')
+ # help text returned
+ # see also: https://www.mediawiki.org/wiki/API:Errors_and_warnings
+ if 'docref' in error:
+ error['help'] = error.pop('docref') # formatversion 2
+ elif '*' in error:
+ error['help'] = error.pop('*') # formatversion 1
+
code = error.setdefault('code', 'Unknown')
info = error.setdefault('info', None)
diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py
index 627d4d2..ec61c45 100644
--- a/pywikibot/site/_apisite.py
+++ b/pywikibot/site/_apisite.py
@@ -841,7 +841,7 @@
:raises ValueError: missing "$1" placeholder
"""
- path = self.siteinfo['general']['articlepath']
+ path = self.siteinfo['articlepath']
if '$1' not in path:
raise ValueError(
f'Invalid article path "{path}": missing "$1" placeholder')
@@ -864,7 +864,7 @@
'ca': "(?:[a-zàèéíòóúç·ïü]|'(?!'))*",
'kaa': "(?:[a-zıʼ’“»]|'(?!'))*",
}
- linktrail = self.siteinfo['general']['linktrail']
+ linktrail = self.siteinfo['linktrail']
if linktrail == '/^()(.*)$/sD': # empty linktrail
return ''
@@ -1183,10 +1183,10 @@
for nsdata in self.siteinfo.get('namespaces', cache=False).values():
ns = nsdata.pop('id')
if ns == 0:
- canonical_name = nsdata.pop('*')
+ custom_name = canonical_name = nsdata.pop('name')
custom_name = canonical_name
else:
- custom_name = nsdata.pop('*')
+ custom_name = nsdata.pop('name')
canonical_name = nsdata.pop('canonical')
default_case = Namespace.default_case(ns)
@@ -1199,16 +1199,16 @@
namespace = Namespace(ns, canonical_name, custom_name, **nsdata)
_namespaces[ns] = namespace
- for item in self.siteinfo.get('namespacealiases'):
+ for item in self.siteinfo['namespacealiases']:
ns = int(item['id'])
try:
namespace = _namespaces[ns]
except KeyError:
pywikibot.warning('Broken namespace alias "{}" (id: {}) on {}'
- .format(item['*'], ns, self))
+ .format(item['alias'], ns, self))
else:
- if item['*'] not in namespace:
- namespace.aliases.append(item['*'])
+ if item['alias'] not in namespace:
+ namespace.aliases.append(item['alias'])
return _namespaces
@@ -3122,7 +3122,7 @@
>>> site.is_uploaddisabled()
True
"""
- return not self.siteinfo.get('general')['uploadsenabled']
+ return not self.siteinfo['uploadsenabled']
def stash_info(
self,
diff --git a/pywikibot/site/_datasite.py b/pywikibot/site/_datasite.py
index cf09ee3..9b3492e 100644
--- a/pywikibot/site/_datasite.py
+++ b/pywikibot/site/_datasite.py
@@ -138,35 +138,32 @@
raise NoWikibaseEntityError(entity)
@property
- def sparql_endpoint(self):
+ def sparql_endpoint(self) -> str | None:
"""Return the sparql endpoint url, if any has been set.
:return: sparql endpoint url
- :rtype: str|None
"""
- return self.siteinfo['general'].get('wikibase-sparql')
+ return self.siteinfo.get('wikibase-sparql')
@property
- def concept_base_uri(self):
+ def concept_base_uri(self) -> str:
"""Return the base uri for concepts/entities.
:return: concept base uri
- :rtype: str
"""
- return self.siteinfo['general']['wikibase-conceptbaseuri']
+ return self.siteinfo['wikibase-conceptbaseuri']
- def geo_shape_repository(self):
+ def geo_shape_repository(self) -> DataSite | None:
"""Return Site object for the geo-shapes repository e.g. commons."""
- url = self.siteinfo['general'].get('wikibase-geoshapestoragebaseurl')
+ url = self.siteinfo.get('wikibase-geoshapestoragebaseurl')
if url:
return pywikibot.Site(url=url, user=self.username())
return None
- def tabular_data_repository(self):
+ def tabular_data_repository(self) -> DataSite | None:
"""Return Site object for the tabular-data repository e.g. commons."""
- url = self.siteinfo['general'].get(
- 'wikibase-tabulardatastoragebaseurl')
+ url = self.siteinfo.get('wikibase-tabulardatastoragebaseurl')
if url:
return pywikibot.Site(url=url, user=self.username())
diff --git a/pywikibot/site/_namespace.py b/pywikibot/site/_namespace.py
index 00de8fd..f82c03b 100644
--- a/pywikibot/site/_namespace.py
+++ b/pywikibot/site/_namespace.py
@@ -1,6 +1,6 @@
"""Objects representing Namespaces of MediaWiki site."""
#
-# (C) Pywikibot team, 2008-2024
+# (C) Pywikibot team, 2008-2025
#
# Distributed under the terms of the MIT license.
#
@@ -323,11 +323,7 @@
class NamespacesDict(Mapping):
- """An immutable dictionary containing the Namespace instances.
-
- It adds a deprecation message when called as the 'namespaces'
- property of APISite was callable.
- """
+ """An immutable dictionary containing the Namespace instances."""
def __init__(self, namespaces) -> None:
"""Create new dict using the given namespaces."""
diff --git a/pywikibot/site/_siteinfo.py b/pywikibot/site/_siteinfo.py
index 389947a..46c6b9b 100644
--- a/pywikibot/site/_siteinfo.py
+++ b/pywikibot/site/_siteinfo.py
@@ -11,9 +11,10 @@
import re
from collections.abc import Container
from contextlib import suppress
-from typing import TYPE_CHECKING, Any, Literal
+from typing import TYPE_CHECKING, Any, Literal, cast
import pywikibot
+from pywikibot.backports import Dict, List
from pywikibot.exceptions import APIError
from pywikibot.tools.collections import EMPTY_DEFAULT
@@ -24,43 +25,43 @@
class Siteinfo(Container):
- """A 'dictionary' like container for siteinfo.
+ """A dictionary-like container for siteinfo.
This class queries the server to get the requested siteinfo
- property. Optionally it can cache this directly in the instance so
- that later requests don't need to query the server.
+ property. Results can be cached in the instance to avoid repeated
+ queries.
- All values of the siteinfo property 'general' are directly
- available.
+ All values of the 'general' property are directly available.
+
+ .. versionchanged:: 10.5
+ formatversion 2 is used for API calls.
+
+ .. admonition:: Compatibility note
+ :class: note
+
+ For formatversion 2, some siteinfo data structures differ from
+ version 1. Fallback '*' keys are added in the data structure for
+ 'namespaces', 'languages', 'namespacealiases' and 'skins'
+ properties for backwards compatibility. These fallbacks may be
+ removed in future versions of Pywikibot.
+
+ The 'thumblimits', 'imagelimits' and 'magiclinks' entries of the
+ 'general' property are normalized to lists for easier use and to
+ match the format used in formatversion 1. For example:
+
+ :code:`'thumblimits': [120, 150, 180, 200, 220, 250, 300, 400]`
+
+ .. deprecated:: 10.5
+ Accessing the fallback '*' keys in 'languages', 'namespaces',
+ 'namespacealiases', and 'skins' properties are deprecated and
+ will be removed in a future release of Pywikibot.
+
+ .. seealso:: :api:`siteinfo`
"""
WARNING_REGEX = re.compile(r'Unrecognized values? for parameter '
r'["\']siprop["\']: (.+?)\.?')
- # Until we get formatversion=2, we have to convert empty-string properties
- # into booleans so they are easier to use.
- BOOLEAN_PROPS = {
- 'general': [
- 'imagewhitelistenabled',
- 'langconversion',
- 'titleconversion',
- 'rtl',
- 'readonly',
- 'writeapi',
- 'variantarticlepath',
- 'misermode',
- 'uploadsenabled',
- ],
- 'namespaces': [ # for each namespace
- 'subpages',
- 'content',
- 'nonincludable',
- ],
- 'magicwords': [ # for each magicword
- 'case-sensitive',
- ],
- }
-
def __init__(self, site: APISite) -> None:
"""Initialize Siteinfo for a given site with an empty cache."""
self._site = site
@@ -81,35 +82,40 @@
Modifies *data* in place.
+ .. versionchanged:: 10.5
+ Modify *data* for formatversion 1 compatibility and easier
+ to use lists.
+
:param prop: The siteinfo property name (e.g., 'general',
'namespaces', 'magicwords')
:param data: The raw data returned from the server
+
+ :meta public:
"""
# Be careful with version tests inside this here as it might need to
# query this method to actually get the version number
- # Convert boolean props from empty strings to actual boolean values
- if prop not in Siteinfo.BOOLEAN_PROPS:
- return
-
- bool_props = Siteinfo.BOOLEAN_PROPS[prop]
if prop == 'general':
- # Direct properties of 'general'
- for p in bool_props:
- data[p] = p in data
- else:
- # 'namespaces' (dict) or 'magicwords' (list of dicts)
- items: list[dict[str, Any]]
- if isinstance(data, dict):
- items = list(data.values())
- elif isinstance(data, list):
- items = data
- else:
- return # unexpected format
-
- for item in items:
- for p in bool_props:
- item[p] = p in item
+ data = cast(Dict[str, Any], data)
+ for key in 'thumblimits', 'imagelimits':
+ data[key] = list(data[key].values())
+ data['magiclinks'] = [k for k, v in data['magiclinks'].items()
+ if v]
+ elif prop == 'namespaces':
+ data = cast(Dict[str, Any], data)
+ for ns_info in data.values():
+ ns_info['*'] = ns_info['name']
+ elif prop in ('languages', 'namespacealiases'):
+ data = cast(List[Dict[str, Any]], data)
+ for ns_info in data:
+ key = 'name' if 'name' in ns_info else 'alias'
+ ns_info['*'] = ns_info[key]
+ elif prop == 'skins':
+ data = cast(List[Dict[str, Any]], data)
+ for ns_info in data:
+ ns_info['*'] = ns_info['name']
+ for key in 'default', 'unusable':
+ ns_info.setdefault(key, False)
def _get_siteinfo(self, prop, expiry) -> dict:
"""Retrieve one or more siteinfo properties from the server.
@@ -144,7 +150,10 @@
expiry=pywikibot.config.API_config_expiry
if expiry is False else expiry,
parameters={
- 'action': 'query', 'meta': 'siteinfo', 'siprop': props,
+ 'action': 'query',
+ 'meta': 'siteinfo',
+ 'siprop': props,
+ 'formatversion': 2,
}
)
@@ -168,7 +177,7 @@
return results
raise
- result = {}
+ result: dict[str, tuple[Any, datetime.datetime | Literal[False]]] = {}
if invalid_properties:
for invalid_prop in invalid_properties:
result[invalid_prop] = (EMPTY_DEFAULT, False)
@@ -334,18 +343,27 @@
return True
- def __contains__(self, key: str) -> bool:
- """Return whether the value is in Siteinfo container.
+ def __contains__(self, key: object) -> bool:
+ """Check whether the given key is present in the Siteinfo container.
+
+ This method implements the Container protocol and allows usage
+ like `key in container`.Only string keys are valid. Non-string
+ keys always return False.
.. versionchanged:: 7.1
Previous implementation only checked for cached keys.
- """
- try:
- self[key]
- except KeyError:
- return False
- return True
+ :param key: The key to check for presence. Should be a string.
+ :return: True if the key exists in the container, False otherwise.
+
+ :meta public:
+ """
+ if isinstance(key, str):
+ with suppress(KeyError):
+ self[key]
+ return True
+
+ return False
def is_recognised(self, key: str) -> bool | None:
"""Return if 'key' is a valid property name.
diff --git a/tests/utils.py b/tests/utils.py
index 44d8227..cf5860f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -261,16 +261,21 @@
self._cache[key] = (value, False)
def get(self, key, get_default=True, cache=True, expiry=False):
- """Return dry data."""
+ """Return dry cached data or default value."""
# Default values are always expired, so only expiry=False doesn't force
# a reload
force = expiry is not False
- if not force and key in self._cache:
- loaded = self._cache[key]
- if not loaded[1] and not get_default:
+ if not force and (key in self._cache or 'general' in self._cache):
+ try:
+ value, is_default = self._cache[key]
+ except KeyError:
+ value, is_default = self._cache['general']
+ value = value[key]
+
+ if not is_default and not get_default:
raise KeyError(key)
- return loaded[0]
+ return value
if get_default:
default = EMPTY_DEFAULT
@@ -343,7 +348,7 @@
self._siteinfo._cache['case'] = (
'case-sensitive' if self.family.name == 'wiktionary' else
'first-letter', True)
- self._siteinfo._cache['mainpage'] = 'Main Page'
+ self._siteinfo._cache['mainpage'] = ('Main Page', True)
extensions = []
if self.family.name == 'wikisource':
extensions.append({'name': 'ProofreadPage'})
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1187941?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I29a515d60330c6889a2cb6ceec4df58a417e5c72
Gerrit-Change-Number: 1187941
Gerrit-PatchSet: 14
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]