jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1203490?usp=email )

Change subject: Add new function review_revision() to  pywikibot/site/ 
_apisite.py
......................................................................

Add new function review_revision() to  pywikibot/site/ _apisite.py

Implements the FlaggedRevs `action=review` API in Pywikibot
so that bots can approve, tag, or un-approve revisions.

Bug: T408389
Change-Id: I8a3ce367f312016ffc4c632cfbd28d254d0e2cb7
---
M pywikibot/data/api/_requests.py
M pywikibot/site/_extensions.py
M tests/flaggedrevs_tests.py
3 files changed, 206 insertions(+), 4 deletions(-)

Approvals:
  jenkins-bot: Verified
  Xqt: Looks good to me, approved




diff --git a/pywikibot/data/api/_requests.py b/pywikibot/data/api/_requests.py
index 8da3f14..7939fea 100644
--- a/pywikibot/data/api/_requests.py
+++ b/pywikibot/data/api/_requests.py
@@ -70,13 +70,15 @@
     'strikevote', 'tag', 'thank', 'threadaction', 'transcodereset',
     'translationreview', 'unblock', 'undelete', 'unlinkaccount', 'upload',
     'userrights', 'watch', 'wikilove',
+    # FlaggedRevs extension
+    'review',
     # wikibase actions, see https://www.mediawiki.org/wiki/Wikibase/API
     'wbcreateclaim', 'wbcreateredirect', 'wbeditentity', 'wblinktitles',
     'wbmergeitems', 'wbremoveclaims', 'wbremovequalifiers',
     'wbremovereferences', 'wbsetaliases', 'wbsetclaim', 'wbsetclaimvalue',
     'wbsetdescription', 'wbsetlabel', 'wbsetqualifier', 'wbsetreference',
     'wbsetsitelink',
-    # lexeme (internal) actions
+    # WikibaseLexeme (internal) actions
     'wbladdform', 'wbladdsense', 'wbleditformelements', 'wbleditsenseelements',
     'wblmergelexemes', 'wblremoveform', 'wblremovesense',
 }
diff --git a/pywikibot/site/_extensions.py b/pywikibot/site/_extensions.py
index 499a7f0..821d77e 100644
--- a/pywikibot/site/_extensions.py
+++ b/pywikibot/site/_extensions.py
@@ -18,8 +18,9 @@
     InconsistentTitleError,
     NoPageError,
     SiteDefinitionError,
+    UnexpectedAPIDataError,
 )
-from pywikibot.site._decorators import need_extension
+from pywikibot.site._decorators import need_extension, need_right
 from pywikibot.tools import merge_unique_dicts


@@ -497,3 +498,65 @@
             return None

         return pages[0].get('flagged', {}).get('stable_revid')
+
+    @need_extension('FlaggedRevs')
+    @need_right('review')
+    def review_revision(
+        self,
+        revid: int,
+        *,
+        comment: str | None = None,
+        unapprove: bool = False,
+        flag: int | None = None,
+    ) -> None:
+        """Review a revision using the FlaggedRevs ``action=review`` API.
+
+        .. note::
+           Reviewing or unapproving a revision may change the stable
+           revision of the associated page. Cached
+           :attr:`BasePage.stable_revision_id
+           <page.BasePage.stable_revision_id>` values are not
+           invalidated automatically.
+
+        :param revid: Revision ID to review.
+        :param comment: Optional review comment.
+        :param unapprove: If True, the revision will be *unapproved*.
+        :param flag: Set the review flag value.
+        :raises APIError: On API failure.
+        :raises UnexpectedAPIDataError: Unexpected API data for review
+            parameters or review result.
+        :raises UnknownExtensionError: FlaggedRevs not available.
+        :raises UserRightsError: User has insufficient rights.
+        :raises ValueError: Unsupported *flag* parameter.
+        """
+        try:
+            review_params = self._paraminfo['review']['parameters']
+        except KeyError as e:
+            raise UnexpectedAPIDataError(
+                'Unexpected API data: no param info found') from e
+
+        names = {item['name'] for item in review_params}
+        flag_param = next((p for p in names if p.startswith('flag_')), None)
+
+        params = {
+            'action': 'review',
+            'token': self.tokens['csrf'],
+            'revid': revid,
+            'comment': comment,
+        }
+
+        if flag is not None:
+            if flag_param is None:
+                raise ValueError(
+                    "The 'flag' parameter is not supported by this wiki")
+            params[flag_param] = flag
+
+        if unapprove:
+            params['unapprove'] = '1'
+
+        request = self.simple_request(**params, formatversion=2)
+        data = request.submit()
+
+        if data.get('review', {}).get('result') != 'Success':
+            raise UnexpectedAPIDataError(
+                f'Unexpected review result:\n{data!r}')
diff --git a/tests/flaggedrevs_tests.py b/tests/flaggedrevs_tests.py
index 6766609..08527df 100644
--- a/tests/flaggedrevs_tests.py
+++ b/tests/flaggedrevs_tests.py
@@ -11,9 +11,9 @@
 from unittest.mock import MagicMock, patch
 
 import pywikibot
-from pywikibot.exceptions import UnknownExtensionError
+from pywikibot.exceptions import APIError, UnknownExtensionError
 from pywikibot.page import Revision
-from tests.aspects import TestCase
+from tests.aspects import PatchingTestCase, TestCase


 class TestPageStableRevision(TestCase):
@@ -141,6 +141,143 @@
             self.assertEqual(result.revid, 999)


+class TestFlaggedRevsReview(PatchingTestCase):
+
+    """Test site.review() with flagged revisions."""
+
+    family = 'wikipedia'
+    code = 'fi'
+    dry = True
+
+    def setUp(self):
+        """Set up Test and patches."""
+        super().setUp()
+        self.token = '123ABC+\\'
+
+        self.mock_req = MagicMock()
+        self.patch(
+            pywikibot.site._apisite.APISite,
+            'simple_request',
+            self.mock_req,
+        )
+        self.submit = self.mock_req.return_value.submit
+
+        self.patch(
+            pywikibot.site._tokenwallet.TokenWallet,
+            '__getitem__',
+            lambda *_: self.token,
+        )
+
+        self.patch(
+            pywikibot.site._apisite.APISite,
+            'has_extension',
+            lambda *_: True,
+        )
+
+        self.patch(
+            pywikibot.site._apisite.APISite,
+            'has_right',
+            lambda *_: True,
+        )
+
+        self.patch(
+            self.site,
+            '_paraminfo',
+            {
+                'review': {
+                    'parameters': [
+                        {'name': 'comment'},
+                        {'name': 'flag_accuracy'},
+                    ]
+                }
+            },
+        )
+
+    def _mock_success(self, revid: int, **extra):
+        return {
+            'review': {
+                'revid': revid,
+                'result': 'Success',
+                **extra
+            }
+        }
+
+    def test_review_basic(self) -> None:
+        """Review a revision without any flags (simple approval)."""
+        revid = 12345
+        self.submit.return_value = self._mock_success(revid)
+        self.site.review_revision(revid=revid, comment='unit test')
+
+        self.mock_req.assert_called_once_with(
+            action='review',
+            token=self.token,
+            revid=revid,
+            comment='unit test',
+            formatversion=2,
+        )
+
+    def test_review_unapprove(self) -> None:
+        """Un-approve a previously approved revision."""
+        revid = 12347
+        self.submit.return_value = self._mock_success(revid)
+
+        self.site.review_revision(
+            revid=revid,
+            comment='unit test unapprove',
+            unapprove=True
+        )
+
+        self.mock_req.assert_called_once_with(
+            action='review',
+            token=self.token,
+            revid=revid,
+            comment='unit test unapprove',
+            unapprove='1',
+            formatversion=2,
+        )
+
+    def test_review_missing_token(self) -> None:
+        """Calling review() without a token raises ``notoken``."""
+        self.site.tokens.clear()
+        self.submit.side_effect = APIError(
+            code='notoken',
+            info='No CSRF token',
+            other={},
+        )
+
+        with self.assertRaises(APIError) as cm:
+            self.site.review_revision(revid=999)
+
+        self.assertEqual(cm.exception.code, 'notoken')
+
+    def test_review_insufficient_rights(self) -> None:
+        """User without ``review`` right gets ``permissiondenied``."""
+        self.submit.side_effect = APIError(
+            code='permissiondenied',
+            info="You don't have permission to review revisions.",
+            other={},
+        )
+
+        with self.assertRaises(APIError) as cm:
+            self.site.review_revision(revid=888)
+
+        self.assertEqual(cm.exception.code, 'permissiondenied')
+
+    def test_review_defaults(self) -> None:
+        """Calling review() with only revid and token is allowed."""
+        revid = 12348
+        self.submit.return_value = self._mock_success(revid)
+        self.site.review_revision(revid=revid)
+
+        self.mock_req.assert_called_once_with(
+            action='review',
+            token=self.token,
+            revid=revid,
+            comment=None,
+            formatversion=2,
+        )
+
+
 if __name__ == '__main__':
     with suppress(SystemExit):
         unittest.main()

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1203490?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: I8a3ce367f312016ffc4c632cfbd28d254d0e2cb7
Gerrit-Change-Number: 1203490
Gerrit-PatchSet: 30
Gerrit-Owner: Ezra yendau <[email protected]>
Gerrit-Reviewer: Tacsipacsi <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: Zache-tool <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to