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

Change subject: MyPy: Solve some MyPy issues in several files
......................................................................

MyPy: Solve some MyPy issues in several files

Also fix pre-commit mypy check

Change-Id: I1f9000f9b33df83dda68b6c7734dfc0d5805442c
---
M .pre-commit-config.yaml
M conftest.py
M pywikibot/backports.py
M pywikibot/comms/eventstreams.py
M pywikibot/data/api/__init__.py
M pywikibot/data/api/_optionset.py
M pywikibot/data/api/_paraminfo.py
M pywikibot/data/api/_requests.py
M pywikibot/data/sparql.py
M pywikibot/diff.py
M pywikibot/page/_category.py
M pywikibot/page/_collections.py
M pywikibot/page/_user.py
M pywikibot/pagegenerators/_factory.py
M pywikibot/pagegenerators/_filters.py
M pywikibot/site/_extensions.py
M tox.ini
17 files changed, 113 insertions(+), 76 deletions(-)

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




diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4544265..cc0cae2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -119,16 +119,22 @@
         args:
           - --config-file=pyproject.toml
           - --follow-imports=silent
+        additional_dependencies:
+          - types-PyMySQL
+          - types-requests
         # Test for files which already passed in past.
         # They should be also used in conftest.py to exclude them from 
non-voting mypy test.
-        files: >
-          
^pywikibot/(__metadata__|config|echo|exceptions|fixes|logging|time)\.py$|
-          ^pywikibot/(comms|data|families|specialbots)/__init__\.py$|
-          ^pywikibot/data/memento\.py$|
-          ^pywikibot/families/[a-z][a-z\d]+_family\.py$|
-          ^pywikibot/page/(__init__|_decorators|_revision)\.py$|
-          ^pywikibot/pagegenerators/__init__\.py$|
-          ^pywikibot/scripts/(?:i18n/)?__init__\.py$|
-          
^pywikibot/site/(__init__|_basesite|_decorators|_extensions|_interwikimap|_tokenwallet|_upload)\.py$|
-          ^pywikibot/tools/(_logging|_unidata|formatter)\.py$|
-          
^pywikibot/userinterfaces/(__init__|_interface_base|terminal_interface)\.py$
+        files: |
+          (?x)^pywikibot/(
+            
(__metadata__|backports|config|diff|echo|exceptions|fixes|logging|time)|
+            (comms|data|families|specialbots)/__init__|
+            comms/eventstreams|
+            data/(api/(__init__|_optionset)|memento)|
+            families/[a-z][a-z\d]+_family|
+            page/(__init__|_decorators|_revision)|
+            pagegenerators/(__init__|_filters)|
+            scripts/(?:i18n/)?__init__|
+            
site/(__init__|_basesite|_decorators|_interwikimap|_tokenwallet|_upload)|
+            tools/(_logging|_unidata|formatter)|
+            userinterfaces/(__init__|_interface_base|terminal_interface)
+          )\.py$
diff --git a/conftest.py b/conftest.py
index c009871..5e92cd4 100644
--- a/conftest.py
+++ b/conftest.py
@@ -16,14 +16,15 @@

 EXCLUDE_PATTERN = re.compile(
     r'(?:'
-    r'(__metadata__|config|echo|exceptions|fixes|logging|time)|'
+    r'(__metadata__|backports|config|diff|echo|exceptions|fixes|logging|time)|'
     r'(comms|data|families|specialbots)/__init__|'
-    r'data/memento|'
+    r'comms/eventstreams|'
+    r'data/(api/(__init__|_optionset)|memento)|'
     r'families/[a-z][a-z\d]+_family|'
     r'page/(__init__|_decorators|_revision)|'
-    r'pagegenerators/__init__|'
+    r'pagegenerators/(__init__|_filters)|'
     r'scripts/(i18n/)?__init__|'
-    r'site/(__init__|_basesite|_decorators|_extensions|_interwikimap|'
+    r'site/(__init__|_basesite|_decorators|_interwikimap|'
     r'_tokenwallet|_upload)|'
     r'tools/(_logging|_unidata|formatter)|'
     r'userinterfaces/(__init__|_interface_base|terminal_interface)'
diff --git a/pywikibot/backports.py b/pywikibot/backports.py
index 25b174b..00636aa 100644
--- a/pywikibot/backports.py
+++ b/pywikibot/backports.py
@@ -16,7 +16,7 @@

 import re
 import sys
-from typing import Any
+from typing import TYPE_CHECKING, Any


 # Placed here to omit circular import in tools
@@ -58,6 +58,7 @@
         Match,
         Pattern,
         Sequence,
+        Set,
     )
 else:
     from collections import Counter
@@ -72,6 +73,7 @@
     from re import Match, Pattern
     Dict = dict  # type: ignore[misc]
     List = list  # type: ignore[misc]
+    Set = set  # type: ignore[misc]


 if PYTHON_VERSION < (3, 9, 2):
@@ -137,8 +139,9 @@
         a, b = tee(iterable)
         next(b, None)
         return zip(a, b)
-else:
-    from itertools import pairwise  # type: ignore[no-redef]
+
+elif not TYPE_CHECKING:
+    from itertools import pairwise
     from types import NoneType


@@ -202,13 +205,18 @@
                     raise ValueError(msg)
                 yield tuple(group)
         else:  # PYTHON_VERSION == (3, 12)
-            from itertools import batched as _batched
+            if TYPE_CHECKING:
+                _batched: Callable[[Iterable, int], Iterable]
+            else:
+                from itertools import batched as _batched
+
             for group in _batched(iterable, n):
                 if strict and len(group) < n:
                     raise ValueError(msg)
                 yield group
-else:
-    from itertools import batched  # type: ignore[no-redef]
+
+elif not TYPE_CHECKING:
+    from itertools import batched


 # gh-115942, gh-134323
@@ -291,4 +299,4 @@
                 return status == 'locked'

 else:
-    from threading import RLock
+    from threading import RLock  # type: ignore[assignment]
diff --git a/pywikibot/comms/eventstreams.py b/pywikibot/comms/eventstreams.py
index 81a32fe..6db46d9 100644
--- a/pywikibot/comms/eventstreams.py
+++ b/pywikibot/comms/eventstreams.py
@@ -26,7 +26,7 @@
 from requests.packages.urllib3.util.response import httplib

 from pywikibot import Site, Timestamp, config, debug, warning
-from pywikibot.backports import NoneType
+from pywikibot.backports import Dict, List, NoneType
 from pywikibot.comms.http import user_agent
 from pywikibot.tools import cached, deprecated_args
 from pywikibot.tools.collections import GeneratorWrapper
@@ -179,7 +179,7 @@
         if isinstance(EventSource, ModuleNotFoundError):
             raise ImportError(INSTALL_MSG) from EventSource

-        self.filter = {'all': [], 'any': [], 'none': []}
+        self.filter: Dict[str, List[Any]] = {'all': [], 'any': [], 'none': []}
         self._total: int | None = None
         self._canary = kwargs.pop('canary', False)

diff --git a/pywikibot/data/api/__init__.py b/pywikibot/data/api/__init__.py
index 0af2e3e..c5af36c 100644
--- a/pywikibot/data/api/__init__.py
+++ b/pywikibot/data/api/__init__.py
@@ -1,6 +1,6 @@
 """Interface to MediaWiki's api.php."""
 #
-# (C) Pywikibot team, 2014-2024
+# (C) Pywikibot team, 2014-2025
 #
 # Distributed under the terms of the MIT license.
 #
@@ -55,7 +55,7 @@
     """
     if isinstance(family, SubdomainFamily):
         for cookie in http.cookie_jar:
-            if family.domain == cookie.domain:
+            if family.domain == cookie.domain:  # type: ignore[attr-defined]
                 http.cookie_jar.clear(cookie.domain, cookie.path, cookie.name)


@@ -71,9 +71,10 @@

     def _handle_text(self, msg) -> None:
         if msg['content-transfer-encoding'] == 'binary':
-            self._fp.write(msg.get_payload(decode=True))
+            self._fp.write(  # type: ignore[attr-defined]
+                msg.get_payload(decode=True))
         else:
-            super()._handle_text(msg)
+            super()._handle_text(msg)  # type: ignore[misc]

     _writeBody = _handle_text  # noqa: N815

diff --git a/pywikibot/data/api/_optionset.py b/pywikibot/data/api/_optionset.py
index 4ba5674..c494ed4 100644
--- a/pywikibot/data/api/_optionset.py
+++ b/pywikibot/data/api/_optionset.py
@@ -9,6 +9,7 @@
 from collections.abc import MutableMapping

 import pywikibot
+from pywikibot.backports import Set
 from pywikibot.tools import deprecate_arg


@@ -41,21 +42,26 @@
            *dict* parameter was renamed to *data*.

         :param site: The associated site
-        :param module: The module name which is used by paraminfo. (Ignored
-            when site is None)
-        :param param: The parameter name inside the module. That parameter must
-            have a 'type' entry. (Ignored when site is None)
+        :param module: The module name which is used by paraminfo.
+            (Ignored when site is None)
+        :param param: The parameter name inside the module. That
+            parameter must have a 'type' entry. (Ignored when site is
+            None)
         :param data: The initializing data dict which is used for
             :meth:`from_dict`
         """
         self._site_set = False
-        self._enabled = set()
-        self._disabled = set()
+        self._enabled: Set[str] = set()
+        self._disabled: Set[str] = set()
         self._set_site(site, module, param)
         if data:
             self.from_dict(data)

-    def _set_site(self, site, module: str, param: str, *,
+    def _set_site(self,
+                  site: pywikibot.site.APISite | None,
+                  module: str | None,
+                  param: str | None,
+                  *,
                   clear_invalid: bool = False) -> None:
         """Set the site and valid names.

@@ -64,7 +70,6 @@
         thrown.

         :param site: The associated site
-        :type site: pywikibot.site.APISite
         :param module: The module name which is used by paraminfo.
         :param param: The parameter name inside the module. That
             parameter must have a 'type' entry.
@@ -80,6 +85,7 @@
         self._valid_disable = set()
         if site is None:
             return
+
         for type_value in site._paraminfo.parameter(module, param)['type']:
             if type_value[0] == '!':
                 self._valid_disable.add(type_value[1:])
@@ -96,7 +102,7 @@
                                '"{}"'.format('", "'.join(invalid_names)))
         self._site_set = True

-    def from_dict(self, dictionary) -> None:
+    def from_dict(self, dictionary: dict[str, bool | None]) -> None:
         """Load options from the dict.

         The options are not cleared before. If changes have been made
@@ -107,7 +113,6 @@
             the value False, True or None. The names must be valid
             depending on whether they enable or disable the option. All
             names with the value None can be in either of the list.
-        :type dictionary: dict (keys are strings, values are bool/None)
         """
         enabled = set()
         disabled = set()
diff --git a/pywikibot/data/api/_paraminfo.py b/pywikibot/data/api/_paraminfo.py
index 44fd7c4..beaf551 100644
--- a/pywikibot/data/api/_paraminfo.py
+++ b/pywikibot/data/api/_paraminfo.py
@@ -11,7 +11,7 @@

 import pywikibot
 from pywikibot import config
-from pywikibot.backports import Iterable, batched
+from pywikibot.backports import Dict, Iterable, Set, batched
 from pywikibot.tools import (
     classproperty,
     deprecated,
@@ -36,6 +36,9 @@
     init_modules = frozenset(['main', 'paraminfo'])
     param_modules = ('list', 'meta', 'prop')

+    _action_modules: frozenset[str]
+    _modules: Dict[str, Set[str] | Dict[str, str]]
+
     @remove_last_args(['modules_only_mode'])
     def __init__(self,
                  site,
@@ -72,7 +75,7 @@
             if self._action_modules:
                 assert modules == self._action_modules
             else:
-                self._action_modules = modules
+                self._action_modules = frozenset(modules)
         elif name in self._modules:
             # update required to updates from dict and set
             self._modules[name].update(modules)
diff --git a/pywikibot/data/api/_requests.py b/pywikibot/data/api/_requests.py
index 035f65c..5fd6e11 100644
--- a/pywikibot/data/api/_requests.py
+++ b/pywikibot/data/api/_requests.py
@@ -1080,7 +1080,7 @@
                 error['help'] = error.pop('*')  # formatversion 1

             code = error.setdefault('code', 'Unknown')
-            info = error.setdefault('info', None)
+            info = error.setdefault('info', '')

             if (code == self.last_error['code']
                     and info == self.last_error['info']):
diff --git a/pywikibot/data/sparql.py b/pywikibot/data/sparql.py
index 4713888..ca5042d 100644
--- a/pywikibot/data/sparql.py
+++ b/pywikibot/data/sparql.py
@@ -1,19 +1,20 @@
 """SPARQL Query interface."""
 #
-# (C) Pywikibot team, 2016-2024
+# (C) Pywikibot team, 2016-2025
 #
 # Distributed under the terms of the MIT license.
 #
 from __future__ import annotations

 from textwrap import fill
+from typing import Any
 from urllib.parse import quote

 from requests import JSONDecodeError
 from requests.exceptions import Timeout

 from pywikibot import Site
-from pywikibot.backports import removeprefix
+from pywikibot.backports import Dict, removeprefix
 from pywikibot.comms import http
 from pywikibot.data import WaitingMixin
 from pywikibot.exceptions import Error, NoUsernameError, ServerError
@@ -111,7 +112,7 @@
         result = []
         qvars = data['head']['vars']
         for row in data['results']['bindings']:
-            values = {}
+            values: Dict[str, Any] = {}
             for var in qvars:
                 if var not in row:
                     # var is not available (OPTIONAL is probably used)
diff --git a/pywikibot/diff.py b/pywikibot/diff.py
index 052e934..c1ba3a8 100644
--- a/pywikibot/diff.py
+++ b/pywikibot/diff.py
@@ -1,6 +1,6 @@
 """Diff module."""
 #
-# (C) Pywikibot team, 2014-2024
+# (C) Pywikibot team, 2014-2025
 #
 # Distributed under the terms of the MIT license.
 #
@@ -9,7 +9,8 @@
 import difflib
 import math
 from collections import abc
-from difflib import SequenceMatcher, _format_range_unified
+from difflib import _format_range_unified  # type: ignore[attr-defined]
+from difflib import SequenceMatcher
 from heapq import nlargest
 from itertools import zip_longest

@@ -612,12 +613,14 @@
     return comparands


-def get_close_matches_ratio(word: Sequence,
-                            possibilities: list[Sequence],
-                            *,
-                            n: int = 3,
-                            cutoff: float = 0.6,
-                            ignorecase: bool = False) -> list[float, Sequence]:
+def get_close_matches_ratio(
+    word: str,
+    possibilities: list[str],
+    *,
+    n: int = 3,
+    cutoff: float = 0.6,
+    ignorecase: bool = False
+) -> list[tuple[float, str]]:
     """Return a list of the best “good enough” matches and its ratio.

     This method is similar to Python's :pylib:`difflib.get_close_matches()
diff --git a/pywikibot/page/_category.py b/pywikibot/page/_category.py
index f352394..ccc457e 100644
--- a/pywikibot/page/_category.py
+++ b/pywikibot/page/_category.py
@@ -61,7 +61,7 @@

     def subcategories(self, *,
                       recurse: int | bool = False,
-                      **kwargs: Any) -> Generator[Page, None, None]:
+                      **kwargs: Any) -> Generator[Category, None, None]:
         """Yield all subcategories of the current category.

         **Usage:**
@@ -200,7 +200,7 @@
                     return

     def members(self, *,
-                recurse: bool = False,
+                recurse: int | bool = False,
                 total: int | None = None,
                 **kwargs: Any) -> Generator[Page, None, None]:
         """Yield all category contents (subcats, pages, and files).
diff --git a/pywikibot/page/_collections.py b/pywikibot/page/_collections.py
index b3b7984..dea73af 100644
--- a/pywikibot/page/_collections.py
+++ b/pywikibot/page/_collections.py
@@ -32,7 +32,7 @@
     in subclasses.
     """

-    def __init__(self, data=None) -> None:
+    def __init__(self, data: dict[str, Any] = None) -> None:
         super().__init__()
         self._data = {}
         if data:
@@ -43,15 +43,15 @@
         """Construct a new empty BaseDataDict."""
         return cls()

-    def __getitem__(self, key):
+    def __getitem__(self, key: BaseSite | str) -> Any:
         key = self.normalizeKey(key)
         return self._data[key]

-    def __setitem__(self, key, value) -> None:
+    def __setitem__(self, key: BaseSite | str, value: Any) -> None:
         key = self.normalizeKey(key)
         self._data[key] = value

-    def __delitem__(self, key) -> None:
+    def __delitem__(self, key: BaseSite | str) -> None:
         key = self.normalizeKey(key)
         del self._data[key]

@@ -61,7 +61,7 @@
     def __len__(self) -> int:
         return len(self._data)

-    def __contains__(self, key) -> bool:
+    def __contains__(self, key: BaseSite | str) -> bool:
         key = self.normalizeKey(key)
         return key in self._data

diff --git a/pywikibot/page/_user.py b/pywikibot/page/_user.py
index 206c1be..8f515e6 100644
--- a/pywikibot/page/_user.py
+++ b/pywikibot/page/_user.py
@@ -112,12 +112,13 @@
         if force and hasattr(self, '_userprops'):
             del self._userprops
         if not hasattr(self, '_userprops'):
-            self._userprops = list(self.site.users([self.username]))[0]
+            self._userprops = next(self.site.users([self.username]))
             if self.isAnonymous() or self.is_CIDR():
-                r = list(self.site.blocks(iprange=self.username, total=1))
+                r = next(self.site.blocks(iprange=self.username, total=1),
+                         None)
                 if r:
-                    self._userprops['blockedby'] = r[0]['by']
-                    self._userprops['blockreason'] = r[0]['reason']
+                    self._userprops['blockedby'] = r['by']
+                    self._userprops['blockreason'] = r['reason']
         return self._userprops

     def registration(self,
diff --git a/pywikibot/pagegenerators/_factory.py 
b/pywikibot/pagegenerators/_factory.py
index 249466d..ba4c648 100644
--- a/pywikibot/pagegenerators/_factory.py
+++ b/pywikibot/pagegenerators/_factory.py
@@ -62,13 +62,13 @@


 if TYPE_CHECKING:
-    from typing import Any, Literal
+    from typing import Any, Literal, Optional

     from pywikibot.site import BaseSite, Namespace

     HANDLER_GEN_TYPE = Iterable[pywikibot.page.BasePage]
     GEN_FACTORY_CLAIM_TYPE = list[tuple[str, str, dict[str, str], bool]]
-    OPT_GENERATOR_TYPE = HANDLER_GEN_TYPE | None
+    OPT_GENERATOR_TYPE = Optional[HANDLER_GEN_TYPE]


 # This is the function that will be used to de-duplicate page iterators.
diff --git a/pywikibot/pagegenerators/_filters.py 
b/pywikibot/pagegenerators/_filters.py
index cd006e2..641724d 100644
--- a/pywikibot/pagegenerators/_filters.py
+++ b/pywikibot/pagegenerators/_filters.py
@@ -1,6 +1,6 @@
 """Page filter generators provided by the pagegenerators module."""
 #
-# (C) Pywikibot team, 2008-2024
+# (C) Pywikibot team, 2008-2025
 #
 # Distributed under the terms of the MIT license.
 #
@@ -20,16 +20,18 @@


 if TYPE_CHECKING:
+    from typing import Union
+
     from pywikibot.site import BaseSite, Namespace

     PRELOAD_SITE_TYPE = dict[pywikibot.site.BaseSite,
                              list[pywikibot.page.BasePage]]
-    PATTERN_STR_OR_SEQ_TYPE = (
-        str
-        | Pattern[str]
-        | Sequence[str]
-        | Sequence[Pattern[str]]
-    )
+    PATTERN_STR_OR_SEQ_TYPE = Union[
+        str,
+        Pattern[str],
+        Sequence[str],
+        Sequence[Pattern[str]],
+    ]


 # This is the function that will be used to de-duplicate page iterators.
diff --git a/pywikibot/site/_extensions.py b/pywikibot/site/_extensions.py
index 7e8191d..8b15cf8 100644
--- a/pywikibot/site/_extensions.py
+++ b/pywikibot/site/_extensions.py
@@ -50,6 +50,11 @@
     def simple_request(self, **kwargs) -> api.Request:
         ...

+    def querypage(
+        self, *args, **kwargs
+    ) -> Generator[tuple[pywikibot.Page, int], None, None]:
+        ...
+

 class EchoMixin:

@@ -287,7 +292,7 @@

     @need_extension('WikibaseClient')
     def unconnected_pages(
-        self,
+        self: BaseSiteProtocol,
         total: int | None = None,
         *,
         strict: bool = False
@@ -305,7 +310,7 @@
         :param strict: If ``True``, verify that each page still has no
             data item before yielding it.
         """
-        if total <= 0:
+        if total is not None and total <= 0:
             return

         if not strict:
@@ -329,9 +334,9 @@

     @need_extension('Linter')
     def linter_pages(
-        self,
+        self: BaseSiteProtocol,
         lint_categories=None,
-        total: int = None,
+        total: int | None = None,
         namespaces=None,
         pageids: str | int | None = None,
         lint_from: str | int | None = None
diff --git a/tox.ini b/tox.ini
index a949b3d..d47e26c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -66,6 +66,7 @@
 basepython = python3.9
 deps =
     pytest-mypy
+    types-PyMySQL
     types-requests
 commands =
     mypy --version

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1189971?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: I1f9000f9b33df83dda68b6c7734dfc0d5805442c
Gerrit-Change-Number: 1189971
Gerrit-PatchSet: 14
Gerrit-Owner: Xqt <[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