jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/803388?usp=email )
Change subject: [feature] Enable all prop informations with usercontribs and User.contribs ......................................................................
[feature] Enable all prop informations with usercontribs and User.contribs
see API:Usercontribs
- add a new 'prop' parameter to APISite.usercontribs() to enable all prop; they can be given as iterable or as a string with '|' as delimiter. This parameter is keyword only. - add formatversion parameter to this method to enable the new format version - enable 'size' prop by default in usercontribs - rename 'top_only' parameter to 'top' - drop deprecated uctoponly api parameter for 'top' and use ucshow instead; 'top' is None by default which gives the same result as uctoponly = False - add a new Contribution mapping which uses the collection.DataRecord - add a new method 'User.contribs' which iterates Contribution items - add a new UnexpectedAPIDataError for API response validation - use user.contribs in UserContributionsGenerator - update documentation
Bug: T308961 Change-Id: Id80df6dd27eec79cb7dfdca326400659761995a8 --- M pywikibot/exceptions.py M pywikibot/page/__init__.py M pywikibot/page/_user.py M pywikibot/pagegenerators/_generators.py M pywikibot/site/_generators.py 5 files changed, 177 insertions(+), 38 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py index 3ff630c..9fdb2b4 100644 --- a/pywikibot/exceptions.py +++ b/pywikibot/exceptions.py @@ -57,9 +57,10 @@ +-- ApiTimeoutError | +-- MaxlagTimeoutError +-- TranslationError + +-- UnexpectedAPIDataError (ValueError) + +-- UnknownExtensionError (NotImplementedError) +-- UserRightsError | +-- HiddenKeyError (KeyError) - +-- UnknownExtensionError (NotImplementedError) +-- VersionParseError +-- WikiBaseError +-- CoordinateGlobeUnknownError (NotImplementedError) @@ -84,6 +85,7 @@ - SectionError: The section specified by # does not exist - TranslationError: no language translation found, i18n/l10n message not available + - UnexpectedAPIDataError: API data contains unexpected fields or values - UnknownExtensionError: Extension is not defined for this site - UserRightsError: insufficient rights for requested action - VersionParseError: failed to parse version information @@ -177,6 +179,9 @@ .. version-changed:: 8.1 ``Server414Error`` class is deprecated; use :class:`Client414Error` instead. + +.. version-changed:: 11.6 + :exc:`UnexpectedAPIDataError` was added. """ from __future__ import annotations
@@ -450,6 +455,14 @@ """Family is not registered."""
+class UnexpectedAPIDataError(Error, ValueError): + + """Raised when API data contains unexpected fields or values. + + .. version-added:: 11.6 + """ + + class UnknownExtensionError(Error, NotImplementedError):
"""Extension is not defined.""" diff --git a/pywikibot/page/__init__.py b/pywikibot/page/__init__.py index a20225a..5115ca0 100644 --- a/pywikibot/page/__init__.py +++ b/pywikibot/page/__init__.py @@ -14,7 +14,7 @@ from pywikibot.page._links import BaseLink, Link, SiteLink, html2unicode from pywikibot.page._page import Page from pywikibot.page._revision import Revision -from pywikibot.page._user import User +from pywikibot.page._user import Contribution, User from pywikibot.page._wikibase import ( Claim, ItemPage, @@ -50,6 +50,7 @@ 'FileInfo', 'WikibaseEntity', 'MediaInfo', + 'Contribution', 'Revision', 'html2unicode', ) diff --git a/pywikibot/page/_user.py b/pywikibot/page/_user.py index a2518a5..6f9d486 100644 --- a/pywikibot/page/_user.py +++ b/pywikibot/page/_user.py @@ -15,15 +15,37 @@ AutoblockUserError, NoRenameTargetError, NotEmailableError, + UnexpectedAPIDataError, UserRightsError, ) from pywikibot.page._links import Link from pywikibot.page._page import Page from pywikibot.page._revision import Revision +from pywikibot.time import Timestamp from pywikibot.tools import is_ip_address, is_ip_network +from pywikibot.tools.collections import DataRecord
-__all__ = ('User', ) +__all__ = ('Contribution', 'User') + + +class Contribution(DataRecord): + + """A structure holding information about a user contribution. + + In addition to the API result, it provides the ``site`` and ``page`` + for the current Site and Page object of the contribution. + + .. version-added:: 11.6 + """ + + @staticmethod + def normalize(data: dict[str, Any]) -> None: + """Upcast dictionary values.""" + if 'timestamp' in data: + data['timestamp'] = Timestamp.fromISOformat(data['timestamp']) + if 'title' in data: + data['page'] = Page(data['site'], data['title'], data['ns'])
class User(Page): @@ -488,7 +510,8 @@
Each tuple is composed of a pywikibot.Page object, the revision id, the edit timestamp and the comment. Pages returned are not - guaranteed to be unique. + guaranteed to be unique. Use :meth:`contribs` if you need + additional revision information.
Example:
@@ -506,8 +529,18 @@ >>> contrib[3] ''
- .. seealso:: :meth:`Site.usercontribs() - <pywikibot.site._generators.GeneratorsMixin.usercontribs>` + .. version-changed:: 3.0.20200609 + The *showMinor* parameter was renamed to *minor*. + .. version-changed:: 11.6 + The keyword *top_only* was renamed to *top*. This parameter + now accepts ``None`` to iterate both latest and non-latest + contributions. ``False`` now iterates only non-latest + contributions. Default is ``None``. + .. seealso:: + - :meth:`contribs` + - :meth:`Site.usercontribs() + <pywikibot.site._generators.GeneratorsMixin.usercontribs>` + - :api:`Usercontribs`
:param total: Limit result to this number of pages :keyword start: Iterate contributions starting at this Timestamp @@ -517,19 +550,79 @@ :type namespaces: Iterable of str or Namespace key, or a single instance of those types. May be a '|' separated list of namespace identifiers. - :keyword showMinor: If True, iterate only minor edits; if False and + :keyword minor: If True, iterate only minor edits; if False and not None, iterate only non-minor edits (default: iterate both) - :keyword top_only: If True, iterate only edits which are the latest - revision (default: False) + :param top: if ``True``, iterate only edits which are the latest + revision; if ``False``, do not iterate last revision edits; + ``None`` to iterate both (default: ``None``) :return: Tuple of pywikibot.Page, revid, pywikibot.Timestamp, comment """ + prop = ('comment', 'ids', 'timestamp', 'title') + for c in self.contribs(total=total, prop=prop, **kwargs): + yield c.page, c.revid, c.timestamp, c.comment # type: ignore[attr-defined] # noqa: E501 + + def contribs(self, **kwargs) -> Generator[Contribution]: + """Yield :class:`Contribution` items describing this user edits. + + Refer :meth:`APISite.usercontribs() + <pywikibot.site._generators.GeneratorsMixin.usercontribs>` + method for for keyword parameters except of `user`and `userprefix`. + + .. version-added:: 11.6 + + Usage: + + >>> site = pywikibot.Site('wikipedia:test') + >>> user = pywikibot.User(site, 'Pywikibot-oauth') + >>> prop = ['title', 'tags', 'flags'] + >>> uc = list(user.contribs(total=8, reverse=True, prop=prop)) + >>> contrib = uc[-1] + >>> contrib.user == user + True + >>> str(contrib.site) + 'wikipedia:test' + >>> contrib.title + 'User:Pywikibot-oauth/edit test' + >>> contrib.page.title() == contrib.title + True + >>> contrib.top + False + >>> contrib.tags # attribute access + ['OAuth CID: 281'] + >>> contrib['tags'] # key access + ['OAuth CID: 281'] + + .. seealso:: + - :meth:`contributions` + - :meth:`Site.usercontribs() + <pywikibot.site._generators.GeneratorsMixin.usercontribs>` + - :api:`Usercontribs` + + :keyword start: Iterate contributions starting at this Timestamp + :keyword end: Iterate contributions ending at this Timestamp + :keyword reverse: Iterate oldest contributions first (default: + newest) + :keyword namespaces: Only iterate pages in these namespaces + :keyword minor: If ``True``, iterate only minor edits; if ``False`` + and not ``None``, iterate only non-minor edits (default: + iterate both) + :keyword total: Limit result to this number of pages + :keyword top: if ``True``, iterate only edits which are the latest + revision; if ``False``, do not iterate last revision edits; + ``None`` to iterate both (default: ``None``) + :keyword prop: Include additional pieces of information. Refer + :api:`Usercontribs` for the elements and the default setting. + :return: For each entry return a tuple of Page, Revision + """ for contrib in self.site.usercontribs( - user=self.username, total=total, **kwargs): - ts = pywikibot.Timestamp.fromISOformat(contrib['timestamp']) - yield (Page(self.site, contrib['title'], contrib['ns']), - contrib['revid'], - ts, - contrib.get('comment')) + user=self.username, formatversion=2, **kwargs): + if {'site', 'page'} & contrib.keys() or 'user' not in contrib: + raise UnexpectedAPIDataError( + "API response contains reserved keys 'site' or 'page' " + "or 'user' is missing" + ) + contrib.pop('user') + yield Contribution(site=self.site, user=self, **contrib)
@property def first_edit( diff --git a/pywikibot/pagegenerators/_generators.py b/pywikibot/pagegenerators/_generators.py index a53d39c..456b262 100644 --- a/pywikibot/pagegenerators/_generators.py +++ b/pywikibot/pagegenerators/_generators.py @@ -546,8 +546,10 @@ pywikibot.warning( f'User "{user.username}" does not exist on site "{site}".')
- gen = (contrib[0] for contrib in user.contributions( - namespaces=namespaces, total=total)) + gen = ( + contrib.page for contrib in user.contribs(namespaces=namespaces, + total=total, prop='title') + ) if _filter_unique: return _filter_unique(gen) return gen diff --git a/pywikibot/site/_generators.py b/pywikibot/site/_generators.py index c4b4851..44e2b61 100644 --- a/pywikibot/site/_generators.py +++ b/pywikibot/site/_generators.py @@ -28,11 +28,18 @@ ) from pywikibot.site._decorators import need_right from pywikibot.site._namespace import NamespaceArgType -from pywikibot.tools import deprecate_arg, deprecated_signature, is_ip_address +from pywikibot.tools import ( + deprecate_arg, + deprecated_args, + deprecated_signature, + is_ip_address, +) from pywikibot.tools.itertools import filter_unique
if typing.TYPE_CHECKING: + from datetime import datetime + from pywikibot.data.api import ParamInfo, Request from pywikibot.site._namespace import NamespacesDict, SingleNamespaceType from pywikibot.site._tokenwallet import TokenWallet @@ -1630,17 +1637,21 @@ total=total, g_content=content, parameters=parameters)
+ @deprecated_args(top_only='top') # since 11.6.0 def usercontribs( self, user: str | None = None, userprefix: str | None = None, - start=None, - end=None, + start: pywikibot.time.Timestamp | datetime | str | None = None, + end: pywikibot.time.Timestamp | datetime | str | None = None, reverse: bool = False, namespaces: NamespaceArgType = None, minor: bool | None = None, total: int | None = None, - top_only: bool = False, + top: bool | None = None, + *, + prop: Iterable[str] | str | None = None, + formatversion: int = 1 ) -> Iterable[dict[str, Any]]: """Iterate contributions by a particular user.
@@ -1650,20 +1661,36 @@ - :api:`Usercontribs` - :meth:`pywikibot.User.contributions`
+ .. version-changed:: 3.0.20200609 + The *showMinor* parameter was renamed to *minor*. + .. version-changed:: 11.6 + The *prop* and *formatversion* parameter were added. The + *top_only* was renamed to *top*. This parameter now accepts + ``None`` to iterate both latest and non-latest contributions. + ``False`` now iterates only non-latest contributions. Default + is ``None``. The ``size`` property is included by default. + :param user: Iterate contributions by this user (name or IP) - :param userprefix: Iterate contributions by all users whose names - or IPs start with this substring + :param userprefix: Iterate contributions by all users whose + names or IPs start with this substring :param start: Iterate contributions starting at this Timestamp :param end: Iterate contributions ending at this Timestamp - :param reverse: Iterate oldest contributions first (default: newest) + :param reverse: Iterate oldest contributions first (default: + newest) :param namespaces: Only iterate pages in these namespaces - :param minor: If True, iterate only minor edits; if False and - not None, iterate only non-minor edits (default: iterate both) + :param minor: If ``True``, iterate only minor edits; if ``False`` + and not ``None``, iterate only non-minor edits (default: + iterate both) :param total: Limit result to this number of pages - :param top_only: If True, iterate only edits which are the latest - revision (default: False) - :raises pywikibot.exceptions.Error: either user or userprefix must be - non-empty + :param top: if ``True``, iterate only edits which are the latest + revision; if ``False``, do not iterate last revision edits; + ``None`` to iterate both (default: ``None``) + :param prop: Include additional pieces of information. Refer + :api:`Usercontribs` for the elements and the default setting. + :param formatversion: The API format version to use for the + response. (``1`` by deault) + :raises pywikibot.exceptions.Error: either user or userprefix + must be non-empty :raises KeyError: A namespace identifier was not resolved :raises TypeError: A namespace identifier has an inappropriate type such as NoneType or bool @@ -1675,22 +1702,25 @@ if start and end: self.assert_valid_iter_params('usercontribs', start, end, reverse)
- ucgen = self._generator(api.ListGenerator, type_arg='usercontribs', - ucprop='ids|title|timestamp|comment|flags', - namespaces=namespaces, - total=total, uctoponly=top_only) + ucgen = self._generator( + api.ListGenerator, + type_arg='usercontribs', + namespaces=namespaces, + total=total, + ucprop=prop, + ucstart=start, + ucend=end, + formatversion=formatversion + ) if user: ucgen.request['ucuser'] = user if userprefix: ucgen.request['ucuserprefix'] = userprefix - if start is not None: - ucgen.request['ucstart'] = str(start) - if end is not None: - ucgen.request['ucend'] = str(end) if reverse: ucgen.request['ucdir'] = 'newer' option_set = api.OptionSet(self, 'usercontribs', 'show') option_set['minor'] = minor + option_set['top'] = top ucgen.request['ucshow'] = option_set return ucgen
pywikibot-commits@lists.wikimedia.org