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


--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/803388?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: Id80df6dd27eec79cb7dfdca326400659761995a8
Gerrit-Change-Number: 803388
Gerrit-PatchSet: 19
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: JJMC89 <[email protected]>
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to