jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1204707?usp=email )
Change subject: Add FlaggedRevs to Pywikibot
......................................................................
Add FlaggedRevs to Pywikibot
- APISite.FlaggedRevsMixin:
an APISite mixin for FlaggedRevs extension
- APSite.stable_revid:
gives the stable (reviewed) revision id for a page
- BasePage.stable_revision_id:
a wrapper around APSite.stable_revid to give the current stable
(reviewed) revision id of its page
- BasePage.stable_revision:
add a convenience property method to retrieve the page’s stable
(reviewed) revision when the FlaggedRevs extension is enabled. Returns
a `Revision` object with content, or None if FlaggedRevs is unavailable
or the page has no stable revision.
- flaggesrevs_tests:
tests were added
Bug: T409848
Change-Id: I67070b06d802c0969d7b0a87c6aca3ce1d2a1f8d
---
M pywikibot/page/_basepage.py
M pywikibot/site/_apisite.py
M pywikibot/site/_extensions.py
M tests/__init__.py
A tests/flaggedrevs_tests.py
5 files changed, 244 insertions(+), 2 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/pywikibot/page/_basepage.py b/pywikibot/page/_basepage.py
index 7dcfc50..196fea9 100644
--- a/pywikibot/page/_basepage.py
+++ b/pywikibot/page/_basepage.py
@@ -550,7 +550,9 @@
>>> type(edit_time)
<class 'pywikibot.time.Timestamp'>
- .. seealso:: :attr:`oldest_revision`
+ .. seealso::
+ - :attr:`stable_revision`
+ - :attr:`oldest_revision`
"""
rev = self._latest_cached_revision()
if rev is not None:
@@ -561,6 +563,65 @@
raise InvalidPageError(self)
@property
+ @cached
+ def stable_revision_id(self) -> int | None:
+ """Return the current stable (reviewed) revision id for this page.
+
+ .. version-added:: 11.7
+ .. seealso:
+ - :attr:`stable_revision`
+ - :attr:`latest_revision_id`
+
+ :raises UnknownExtensionError: FlaggedRevs not available
+ """
+ return self.site.stable_revid(self)
+
+ @stable_revision_id.deleter
+ def stable_revision_id(self) -> None:
+ """Remove the cached latest stable revision id set for this Page."""
+ with suppress(AttributeError):
+ del self._stable_revision_id
+
+ @property
+ def stable_revision(self) -> pywikibot.page.Revision | None:
+ """Return the latest stable (reviewed) revision for this page, if any.
+
+ Uses the site’s
+ :class:`FlaggedRevsMixin<pywikibot.site._extensions.FlaggedRevsMixin>`
+ mixin to obtain a stable revid.
+
+ **Example:**
+
+ >>> site = pywikibot.Site('wikipedia:fi')
+ >>> page = pywikibot.Page(site, 'Main Page')
+ ... # get the stable revision timestamp of that page
+ >>> edit_time = page.stable_revision.timestamp
+ >>> type(edit_time)
+ <class 'pywikibot.time.Timestamp'>
+ >>> page.stable_revision.revid == site.stable_revid(page)
+ True
+ >>> # delete cache to force reloading the latest stable id
+ >>> del page.stable_revision_id
+ >>> page.stable_revision.revid == page.stable_revision_id
+ True
+
+ .. version-added:: 11.7
+ .. seealso::
+ - :attr:`stable_revision_id`
+ - :attr:`latest_revision`
+ - :attr:`oldest_revision`
+ - :attr:`APISite.stable_revid
+ <pywikibot.site._extensions.FlaggedRevsMixin.stable_revid>`
+
+ :raises UnknownExtensionError: FlaggedRevs not available
+ """
+ revid = self.stable_revision_id
+ if revid is not None:
+ return self.get_revision(revid, content=True)
+
+ return None
+
+ @property
def text(self) -> str:
"""Return the current (edited) wikitext, loading it if necessary.
diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py
index 6965b9c..4e193d0 100644
--- a/pywikibot/site/_apisite.py
+++ b/pywikibot/site/_apisite.py
@@ -51,6 +51,7 @@
from pywikibot.site._decorators import need_right
from pywikibot.site._extensions import (
EchoMixin,
+ FlaggedRevsMixin,
GeoDataMixin,
GlobalUsageMixin,
LinterMixin,
@@ -100,6 +101,7 @@
class APISite(
BaseSite,
EchoMixin,
+ FlaggedRevsMixin,
GeneratorsMixin,
GeoDataMixin,
GlobalUsageMixin,
diff --git a/pywikibot/site/_extensions.py b/pywikibot/site/_extensions.py
index 4c2ea88..e0ca075 100644
--- a/pywikibot/site/_extensions.py
+++ b/pywikibot/site/_extensions.py
@@ -467,3 +467,35 @@
raise Error(msg)
return data[str(page.pageid)]['extract']
+
+
+class FlaggedRevsMixin:
+
+ """APISite mixin for the FlaggesRevs extension.
+
+ .. version-added:: 11.7
+ .. seealso:: https://www.mediawiki.org/wiki/Extension:FlaggedRevs
+ """
+
+ @need_extension('FlaggedRevs')
+ def stable_revid(self: BaseSiteProtocol,
+ page: pywikibot.Page) -> int | None:
+ """Return the stable (reviewed) revision id for a page, if any.
+
+ :param page: The page to inspect.
+ :return: The stable revision id or None if not available.
+ :raises UnknownExtensionError: FlaggedRevs not available
+ """
+ req = self.simple_request(
+ action='query',
+ prop='flagged',
+ titles=page.title(with_section=False),
+ formatversion=2
+ )
+ data = req.submit()
+
+ pages = data.get('query', {}).get('pages', [])
+ if not pages:
+ return None
+
+ return pages[0].get('flagged', {}).get('stable_revid')
diff --git a/tests/__init__.py b/tests/__init__.py
index c84212e..79b8116 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -111,6 +111,7 @@
'family',
'file',
'fixes',
+ 'flaggedrevs',
'gui',
'http',
'i18n',
@@ -139,8 +140,8 @@
'site_login_logout',
'site_obsoletesites',
'siteinfo',
- 'superset',
'sparql',
+ 'superset',
'tests',
'textlib',
'thanks',
diff --git a/tests/flaggedrevs_tests.py b/tests/flaggedrevs_tests.py
new file mode 100644
index 0000000..6766609
--- /dev/null
+++ b/tests/flaggedrevs_tests.py
@@ -0,0 +1,146 @@
+#
+# (C) Pywikibot team, 2026
+#
+# Distributed under the terms of the MIT license.
+#
+"""Unit tests for Page.stable_revision."""
+from __future__ import annotations
+
+import unittest
+from contextlib import suppress
+from unittest.mock import MagicMock, patch
+
+import pywikibot
+from pywikibot.exceptions import UnknownExtensionError
+from pywikibot.page import Revision
+from tests.aspects import TestCase
+
+
+class TestPageStableRevision(TestCase):
+
+ """Test Page.stable_revision with full mocking."""
+
+ family = 'wikipedia'
+ code = 'fi'
+ dry = True
+
+ def setUp(self) -> None:
+ """Test setup."""
+ super().setUp()
+ self.page = pywikibot.Page(self.site, 'TestPage')
+ self.page.exists = MagicMock(return_value=True)
+
+ def _mock_response(self, data: dict | list[dict]) -> dict:
+ """Helper: build API response for action=query&prop=flagged."""
+ return {
+ 'query': {
+ 'pages': data if isinstance(data, list) else [data]
+ }
+ }
+
+ @patch('pywikibot.site._apisite.APISite.has_extension')
+ def test_stable_revision_flaggedrevs_disabled(self, mock_has_ext):
+ """FlaggedRevs not enabled → return None."""
+ mock_has_ext.return_value = False
+
+ with self.assertRaisesRegex(
+ UnknownExtensionError,
+ 'Method "stable_revid" is not implemented without the extension '
+ 'FlaggedRevs'
+ ):
+ self.page.stable_revision
+
+ @patch('pywikibot.site._apisite.APISite.simple_request')
+ @patch('pywikibot.site._apisite.APISite.has_extension')
+ def test_stable_revision_no_flagged_data(self, mock_has_ext, mock_req):
+ """API returns no 'flagged' key → return None."""
+ mock_has_ext.return_value = True
+ mock_req.return_value.submit.return_value = self._mock_response(
+ {'pageid': 1, 'ns': 0, 'title': 'TestPage'}
+ )
+
+ result = self.page.stable_revision
+ self.assertIsNone(result)
+
+ @patch('pywikibot.site._apisite.APISite.simple_request')
+ @patch('pywikibot.site._apisite.APISite.has_extension')
+ def test_stable_revision_no_stable_revid(self, mock_has_ext, mock_req):
+ """'flagged' exists but no 'stable_revid' → return None."""
+ mock_has_ext.return_value = True
+ mock_req.return_value.submit.return_value = self._mock_response(
+ {
+ 'pageid': 1,
+ 'title': 'TestPage',
+ 'flagged': {'level': 1}
+ }
+ )
+
+ result = self.page.stable_revision
+ self.assertIsNone(result)
+
+ @patch('pywikibot.site._apisite.APISite.simple_request')
+ @patch('pywikibot.site._apisite.APISite.has_extension')
+ def test_stable_revision_success(self, mock_has_ext, mock_req):
+ """Valid stable_revid → return Revision with content."""
+ mock_has_ext.return_value = True
+
+ # Mock get_revision to return a real-looking Revision
+ mock_rev = MagicMock(spec=Revision)
+ mock_rev.revid = 12345
+ mock_rev.text = 'Stable content'
+ mock_rev.user = 'Reviewer'
+ mock_rev.timestamp = pywikibot.Timestamp(2025, 1, 1)
+
+ with patch.object(self.page,
+ 'get_revision',
+ return_value=mock_rev):
+ mock_req.return_value.submit.return_value = self._mock_response(
+ {
+ 'pageid': 1,
+ 'title': 'TestPage',
+ 'flagged': {
+ 'stable_revid': 12345,
+ 'level': 2,
+ 'pending_since': '2025-01-01T00:00:00Z'
+ }
+ }
+ )
+
+ result = self.page.stable_revision
+
+ self.assertEqual(result, mock_rev)
+ self.assertEqual(result.revid, 12345)
+ self.assertIn('Stable content', result.text)
+
+ @patch('pywikibot.site._apisite.APISite.simple_request')
+ @patch('pywikibot.site._apisite.APISite.has_extension')
+ def test_stable_revision_multiple_pages(self, mock_has_ext, mock_req):
+ """Multiple pages in response → still find correct one."""
+ mock_has_ext.return_value = True
+
+ # Mock get_revision to return a Revision with revid=999
+ mock_rev = MagicMock(spec=Revision)
+ mock_rev.revid = 999
+ mock_rev.text = 'Other stable content'
+
+ with patch.object(self.page, 'get_revision', return_value=mock_rev):
+ mock_req.return_value.submit.return_value = self._mock_response([
+ {
+ 'pageid': 1,
+ 'title': 'TestPage',
+ 'flagged': {'stable_revid': 999}
+ },
+ {
+ 'pageid': 2,
+ 'title': 'OtherPage',
+ 'flagged': {'stable_revid': 888}
+ }
+ ])
+
+ result = self.page.stable_revision
+ self.assertEqual(result.revid, 999)
+
+
+if __name__ == '__main__':
+ with suppress(SystemExit):
+ unittest.main()
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1204707?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: I67070b06d802c0969d7b0a87c6aca3ce1d2a1f8d
Gerrit-Change-Number: 1204707
Gerrit-PatchSet: 16
Gerrit-Owner: Ezra yendau <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: Zache-tool <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: Tacsipacsi <[email protected]>
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]