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

Change subject: Add PageViewInfo extension support
......................................................................

Add PageViewInfo extension support

Expose daily page and site view counts through the extension Action API
modules. Add a generator for the most-viewed pages.

Bug: T325473
Change-Id: If2f8459167c45b010cab9325a75313f16b33e03a
---
M pywikibot/page/_basepage.py
M pywikibot/site/_apisite.py
M pywikibot/site/_extensions.py
A tests/pageviewinfo_tests.py
4 files changed, 220 insertions(+), 0 deletions(-)

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




diff --git a/pywikibot/page/_basepage.py b/pywikibot/page/_basepage.py
index 62cfc3a..9ebfc4f 100644
--- a/pywikibot/page/_basepage.py
+++ b/pywikibot/page/_basepage.py
@@ -2037,6 +2037,26 @@

         return self._pageimage

+    def pageviews(
+        self,
+        days: int | None = None,
+        *,
+        metric: str = 'pageviews',
+    ) -> dict[str, int | None]:
+        """Return daily page views for this page.
+
+        Uses the MediaWiki extension PageViewInfo.
+
+        .. version-added:: 11.8
+
+        :param days: Number of days to return, up to the site-configured
+            maximum. If ``None``, use the site's default.
+        :param metric: Page view metric supported by the site.
+        :return: Mapping of ISO date strings to view counts. A count may
+            be ``None`` when data is unavailable.
+        """
+        return self.site.pageviews(self, days, metric=metric)
+
     def getRedirectTarget(self, *,
                           ignore_section: bool = True) -> pywikibot.Page:
         """Return a Page object for the target this Page redirects to.
diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py
index 7f9a4f9..c14ec44 100644
--- a/pywikibot/site/_apisite.py
+++ b/pywikibot/site/_apisite.py
@@ -56,6 +56,7 @@
     GlobalUsageMixin,
     LinterMixin,
     PageImagesMixin,
+    PageViewInfoMixin,
     ProofreadPageMixin,
     TextExtractsMixin,
     ThanksMixin,
@@ -110,6 +111,7 @@
     GlobalUsageMixin,
     LinterMixin,
     PageImagesMixin,
+    PageViewInfoMixin,
     ProofreadPageMixin,
     TextExtractsMixin,
     ThanksMixin,
diff --git a/pywikibot/site/_extensions.py b/pywikibot/site/_extensions.py
index 3a2cdec..7dfa2f9 100644
--- a/pywikibot/site/_extensions.py
+++ b/pywikibot/site/_extensions.py
@@ -238,6 +238,105 @@
         self._update_page(page, query)


+class PageViewInfoMixin:
+
+    """APISite mixin for PageViewInfo extension.
+
+    .. version-added:: 11.8
+    """
+
+    @need_extension('PageViewInfo')
+    def pageviews(
+        self: BaseSiteProtocol,
+        page: pywikibot.Page,
+        days: int | None = None,
+        *,
+        metric: str = 'pageviews',
+    ) -> dict[str, int | None]:
+        """Return daily page views for *page*.
+
+        :param page: Page for which to retrieve view counts.
+        :param days: Number of days to return, up to the site-configured
+            maximum. If ``None``, use the site's default.
+        :param metric: Page view metric supported by the site.
+        :raises NoPageError: The page does not exist.
+        :return: Mapping of ISO date strings to view counts. A count may
+            be ``None`` when data is unavailable.
+        """
+        if page.namespace() >= 0 and not page.exists():
+            raise NoPageError(page)
+
+        parameters = {
+            'titles': page.title(with_section=False),
+            'pvipmetric': metric,
+        }
+        if days is not None:
+            parameters['pvipdays'] = days
+        query = self._generator(api.PropertyGenerator,
+                                type_arg='pageviews', **parameters)
+        try:
+            pagedata = next(iter(query))
+            return pagedata['pageviews']
+        except (KeyError, StopIteration) as e:
+            raise UnexpectedAPIDataError(
+                f'PageViewInfo response contains no data for {page}'
+            ) from e
+
+    @need_extension('PageViewInfo')
+    def siteviews(
+        self: BaseSiteProtocol,
+        days: int | None = None,
+        *,
+        metric: str = 'pageviews',
+    ) -> dict[str, int | None]:
+        """Return daily view totals for this site.
+
+        :param days: Number of days to return, up to the site-configured
+            maximum. If ``None``, use the site's default.
+        :param metric: Site view metric supported by the site.
+        :return: Mapping of ISO date strings to view counts. A count may
+            be ``None`` when data is unavailable.
+        """
+        parameters = {
+            'action': 'query',
+            'meta': 'siteviews',
+            'pvismetric': metric,
+            'formatversion': 2,
+        }
+        if days is not None:
+            parameters['pvisdays'] = days
+        request = self.simple_request(**parameters)
+        try:
+            return request.submit()['query']['siteviews']
+        except KeyError as e:
+            raise UnexpectedAPIDataError(
+                'PageViewInfo response contains no site view data'
+            ) from e
+
+    @need_extension('PageViewInfo')
+    def mostviewed(
+        self: BaseSiteProtocol,
+        total: int | None = 10,
+        *,
+        metric: str = 'pageviews',
+    ) -> Generator[tuple[pywikibot.Page, int]]:
+        """Yield the most viewed pages and their view counts.
+
+        :param total: Maximum number of pages to return, or ``None`` for
+            all available pages.
+        :param metric: Page view metric supported by the site.
+        :yield: A page and its view count for the previous day.
+        """
+        query = self._generator(
+            api.ListGenerator,
+            type_arg='mostviewed',
+            total=total,
+            pvimmetric=metric,
+        )
+        for pagedata in query:
+            yield pywikibot.Page(self, pagedata['title']), pagedata['count']
+
+
 class GlobalUsageMixin:

     """APISite mixin for Global Usage extension."""
diff --git a/tests/pageviewinfo_tests.py b/tests/pageviewinfo_tests.py
new file mode 100755
index 0000000..7302fc7
--- /dev/null
+++ b/tests/pageviewinfo_tests.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+# (C) Pywikibot team, 2026
+#
+# Distributed under the terms of the MIT license.
+#
+"""Tests for the PageViewInfo extension."""
+from __future__ import annotations
+
+from unittest.mock import Mock, patch
+
+import pywikibot
+from pywikibot.data import api
+from tests.aspects import DefaultSiteTestCase
+
+
+class TestPageViewInfo(DefaultSiteTestCase):
+
+    """Test PageViewInfo methods against mocked API responses."""
+
+    dry = True
+
+    def test_pageviews(self) -> None:
+        """Test retrieving views for a page."""
+        page = pywikibot.Page(self.site, 'Special:Search')
+        response = {
+            'title': 'Special:Search',
+            'pageviews': {'2026-09-17': 42, '2026-09-18': None},
+        }
+
+        with (
+            patch.object(self.site, 'has_extension', return_value=True),
+            patch.object(page, 'exists') as exists,
+            patch.object(self.site, '_generator') as generator,
+        ):
+            generator.return_value.__iter__.return_value = iter((response,))
+            result = page.pageviews()
+
+        self.assertEqual(result, response['pageviews'])
+        exists.assert_not_called()
+        generator.assert_called_once_with(
+            api.PropertyGenerator,
+            type_arg='pageviews',
+            titles='Special:Search',
+            pvipmetric='pageviews',
+        )
+
+    def test_siteviews(self) -> None:
+        """Test retrieving site-wide views."""
+        request = Mock()
+        response = {'2026-09-17': 42, '2026-09-18': 43}
+        request.submit.return_value = {'query': {'siteviews': response}}
+
+        with (
+            patch.object(self.site, 'has_extension', return_value=True),
+            patch.object(self.site, 'simple_request', return_value=request)
+            as simple_request,
+        ):
+            result = self.site.siteviews(days=2, metric='uniques')
+
+        self.assertEqual(result, response)
+        simple_request.assert_called_once_with(
+            action='query',
+            meta='siteviews',
+            pvisdays=2,
+            pvismetric='uniques',
+            formatversion=2,
+        )
+
+    def test_mostviewed(self) -> None:
+        """Test retrieving the most viewed pages."""
+        response = (
+            {'title': 'Main Page', 'count': 42},
+            {'title': 'Earth', 'count': 23},
+        )
+
+        with (
+            patch.object(self.site, 'has_extension', return_value=True),
+            patch.object(self.site, '_generator') as generator,
+        ):
+            generator.return_value.__iter__.return_value = iter(response)
+            result = list(self.site.mostviewed(total=2))
+
+        self.assertEqual(
+            [(page.title(), count) for page, count in result],
+            [('Main Page', 42), ('Earth', 23)],
+        )
+        generator.assert_called_once_with(
+            api.ListGenerator,
+            type_arg='mostviewed',
+            total=2,
+            pvimmetric='pageviews',
+        )
+
+
+if __name__ == '__main__':
+    import unittest
+
+    unittest.main()

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1343242?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: If2f8459167c45b010cab9325a75313f16b33e03a
Gerrit-Change-Number: 1343242
Gerrit-PatchSet: 2
Gerrit-Owner: Mahveotm <[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]

Reply via email to