jenkins-bot submitted this change.

View Change

Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
[IMPR] add type hints

Bug: T286403
Change-Id: I77eb4c6bd0160f7fdc5078de701dc02a55d8edcf
---
M pywikibot/config.py
1 file changed, 61 insertions(+), 38 deletions(-)

diff --git a/pywikibot/config.py b/pywikibot/config.py
index 426f39d..c6bbe26 100644
--- a/pywikibot/config.py
+++ b/pywikibot/config.py
@@ -44,16 +44,21 @@
from os import environ, getenv
from pathlib import Path
from textwrap import fill
-from typing import Optional, Union
+from typing import Optional, TypeVar, Union
from warnings import warn
from zipfile import ZipFile, is_zipfile

from pywikibot.__metadata__ import __version__ as pwb_version
-from pywikibot.backports import Dict, List, Tuple, removesuffix
+from pywikibot.backports import (DefaultDict, Dict, FrozenSet, List, Mapping,
+ Tuple, removesuffix)
from pywikibot.logging import error, output, warning
from pywikibot.tools import deprecated, issue_deprecation_warning


+_DabComDict = DefaultDict[str, Dict[str, str]]
+_ValueType = TypeVar('_ValueType')
+
+
OSWIN32 = (sys.platform == 'win32')

if OSWIN32:
@@ -143,7 +148,7 @@
# You may use '*' for family name in a similar manner.
#
usernames = collections.defaultdict(dict) # type: Dict[str, Dict[str, str]]
-disambiguation_comment = collections.defaultdict(dict)
+disambiguation_comment = collections.defaultdict(dict) # type: _DabComDict

# User agent format.
# For the meaning and more help in customization see:
@@ -168,7 +173,7 @@
# True for enabling, False for disabling, str to hardcode a UA.
# Example: {'problematic.site.example': True,
# 'prefers.specific.ua.example': 'snakeoil/4.2'}
-fake_user_agent_exceptions = {}
+fake_user_agent_exceptions = {} # type: Dict[str, Union[bool, str]]
# This following option is deprecated in favour of finer control options above.
fake_user_agent = False

@@ -218,7 +223,7 @@
# 'access_key', 'access_secret')
#
# Note: the target wiki site must install OAuth extension
-authenticate = {}
+authenticate = {} # type: Dict[str, Tuple[str, ...]]

# By default you are asked for a password on the terminal.
# A password file may be used, e.g. password_file = '.passwd'
@@ -271,7 +276,7 @@
#
# Note that these headers will be sent with all requests,
# not just MediaWiki API calls.
-extra_headers = {}
+extra_headers = {} # type: Mapping[str, str]

# Set to True to override the {{bots}} exclusion protocol (at your own risk!)
ignore_bot_templates = False
@@ -279,7 +284,7 @@
# #############################################


-def user_home_path(path):
+def user_home_path(path: str) -> str:
"""Return a file path to a file in the user home."""
return os.path.join(os.path.expanduser('~'), path)

@@ -308,7 +313,7 @@
directory. Used to test whether placing a user config file in this
directory will cause it to be selected as the base directory.
"""
- def exists(directory):
+ def exists(directory: str) -> bool:
directory = os.path.abspath(directory)
if directory == test_directory:
return True
@@ -342,20 +347,22 @@
elif win_version in (6, 10):
sub_dir = ['AppData', 'Roaming']
else:
- raise WindowsError('Windows version {} not supported yet.'
- .format(win_version))
+ raise WindowsError( # type: ignore[name-defined]
+ 'Windows version {} not supported yet.'
+ .format(win_version)
+ )
base_dir_cand.extend([[home] + sub_dir + ['Pywikibot'],
[home] + sub_dir + ['pywikibot']])
else:
base_dir_cand.append([home, '.pywikibot'])

- for dir in base_dir_cand:
- dir = os.path.join(*dir)
+ for dir_ in base_dir_cand:
+ dir_s = os.path.join(*dir_)
try:
- os.makedirs(dir, mode=private_files_permission)
+ os.makedirs(dir_s, mode=private_files_permission)
except OSError: # PermissionError or already exists
- if exists(dir):
- base_dir = dir
+ if exists(dir_s):
+ base_dir = dir_s
break

if not os.path.isabs(base_dir):
@@ -369,6 +376,7 @@
base_dir)

if __no_user_config is None:
+ assert get_base_dir.__doc__ is not None
exc_text += (
' Please check that user-config.py is stored in the correct '
'location.\n'
@@ -393,7 +401,7 @@


@deprecated('family_files[family_name] = file_path', since='20210305')
-def register_family_file(family_name, file_path):
+def register_family_file(family_name: str, file_path: str) -> None:
"""Register a single family class file.

Parameter file_path may be a path or an url.
@@ -402,7 +410,7 @@
family_files[family_name] = file_path


-def register_families_folder(folder_path: str):
+def register_families_folder(folder_path: str) -> None:
"""Register all family class files contained in a directory.

:param folder_path: The path of a folder containing family files.
@@ -554,9 +562,9 @@
# log = []
# Per default, no logging is enabled.
# This setting can be overridden by the -log or -nolog command-line arguments.
-log = []
+log = [] # type: List[str]
# filename defaults to modulename-bot.log
-logfilename = None
+logfilename = None # type: Optional[str]
# maximal size of a logfile in kilobytes. If the size reached that limit the
# logfile will be renamed (if logfilecount is not 0) and the old file is filled
# again. logfilesize must be an integer value
@@ -574,7 +582,7 @@
log_pywiki_repo_version = False
# if True, include a lot of debugging info in logfile
# (overrides log setting above)
-debug_log = []
+debug_log = [] # type: List[str]

# ############# EXTERNAL SCRIPT PATH SETTINGS ##############
# Set your own script path to lookup for your script files.
@@ -815,7 +823,7 @@

# Set simulate to True or use -simulate option to block all actions given
# above.
-simulate = False
+simulate = False # type: Union[bool, str]

# How many pages should be put to a queue in asynchronous mode.
# If maxsize is <= 0, the queue size is infinite.
@@ -840,7 +848,7 @@
# #############################################


-def makepath(path: str, create: bool = True):
+def makepath(path: str, create: bool = True) -> str:
"""Return a normalized absolute version of the path argument.

If the given path already exists in the filesystem or create is False
@@ -861,7 +869,7 @@
return os.path.normpath(os.path.abspath(path))


-def datafilepath(*filename, **kwargs):
+def datafilepath(*filename: str, create: bool = True) -> str:
"""Return an absolute path to a data file in a standard location.

Argument(s) are zero or more directory names, optionally followed by a
@@ -869,24 +877,21 @@
directories in the path that do not already exist are created if create
is True, otherwise the filesystem keeps unchanged.

- :param path: path in the filesystem
- :type path: str
- :keyword create: create the directory if it is True. Otherwise don't change
+ :param filename: path in the filesystem
+ :param create: create the directory if it is True. Otherwise don't change
the filesystem. Default is True.
- :type create: bool
"""
- create = kwargs.get('create', True)
return makepath(os.path.join(base_dir, *filename), create=create)


-def shortpath(path):
+def shortpath(path: str) -> str:
"""Return a file path relative to config.base_dir."""
if path.startswith(base_dir):
return path[len(base_dir) + len(os.path.sep):]
return path


-def _win32_extension_command(extension):
+def _win32_extension_command(extension: str) -> Optional[str]:
"""Get the command from the Win32 registry for an extension."""
fileexts_key = \
r'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts'
@@ -896,7 +901,7 @@
_prog_id = winreg.EnumValue(key1, 0)[0]
_key2 = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT,
r'{}\shell\open\command'.format(_prog_id))
- _cmd = winreg.QueryValueEx(_key2, None)[0]
+ _cmd = winreg.QueryValueEx(_key2, '')[0]
# See T102465 for issues relating to using this value.
cmd = _cmd
if cmd.find('%1'):
@@ -904,13 +909,14 @@
# Remove any trailing character, which should be a quote or space
# and then remove all whitespace.
return cmd[:-1].strip()
- except WindowsError as e:
+ except WindowsError as e: # type: ignore[name-defined]
# Catch any key lookup errors
output('Unable to detect program for file extension "{}": {!r}'
.format(extension, e))
+ return None


-def _detect_win32_editor():
+def _detect_win32_editor() -> Optional[str]:
"""Detect the best Win32 editor."""
# Notepad is even worse than our Tkinter editor.
unusable_exes = ['notepad.exe',
@@ -962,7 +968,12 @@

"""An error when the required type doesn't match the actual type."""

- def __init__(self, name, actual_type, allowed_types):
+ def __init__(
+ self,
+ name: str,
+ actual_type: type,
+ allowed_types: Tuple[type, ...],
+ ) -> None:
super().__init__(
'Configuration variable "{}" is defined as "{}" in '
'your user-config.py but expected "{}".'
@@ -970,17 +981,25 @@
'", "'.join(t.__name__ for t in allowed_types)))


-def _assert_default_type(name, value, default_value):
+def _assert_default_type(
+ name: str,
+ value: _ValueType,
+ default_value: object,
+) -> Union[_ValueType, float, None]:
"""Return the value if the old or new is None or both same type."""
if (value is None or default_value is None
or isinstance(value, type(default_value))):
return value
if isinstance(value, int) and isinstance(default_value, float):
return float(value)
- raise _DifferentTypeError(name, type(value), [type(default_value)])
+ raise _DifferentTypeError(name, type(value), (type(default_value),))


-def _assert_types(name, value, types):
+def _assert_types(
+ name: str,
+ value: _ValueType,
+ types: Tuple[type, ...],
+) -> _ValueType:
"""Return the value if it's one of the types."""
if isinstance(value, types):
return value
@@ -993,7 +1012,11 @@
'maintainers if you depend on it.')


-def _check_user_config_types(user_config, default_values, skipped):
+def _check_user_config_types(
+ user_config: Dict[str, object],
+ default_values: Dict[str, object],
+ skipped: FrozenSet[str],
+) -> None:
"""Check the types compared to the default values."""
for name, value in user_config.items():
if name in default_values:

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

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