"""Object representing interface to toolforge tools.
.. versionadded:: 7.7
"""
#
# (C) Pywikibot team, 2022-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations
import collections
import re
from typing import TYPE_CHECKING
import pywikibot
from pywikibot import textlib
from pywikibot.tools import deprecated, deprecated_args
try:
import wikitextparser
except ImportError as e:
wikitextparser = e
if TYPE_CHECKING:
import datetime
from pywikibot import Timestamp
DATETYPE = str | Timestamp | datetime.datetime | datetime.date | None
[docs]
class WikiBlameMixin:
"""Page mixin for main authorship.
.. versionadded:: 7.7
"""
#: Supported wikipedia site codes
WIKIBLAME_CODES = (
'ar', 'de', 'en', 'es', 'eu', 'fr', 'hu', 'id', 'it', 'ja', 'nl', 'pl',
'pt', 'tr',
)
def _check_wh_supported(self):
"""Check if WikiHistory is supported."""
if self.site.family.name != 'wikipedia':
raise NotImplementedError(
'main_authors method is implemented for wikipedia family only')
if self.site.code not in self.WIKIBLAME_CODES:
raise NotImplementedError(
'main_authors method is not implemented for wikipedia:{}'
.format(self.site.code))
if self.namespace() != pywikibot.site.Namespace.MAIN:
raise NotImplementedError(
'main_authors method is implemented for main namespace only')
if not self.exists():
raise pywikibot.exceptions.NoPageError(self)
if isinstance(wikitextparser, ImportError):
raise wikitextparser
[docs]
@deprecated('authorsship', since='9.3.0')
@deprecated_args(onlynew=None) # since 9.2.0
def main_authors(self) -> collections.Counter[str, int]:
"""Retrieve the 5 topmost main authors of an article.
Sample:
>>> import pywikibot
>>> site = pywikibot.Site('wikipedia:eu')
>>> page = pywikibot.Page(site, 'Python (informatika)')
>>> auth = page.main_authors()
>>> auth.most_common(1)
[('Ksarasola', 82)]
.. important:: Only implemented for main namespace pages and
only wikipedias of :attr:`WIKIBLAME_CODES` are supported.
.. seealso::
- https://wikihistory.toolforge.org
- https://de.wikipedia.org/wiki/Wikipedia:Technik/Cloud/wikihistory
- https://xtools.wmcloud.org/authorship/
.. versionchanged:: 9.2
do not use any wait cycles due to :phab:`366100`.
.. versionchanged:: 9.3
https://xtools.wmcloud.org/authorship/ is used to retrieve
authors
.. deprecated:: 9.3
use :meth:`authorship` instead.
:return: Percentage of edits for each username
:raise ImportError: missing ``wikitextparser`` module.
:raise NotImplementedError: unsupported site or unsupported
namespace.
:raise Error: Error response from xtools.
:raise NoPageError: The page does not exist.
:raise requests.exceptions.HTTPError: 429 Client Error: Too Many
Requests for url; login to meta family first.
"""
return collections.Counter(
{user: int(cnt) for user, (_, cnt) in self.authorship(5).items()})
[docs]
def authorship(
self,
n: int | None = None,
*,
min_chars: int = 0,
min_pct: float = 0.0,
max_pct_sum: float | None = None,
revid: int | None = None,
date: DATETYPE = None,
) -> dict[str, tuple[int, float]]:
"""Retrieve authorship attribution of an article.
This method uses XTools/Authorship to retrieve the authors
measured by character count.
Sample:
>>> import pywikibot
>>> site = pywikibot.Site('wikipedia:en')
>>> page = pywikibot.Page(site, 'Pywikibot')
>>> auth = page.authorship()
>>> auth
{'1234qwer1234qwer4': (68, 100.0)}
.. important:: Only implemented for main namespace pages and
only wikipedias of :attr:`WIKIBLAME_CODES` are supported.
.. seealso::
- https://xtools.wmcloud.org/authorship/
- https://www.mediawiki.org/wiki/XTools/Authorship
- https://www.mediawiki.org/wiki/WikiWho
.. versionadded:: 9.3
this method replaces :meth:`main_authors`.
:param n: Only return the first *n* or fewer authors.
:param min_chars: Only return authors with more than *min_chars*
chars changes.
:param min_pct: Only return authors with more than *min_pct*
percentage edits.
:param max_pct_sum: Only return authors until the prcentage sum
reached *max_pct_sum*.
:param revid: The revision id for the authors should be found.
If ``None`` or ``0``, the latest revision is be used. Cannot
be used together with *date*.
:param date: The revision date for the authors should be found.
If ``None``, it will be ignored. Cannot be used together
with *revid*. If the parameter is a string it must be given
in the form ``YYYY-MM-DD``
:return: Character count and percentage of edits for each
username.
:raise ImportError: missing ``wikitextparser`` module
:raise NotImplementedError: unsupported site or unsupported
namespace.
:raise Error: Error response from xtools.
:raiseNoPageError: The page does not exist.
:raise requests.exceptions.HTTPError: 429 Client Error: Too Many
Requests for url; login to meta family first.
"""
baseurl = 'https://xtools.wmcloud.org/authorship/{url}&format=wikitext'
pattern = r'\[\[.+[|/](?P<user>.+)\]\]'
self._check_wh_supported()
if revid and date:
raise ValueError(
'You cannot specify revid together with date argument')
show = revid or 0 if date is None else str(date)[:10]
url = '{}.wikipedia.org/{}/{}?uselang={}'.format(
self.site.code,
self.title(as_url=True, with_ns=False, with_section=False),
show,
'en',
)
url = baseurl.format(url=url)
r = pywikibot.comms.http.fetch(url)
if r.status_code != 200:
r.raise_for_status()
result: list[list[str]] = []
try:
table = wikitextparser.parse(r.text).tables[0]
except IndexError:
pattern = textlib.get_regexes('code')[0]
msg = textlib.removeHTMLParts(pattern.search(r.text)[0])
raise pywikibot.exceptions.Error(msg) from None
pct_sum = 0.0
for row in table.data():
if row[0] == 'Rank':
continue # skip headline
rank = int(row[0])
user = re.match(pattern, row[1])['user']
chars = int(row[3].replace(',', '_'))
percent = float(row[4].rstrip('%'))
# take into account tht data() is ordered
if n and rank > n or chars < min_chars or percent < min_pct:
break
result.append((user, chars, percent))
pct_sum += percent
if max_pct_sum and pct_sum >= max_pct_sum:
break
return {user: (chars, percent) for user, chars, percent in result}