jenkins-bot submitted this change.

View Change


Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
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(-)

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)

To view, visit change 985373. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I1802458320777ba0aaa7e59d406ca3358981eed8
Gerrit-Change-Number: 985373
Gerrit-PatchSet: 3
Gerrit-Owner: JJMC89 <JJMC89.Wikimedia@gmail.com>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged