jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/985373 )
Change subject: replace collections.namedtuple with typing.NamedTuple ......................................................................
replace collections.namedtuple with typing.NamedTuple
Change-Id: I1802458320777ba0aaa7e59d406ca3358981eed8 --- M pywikibot/textlib.py M scripts/blockpageschecker.py M pywikibot/pagegenerators/_filters.py M pywikibot/throttle.py M pywikibot/scripts/generate_user_files.py M pywikibot/site/_apisite.py 6 files changed, 98 insertions(+), 48 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/pagegenerators/_filters.py b/pywikibot/pagegenerators/_filters.py index 16af382..500bc12 100644 --- a/pywikibot/pagegenerators/_filters.py +++ b/pywikibot/pagegenerators/_filters.py @@ -8,9 +8,8 @@
import datetime import re -from collections import namedtuple from functools import partial -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, NamedTuple, Union
import pywikibot from pywikibot import config @@ -362,6 +361,12 @@ RegexBodyFilterPageGenerator = RegexFilter.contentfilter
+class _Edit(NamedTuple): + do_edit: datetime.datetime + edit_start: datetime.datetime + edit_end: datetime.datetime + + def EdittimeFilterPageGenerator( generator: Iterable[pywikibot.page.Page], last_edit_start: datetime.datetime | None = None, @@ -380,9 +385,7 @@ :param first_edit_end: Only yield pages first edited before this time :param show_filtered: Output a message for each page not yielded """ - Edit = namedtuple('Edit', ['do_edit', 'edit_start', 'edit_end']) - - def to_be_yielded(edit: Edit, + def to_be_yielded(edit: _Edit, page: pywikibot.page.Page, rev: pywikibot.page.Revision, show_filtered: bool) -> bool: @@ -407,13 +410,13 @@
return True
- latest_edit = Edit(last_edit_start or last_edit_end, - last_edit_start or datetime.datetime.min, - last_edit_end or datetime.datetime.max) + latest_edit = _Edit(last_edit_start or last_edit_end, + last_edit_start or datetime.datetime.min, + last_edit_end or datetime.datetime.max)
- first_edit = Edit(first_edit_start or first_edit_end, - first_edit_start or datetime.datetime.min, - first_edit_end or datetime.datetime.max) + first_edit = _Edit(first_edit_start or first_edit_end, + first_edit_start or datetime.datetime.min, + first_edit_end or datetime.datetime.max)
for page in generator or []: yield_for_last = to_be_yielded(latest_edit, page, diff --git a/pywikibot/scripts/generate_user_files.py b/pywikibot/scripts/generate_user_files.py index 440a5c2..6e03aae 100755 --- a/pywikibot/scripts/generate_user_files.py +++ b/pywikibot/scripts/generate_user_files.py @@ -18,9 +18,9 @@ import os import re import sys -from collections import namedtuple from pathlib import Path from textwrap import fill +from typing import NamedTuple
from pywikibot.backports import Callable from pywikibot.scripts import _import_with_no_user_config @@ -228,7 +228,10 @@ {botpasswords}"""
-ConfigSection = namedtuple('ConfigSection', 'head, info, section') +class _ConfigSection(NamedTuple): + head: str + info: str + section: str
def parse_sections() -> list: @@ -237,7 +240,7 @@ config.py will be in the pywikibot/ directory whereas generate_user_files script is in pywikibot/scripts.
- :return: a list of ConfigSection named tuples. + :return: a list of _ConfigSection named tuples. """ data = []
@@ -255,7 +258,7 @@
for section, head, comment in result: info = ' '.join(text.strip('# ') for text in comment.splitlines()) - data.append(ConfigSection(head, info, section)) + data.append(_ConfigSection(head, info, section)) return data
@@ -290,7 +293,7 @@
def input_sections(variant: str, - sections: list[ConfigSection], + sections: list[_ConfigSection], skip: Callable | None = None, force: bool = False, default: str = 'n') -> None: @@ -339,10 +342,16 @@ return copies
+class _UserItem(NamedTuple): + family: str + code: str + name: str + + def create_user_config( - main_family, - main_code, - main_username, + main_family: str, + main_code: str, + main_username: str, force: bool = False ): """ @@ -353,14 +362,13 @@ _fnc = os.path.join(base_dir, USER_BASENAME) _fncpass = os.path.join(base_dir, PASS_BASENAME)
- useritem = namedtuple('useritem', 'family, code, name') userlist = [] if force and not config.verbose_output: if main_username: - userlist = [useritem(main_family, main_code, main_username)] + userlist = [_UserItem(main_family, main_code, main_username)] else: while True: - userlist += [useritem(*get_site_and_lang( + userlist += [_UserItem(*get_site_and_lang( main_family, main_code, main_username, force=force))] if not pywikibot.input_yn('Do you want to add any other projects?', force=force, diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py index 3c46cad..380811b 100644 --- a/pywikibot/site/_apisite.py +++ b/pywikibot/site/_apisite.py @@ -10,10 +10,10 @@ import re import time import typing -from collections import OrderedDict, defaultdict, namedtuple +from collections import OrderedDict, defaultdict from contextlib import suppress from textwrap import fill -from typing import TYPE_CHECKING, Any, TypeVar, Union +from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar, Union
import pywikibot from pywikibot import login @@ -93,6 +93,11 @@ _RequestWrapperT = TypeVar('_RequestWrapperT', bound='api._RequestWrapper')
+class _OnErrorExc(NamedTuple): + exception: Exception + on_new_page: bool | None + + class APISite( BaseSite, EchoMixin, @@ -2173,8 +2178,6 @@
return False
- OnErrorExc = namedtuple('OnErrorExc', 'exception on_new_page') - # catalog of merge history errors for use in error messages _mh_errors = { 'noapiwrite': 'API editing not enabled on {site} wiki', @@ -2283,7 +2286,7 @@ raise Error('mergehistory: unexpected response')
# catalog of move errors for use in error messages - _mv_errors: dict[str, str | OnErrorExc] = { + _mv_errors: dict[str, str | _OnErrorExc] = { 'noapiwrite': 'API editing not enabled on {site} wiki', 'writeapidenied': 'User {user} is not authorized to edit on {site} wiki', @@ -2298,13 +2301,13 @@ 'immobilenamespace': 'Pages in {oldnamespace} namespace cannot be moved on {site} ' 'wiki', - 'articleexists': OnErrorExc(exception=ArticleExistsConflictError, - on_new_page=True), - # "protectedpage" can happen in both directions. - 'protectedpage': OnErrorExc(exception=LockedPageError, - on_new_page=None), - 'protectedtitle': OnErrorExc(exception=LockedNoPageError, + 'articleexists': _OnErrorExc(exception=ArticleExistsConflictError, on_new_page=True), + # "protectedpage" can happen in both directions. + 'protectedpage': _OnErrorExc(exception=LockedPageError, + on_new_page=None), + 'protectedtitle': _OnErrorExc(exception=LockedNoPageError, + on_new_page=True), 'nonfilenamespace': 'Cannot move a file to {newnamespace} namespace on {site} ' 'wiki', diff --git a/pywikibot/textlib.py b/pywikibot/textlib.py index 0c5f80e..b6a0d45 100644 --- a/pywikibot/textlib.py +++ b/pywikibot/textlib.py @@ -8,7 +8,7 @@
import itertools import re -from collections import OrderedDict, namedtuple +from collections import OrderedDict from collections.abc import Sequence from contextlib import suppress from html.parser import HTMLParser @@ -940,7 +940,11 @@ HEAD_PATTERN = re.compile(r'(={1,6}).+\1', re.DOTALL) TITLE_PATTERN = re.compile("'{3}([^']+)'{3}")
-_Heading = namedtuple('_Heading', ('text', 'start', 'end')) + +class _Heading(NamedTuple): + text: str + start: int + end: int
class Section(NamedTuple): @@ -1947,11 +1951,20 @@
TIMEGROUPS = ('time', 'tzinfo', 'year', 'month', 'day', 'hour', 'minute')
-#: Hold precompiled timestamp patterns for :class:`TimeStripper`. -#: Order of TimeStripperPatterns is important to avoid mismatch when searching. -#: -#: .. versionadded:: 8.0 -TimeStripperPatterns = namedtuple('TimeStripperPatterns', TIMEGROUPS[:-2]) + +class TimeStripperPatterns(NamedTuple): + """Hold precompiled timestamp patterns for :class:`TimeStripper`. + + Attribute order is important to avoid mismatch when searching. + + .. versionadded:: 8.0 + """ + + time: Pattern[str] + tzinfo: Pattern[str] + year: Pattern[str] + month: Pattern[str] + day: Pattern[str]
class TimeStripper: diff --git a/pywikibot/throttle.py b/pywikibot/throttle.py index 3944d27..ec0cfba 100644 --- a/pywikibot/throttle.py +++ b/pywikibot/throttle.py @@ -10,9 +10,10 @@ import math import threading import time -from collections import Counter, namedtuple +from collections import Counter from contextlib import suppress from hashlib import blake2b +from typing import NamedTuple
import pywikibot from pywikibot import config @@ -21,7 +22,6 @@
FORMAT_LINE = '{module_id} {pid} {time} {site}\n' -ProcEntry = namedtuple('ProcEntry', ['module_id', 'pid', 'time', 'site'])
pid: bool | int = False """global process identifier @@ -32,6 +32,15 @@ """
+class ProcEntry(NamedTuple): + """ProcEntry namedtuple.""" + + module_id: str + pid: int + time: int + site: str + + class Throttle:
"""Control rate of access to wiki server. diff --git a/scripts/blockpageschecker.py b/scripts/blockpageschecker.py index 9aa4f2c..438cbc8 100755 --- a/scripts/blockpageschecker.py +++ b/scripts/blockpageschecker.py @@ -52,8 +52,8 @@
import re import webbrowser -from collections import namedtuple from itertools import chain +from typing import NamedTuple
import pywikibot from pywikibot import i18n, pagegenerators @@ -169,7 +169,12 @@ project_inserted = ['ar', 'cs', 'fr', 'it', 'ja', 'pt', 'sr', 'ur', 'zh']
# END PREFERENCES -ParsedTemplate = namedtuple('ParsedTemplate', 'blocktype, regex, msgtype') + + +class _ParsedTemplate(NamedTuple): + blocktype: str + regex: str + msgtype: str
class CheckerBot(ConfigParserBot, ExistingPageBot, SingleSiteBot): @@ -260,25 +265,25 @@ for catch_regex in template: result_catch = re.findall(catch_regex, text) if result_catch: - return ParsedTemplate( + return _ParsedTemplate( results[index], catch_regex, 'modifying')
if tsmp and ttmp and ttp != ttmp and tsp != tsmp: for catch_regex in ttmp: result_catch = re.findall(catch_regex, text) if result_catch: - return ParsedTemplate( + return _ParsedTemplate( 'sysop-move', catch_regex, 'modifying')
for catch_regex in tsmp: result_catch = re.findall(catch_regex, text) if result_catch: - return ParsedTemplate( + return _ParsedTemplate( 'autoconfirmed-move', catch_regex, 'modifying')
# If editable means that we have no regex, won't change anything # with this regex - return ParsedTemplate('editable', r'\A', 'adding') + return _ParsedTemplate('editable', r'\A', 'adding')
tsp = i18n.translate(self.site, template_semi_protection) ttp = i18n.translate(self.site, template_total_protection)
pywikibot-commits@lists.wikimedia.org