jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/623030 )
Change subject: [IMPR] Add typing hints and solve some statical type errors
......................................................................
[IMPR] Add typing hints and solve some statical type errors
Change-Id: I79525d21778cdbce8032df49ebf2d716b1eee5d5
---
M pywikibot/bot.py
M pywikibot/config2.py
M pywikibot/exceptions.py
M pywikibot/family.py
M pywikibot/logentries.py
M pywikibot/page/__init__.py
M pywikibot/tools/formatter.py
7 files changed, 41 insertions(+), 36 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 4f87b3a..1dd2c69 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -99,6 +99,7 @@
from importlib import import_module
from pathlib import Path
from textwrap import fill
+from typing import Any, Dict
from warnings import warn
import pywikibot
@@ -1024,7 +1025,7 @@
# The values are the default values
# Overwrite this in subclasses!
- availableOptions = {}
+ availableOptions = {} # type: Dict[str, Any]
def __init__(self, **kwargs):
"""
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index a6ef6ef..2c67120 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -48,6 +48,7 @@
from locale import getdefaultlocale
from os import getenv, environ
from textwrap import fill
+from typing import Dict, List, Tuple
from warnings import warn
from pywikibot import __version__ as pwb_version
@@ -133,7 +134,7 @@
# usernames['wikibooks']['*'] = 'mySingleUsername'
# You may use '*' for family name in a similar manner.
#
-usernames = collections.defaultdict(dict)
+usernames = collections.defaultdict(dict) # type: Dict[str, Dict[str, str]]
disambiguation_comment = collections.defaultdict(dict)
# User agent format.
@@ -441,7 +442,7 @@
# this can be used to pass variables to the UI init function
# useful for e.g.
# userinterface_init_kwargs = {'default_stream': 'stdout'}
-userinterface_init_kwargs = {}
+userinterface_init_kwargs = {} # type: Dict[str, str]
# i18n setting for user interface language
# default is obtained from L{locale.getdefaultlocale}
@@ -552,7 +553,7 @@
#
# sample:
# user_script_paths = ['scripts.myscripts']
-user_script_paths = []
+user_script_paths = [] # type: List[str]
# ############# INTERWIKI SETTINGS ##############
@@ -807,7 +808,7 @@
# (if cosmetic_changes_mylang_only is set)
# Please set your dictionary by adding such lines to your user-config.py:
# cosmetic_changes_enable['wikipedia'] = ('de', 'en', 'fr')
-cosmetic_changes_enable = {}
+cosmetic_changes_enable = {} # type: Dict[str, Tuple[str, ...]]
# The dictionary cosmetic_changes_disable should contain a tuple of languages
# for each site where you wish to disable cosmetic changes. You may use it with
@@ -815,7 +816,7 @@
# language. This also overrides the settings in the cosmetic_changes_enable
# dictionary. Please set your dict by adding such lines to your user-config.py:
# cosmetic_changes_disable['wikipedia'] = ('de', 'en', 'fr')
-cosmetic_changes_disable = {}
+cosmetic_changes_disable = {} # type: Dict[str, Tuple[str, ...]]
# cosmetic_changes_deny_script is a list of scripts for which cosmetic changes
# are disabled. You may add additional scripts by appending script names in
@@ -837,7 +838,7 @@
#
# to replace all occurrences of 'Hoofdpagina' with 'Veurblaad' when writing to
# liwiki. Note that this does not take the origin wiki into account.
-replicate_replace = {}
+replicate_replace = {} # type: Dict[str, Dict[str, str]]
# ############# FURTHER SETTINGS ##############
@@ -847,7 +848,7 @@
# on the wiki server. Allows simulation runs of bots to be carried out without
# changing any page on the server side. Use this setting to add more actions
# in user-config.py for wikis with extra write actions.
-actions_to_block = []
+actions_to_block = [] # type: List[str]
# Set simulate to True or use -simulate option to block all actions given
# above.
@@ -1117,7 +1118,7 @@
if userinterface_lang is None:
userinterface_lang = os.getenv('PYWIKIBOT_USERINTERFACE_LANG') \
or getdefaultlocale()[0]
- if userinterface_lang in [None, 'C']:
+ if userinterface_lang is None or userinterface_lang == 'C':
userinterface_lang = 'en'
else:
userinterface_lang = userinterface_lang.split('_')[0]
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index a16de1b..9bbe2b7 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -87,6 +87,8 @@
#
# Distributed under the terms of the MIT license.
#
+from typing import Optional
+
from pywikibot.tools import _NotImplementedWarning
@@ -133,12 +135,11 @@
Page, and when a generic message can be written once for all.
"""
- # Preformatted UNICODE message where the page title will be inserted
+ # Preformatted message where the page title will be inserted.
# Override this in subclasses.
- # 'Oh noes! Page %s is too funky, we should not delete it ;('
- message = None
+ message = ''
- def __init__(self, page, message=None):
+ def __init__(self, page, message: Optional[str] = None):
"""
Initializer.
diff --git a/pywikibot/family.py b/pywikibot/family.py
index 3641c68..8df90ce 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -16,6 +16,7 @@
from importlib import import_module
from itertools import chain
from os.path import basename, dirname, splitext
+from typing import Dict, List, Optional, Tuple
import pywikibot
from pywikibot.comms.http import fetch
@@ -78,7 +79,7 @@
name = None
- langs = {}
+ langs = {} # type: Dict[str, str]
# For interwiki sorting order see
# https://meta.wikimedia.org/wiki/Interwiki_sorting_order
@@ -471,12 +472,12 @@
# A dict of tuples for different sites with names of templates
# that indicate an edit should be avoided
- edit_restricted_templates = {}
+ edit_restricted_templates = {} # type: Dict[str, Tuple[str, ...]]
# A dict of tuples for different sites with names of archive
# templates that indicate an edit of non-archive bots
# should be avoided
- archived_page_templates = {}
+ archived_page_templates = {} # type: Dict[str, Tuple[str, ...]]
# A list of projects that share cross-project sessions.
cross_projects = []
@@ -489,38 +490,38 @@
cross_projects_cookie_username = 'centralauth_User'
# A list with the name in the cross-language flag permissions
- cross_allowed = []
+ cross_allowed = [] # type: List[str]
# A dict with the name of the category containing disambiguation
# pages for the various languages. Only one category per language,
# and without the namespace, so add things like:
# 'en': "Disambiguation"
- disambcatname = {}
+ disambcatname = {} # type: Dict[str, str]
# DEPRECATED, stores the code of the site which have a case sensitive
# main namespace. Use the Namespace given from the Site instead
- nocapitalize = []
+ nocapitalize = [] # type: List[str]
# attop is a list of languages that prefer to have the interwiki
# links at the top of the page.
- interwiki_attop = []
+ interwiki_attop = [] # type: List[str]
# on_one_line is a list of languages that want the interwiki links
# one-after-another on a single line
- interwiki_on_one_line = []
+ interwiki_on_one_line = [] # type: List[str]
# String used as separator between interwiki links and the text
interwiki_text_separator = '\n\n'
# Similar for category
- category_attop = []
+ category_attop = [] # type: List[str]
# on_one_line is a list of languages that want the category links
# one-after-another on a single line
- category_on_one_line = []
+ category_on_one_line = [] # type: List[str]
# String used as separator between category links and the text
category_text_separator = '\n\n'
# When both at the bottom should categories come after interwikilinks?
# TODO: T86284 Needed on Wikia sites, as it uses the CategorySelect
# extension which puts categories last on all sites. TO BE DEPRECATED!
- categories_last = []
+ categories_last = [] # type: List[str]
# Which languages have a special order for putting interlanguage
# links, and what order is it? If a language is not in
@@ -546,15 +547,15 @@
# Which language codes no longer exist and by which language code
# should they be replaced. If for example the language with code xx:
# now should get code yy:, add {'xx':'yy'} to obsolete.
- interwiki_replacements = {}
+ interwiki_replacements = {} # type: Dict[str, str]
# Codes that should be removed, usually because the site has been
# taken down.
- interwiki_removals = []
+ interwiki_removals = [] # type: List[str]
# Language codes of the largest wikis. They should be roughly sorted
# by size.
- languages_by_size = []
+ languages_by_size = [] # type: List[str]
# Some languages belong to a group where the possibility is high that
# equivalent articles have identical titles among the group.
@@ -646,7 +647,7 @@
# Some wiki farms have UrlShortener extension enabled only on the main
# site. This value can specify this last one with (lang, family) tuple.
- shared_urlshortner_wiki = None
+ shared_urlshortner_wiki = None # type: Optional[Tuple[str, str]]
_families = {}
@@ -1364,9 +1365,9 @@
}
# Not open for edits; stewards can still edit.
- closed_wikis = []
+ closed_wikis = [] # type: List[str]
# Completely removed
- removed_wikis = []
+ removed_wikis = [] # type: List[str]
# WikimediaFamily uses wikibase for the category name containing
# disambiguation pages for the various languages. We need the
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py
index b4dd0e7..61a31a3 100644
--- a/pywikibot/logentries.py
+++ b/pywikibot/logentries.py
@@ -6,6 +6,7 @@
# Distributed under the terms of the MIT license.
#
from collections import UserDict
+from typing import Optional
import pywikibot
from pywikibot.exceptions import Error, HiddenKeyError
@@ -29,7 +30,7 @@
# Log type expected. None for every type, or one of the (letype) str :
# block/patrol/etc...
# Overridden in subclasses.
- _expected_type = None
+ _expected_type = None # type: Optional[str]
def __init__(self, apidata, site):
"""Initialize object from a logevent dict returned by MW API."""
diff --git a/pywikibot/page/__init__.py b/pywikibot/page/__init__.py
index 9c5054a..b5e8495 100644
--- a/pywikibot/page/__init__.py
+++ b/pywikibot/page/__init__.py
@@ -27,7 +27,7 @@
from collections.abc import MutableMapping
from html.entities import name2codepoint
from itertools import chain
-from typing import List, Optional, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import quote_from_bytes, unquote_to_bytes
from warnings import warn
@@ -3855,7 +3855,6 @@
@cvar DATA_ATTRIBUTES: dictionary which maps data attributes (eg. 'labels',
'claims') to appropriate collection classes (eg. LanguageDict,
ClaimsCollection)
- @type DATA_ATTRIBUTES: dict
@cvar entity_type: entity type identifier
@type entity_type: str
@@ -3865,7 +3864,7 @@
@type title_pattern: str
"""
- DATA_ATTRIBUTES = {}
+ DATA_ATTRIBUTES = {} # type: Dict[str, Any]
def __init__(self, repo, id_=None):
"""
diff --git a/pywikibot/tools/formatter.py b/pywikibot/tools/formatter.py
index 7ee318b..6ab45cb 100644
--- a/pywikibot/tools/formatter.py
+++ b/pywikibot/tools/formatter.py
@@ -8,7 +8,7 @@
import math
from string import Formatter
-from typing import Sequence
+from typing import Any, Mapping, Sequence
from pywikibot.logging import output
from pywikibot.userinterfaces.terminal_interface_base import colors
@@ -104,7 +104,8 @@
if previous_literal:
yield previous_literal, None, None, None
- def vformat(self, format_string: str, args: Sequence, kwargs: dict) -> str:
+ def vformat(self, format_string: str, args: Sequence,
+ kwargs: Mapping[str, Any]) -> str:
"""Return the format result but verify no colors are keywords.
@param format_string: The format template string
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/623030
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I79525d21778cdbce8032df49ebf2d716b1eee5d5
Gerrit-Change-Number: 623030
Gerrit-PatchSet: 3
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Zhuyifei1999 <zhuyifei1999(a)gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/625361 )
Change subject: [4.0] Use Python 3 code in api.py
......................................................................
[4.0] Use Python 3 code in api.py
Change-Id: If95de958d66cf46e30e96846537b4ac44aac2963
---
M pywikibot/data/api.py
1 file changed, 85 insertions(+), 152 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index fd72de1..516731d 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -16,11 +16,13 @@
import traceback
from collections.abc import Container, MutableMapping, Sized
+from contextlib import suppress
from email.generator import BytesGenerator
from email.mime.multipart import MIMEMultipart as MIMEMultipartOrig
from email.mime.nonmultipart import MIMENonMultipart
from inspect import getfullargspec
from io import BytesIO
+from typing import Optional, Set, Tuple, Union
from warnings import warn
from urllib.parse import urlencode, unquote
@@ -74,14 +76,14 @@
def __init__(self, *args, **kwargs):
"""Initializer."""
- super(CTEBinaryBytesGenerator, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self._writeBody = self._write_body
def _write_body(self, msg):
if msg['content-transfer-encoding'] == 'binary':
self._fp.write(msg.get_payload(decode=True))
else:
- super(CTEBinaryBytesGenerator, self)._handle_text(msg)
+ super()._handle_text(msg)
class CTEBinaryMIMEMultipart(MIMEMultipartOrig):
@@ -133,18 +135,18 @@
"""Upload failed with a warning message (passed as the argument)."""
- def __init__(self, code, message, file_key=None, offset=0):
+ def __init__(self, code, message,
+ file_key: Optional[str] = None,
+ offset: Union[int, bool] = 0):
"""
Create a new UploadWarning instance.
@param filekey: The filekey of the uploaded file to reuse it later. If
no key is known or it is an incomplete file it may be None.
- @type filekey: str or None
@param offset: The starting offset for a chunked upload. Is False when
there is no offset.
- @type offset: int or bool
"""
- super(UploadWarning, self).__init__(code, message)
+ super().__init__(code, message)
self.file_key = file_key
self.offset = offset
@@ -162,7 +164,7 @@
"""Save error dict returned by MW API."""
self.mediawiki_exception_class_name = mediawiki_exception_class_name
code = 'internal_api_error_' + mediawiki_exception_class_name
- super(APIMWException, self).__init__(code, info, **kwargs)
+ super().__init__(code, info, **kwargs)
class ParamInfo(Sized, Container):
@@ -351,17 +353,16 @@
}
@staticmethod
- def _modules_to_set(modules):
+ def _modules_to_set(modules) -> set:
"""Return modules as a set.
- @type modules: iterable or basestring
- @rtype: set
+ @type modules: iterable or str
"""
if isinstance(modules, str):
return set(modules.split('|'))
return set(modules)
- def fetch(self, modules):
+ def fetch(self, modules) -> None:
"""
Fetch paraminfo for multiple modules.
@@ -370,7 +371,6 @@
@param modules: API modules to load
@type modules: iterable or str
- @rtype: NoneType
"""
if 'main' not in self._paraminfo:
# The first request should be 'paraminfo', so that
@@ -391,13 +391,11 @@
self._fetch(modules)
- def _fetch(self, modules):
+ def _fetch(self, modules: set) -> None:
"""
Fetch paraminfo for multiple modules without initializing beforehand.
@param modules: API modules to load and which haven't been loaded yet.
- @type modules: set
- @rtype: NoneType
"""
def module_generator():
"""A generator yielding batches of modules."""
@@ -553,7 +551,7 @@
assert param['name'] == 'generator' and \
submodules >= set(param['type'])
- def _normalize_modules(self, modules):
+ def _normalize_modules(self, modules) -> set:
"""Add query+ to any query module name not also in action modules."""
# Users will supply the wrong type, and expect it to work.
modules = self._modules_to_set(modules)
@@ -566,14 +564,13 @@
else mod
for mod in modules}
- def normalize_modules(self, modules):
+ def normalize_modules(self, modules) -> set:
"""
Convert the modules into module paths.
Add query+ to any query module name not also in action modules.
@return: The modules converted into a module paths
- @rtype: set
"""
self._init()
return self._normalize_modules(modules)
@@ -657,7 +654,7 @@
else:
raise KeyError(key)
- def __contains__(self, key):
+ def __contains__(self, key) -> bool:
"""Return whether the key is valid."""
try:
self[key]
@@ -665,22 +662,19 @@
except KeyError:
return False
- def __len__(self):
+ def __len__(self) -> int:
"""Return number of cached modules."""
return len(self._paraminfo)
- def parameter(self, module, param_name):
+ def parameter(self, module: str, param_name: str) -> Optional[dict]:
"""
Get details about one modules parameter.
Returns None if the parameter does not exist.
@param module: API module name
- @type module: str
@param param_name: parameter name in the module
- @type param_name: str
@return: metadata that describes how the parameter may be used
- @rtype: dict or None
"""
# TODO: the 'description' field of each parameter is not in the default
# output of v1.25, and can't removed from previous API versions.
@@ -711,7 +705,7 @@
@property
@deprecated('submodules() or module_paths', since='20150715')
- def modules(self):
+ def modules(self) -> Set[str]:
"""
Set of all main and query modules without path prefixes.
@@ -719,7 +713,6 @@
the action modules and query modules).
@return: module names
- @rtype: set of str
"""
return self.action_modules | self.query_modules
@@ -751,16 +744,13 @@
"""Set of all query module names without query+ path prefix."""
return self.submodules('query')
- def submodules(self, name, path=False):
+ def submodules(self, name: str, path: bool = False) -> set:
"""
Set of all submodules.
@param name: The name of the parent module.
- @type name: str
@param path: Whether the path and not the name is returned.
- @type path: bool
@return: The names or paths of the submodules.
- @rtype: set
"""
if name not in self._modules:
self.fetch([name])
@@ -772,7 +762,7 @@
@staticmethod
def _prefix_submodules(modules, prefix):
"""Prefix submodules with path."""
- return {'{0}+{1}'.format(prefix, mod) for mod in modules}
+ return {'{}+{}'.format(prefix, mod) for mod in modules}
@property
@deprecated('prefix_map', since='20150715')
@@ -800,7 +790,7 @@
if prefix}
return self._prefix_map.copy()
- def attributes(self, attribute, modules=None):
+ def attributes(self, attribute: str, modules: Optional[set] = None):
"""
Mapping of modules with an attribute to the attribute value.
@@ -808,10 +798,8 @@
attribute is empty or set to False.
@param attribute: attribute name
- @type attribute: basestring
@param modules: modules to include. If None (default), it'll load all
modules including all submodules using the paths.
- @type modules: set or None
@rtype: dict using modules as keys
"""
if modules is None:
@@ -822,17 +810,16 @@
for mod in modules if attribute in self[mod]}
@deprecated('attributes', since='20150715')
- def module_attribute_map(self, attribute, modules=None):
+ def module_attribute_map(self, attribute: str,
+ modules: Optional[set] = None):
"""
Mapping of modules with an attribute to the attribute value.
@param attribute: attribute name
- @type attribute: basestring
@param modules: modules to include. If None (default) it'll load all
action and query modules using the module names. It only uses the
path for query modules which have the same name as an action
module.
- @type modules: set
@rtype: dict using modules as keys
"""
if modules is None:
@@ -868,22 +855,22 @@
None and after setting it, any site (even None) will fail.
"""
- def __init__(self, site=None, module=None, param=None, dict=None):
+ def __init__(self, site=None,
+ module: Optional[str] = None,
+ param: Optional[str] = None,
+ dict: Optional[dict] = None):
"""
Initializer.
If a site is given, the module and param must be given too.
@param site: The associated site
- @type site: piwikibot.site.APISite
+ @type site: pywikibot.site.APISite or None
@param module: The module name which is used by paraminfo. (Ignored
when site is None)
- @type module: str
@param param: The parameter name inside the module. That parameter must
have a 'type' entry. (Ignored when site is None)
- @type param: str
@param dict: The initializing dict which is used for L{from_dict}.
- @type dict: dict
"""
self._site_set = False
self._enabled = set()
@@ -892,9 +879,9 @@
if dict:
self.from_dict(dict)
- def _set_site(self, site, module, param, clear_invalid=False):
- """
- Set the site and valid names.
+ def _set_site(self, site, module: str, param: str,
+ clear_invalid: bool = False):
+ """Set the site and valid names.
As soon as the site has been not None, any subsequent calls will fail,
unless there had been invalid names and a KeyError was thrown.
@@ -902,13 +889,10 @@
@param site: The associated site
@type site: pywikibot.site.APISite
@param module: The module name which is used by paraminfo.
- @type module: str
@param param: The parameter name inside the module. That parameter must
have a 'type' entry.
- @type param: str
@param clear_invalid: Instead of throwing a KeyError, invalid names are
silently removed from the options (disabled by default).
- @type clear_invalid: bool
"""
if self._site_set:
raise TypeError('The site can not be set multiple times.')
@@ -997,7 +981,7 @@
else:
raise ValueError('Invalid value "{0}"'.format(value))
- def __getitem__(self, name):
+ def __getitem__(self, name) -> Optional[bool]:
"""
Return whether the option is enabled.
@@ -1005,7 +989,6 @@
Otherwise it returns None. If the site has been set it raises a
KeyError if the name is invalid. Otherwise it might return a value
even though the name might be invalid.
- @rtype: bool/None
"""
if name in self._enabled:
return True
@@ -1026,15 +1009,12 @@
def __iter__(self):
"""Iterate over each enabled and disabled option."""
- for enabled in self._enabled:
- yield enabled
- for disabled in self._disabled:
- yield disabled
+ yield from self._enabled
+ yield from self._disabled
def api_iter(self):
"""Iterate over each option as they appear in the URL."""
- for enabled in self._enabled:
- yield enabled
+ yield from self._enabled
for disabled in self._disabled:
yield '!{0}'.format(disabled)
@@ -1130,8 +1110,6 @@
so that when the API parameters are modified the changes can always be
applied to the 'parameters' parameter.
- @param parameters: The parameters used for the request to the API.
- @type parameters: dict
@param site: The Site to which the request will be submitted. If not
supplied, uses the user's configured default Site.
@param mime: If true, send in "multipart/form-data" format (default
@@ -1150,6 +1128,8 @@
@param use_get: (optional) Use HTTP GET request if possible. If False
it uses a POST request. If None, it'll try to determine via
action=paraminfo if the action requires a POST.
+ @param parameters: The parameters used for the request to the API.
+ @type parameters: dict
@param kwargs: The parameters used for the request to the API.
"""
if site is None:
@@ -1277,7 +1257,7 @@
'"parameters" parameter.', DeprecationWarning, 3)
@classmethod
- def clean_kwargs(cls, kwargs):
+ def clean_kwargs(cls, kwargs: dict) -> dict:
"""
Convert keyword arguments into new parameters mode.
@@ -1287,9 +1267,7 @@
added as a 'parameters' keyword. It will always create a shallow copy.
@param kwargs: The original keyword arguments which is not modified.
- @type kwargs: dict
@return: The normalized keyword arguments.
- @rtype: dict
"""
if 'expiry' in kwargs and kwargs['expiry'] is None:
del kwargs['expiry']
@@ -1350,15 +1328,10 @@
"""Implement dict interface."""
return self._params[key]
- def __setitem__(self, key, value):
+ def __setitem__(self, key: str, value):
"""Set MediaWiki API request parameter.
- @param key: param key
-
- @type key: basestring
-
@param value: param value(s)
-
@type value: str in site encoding
(string types may be a `|`-separated list)
iterable, where items are converted to string
@@ -1641,16 +1614,15 @@
return use_get
@classmethod
- def _build_mime_request(cls, params, mime_params):
+ def _build_mime_request(cls, params: dict,
+ mime_params) -> Tuple[dict, str]:
"""
Construct a MIME multipart form post.
@param params: HTTP request params
- @type params: dict
@param mime_params: HTTP request parts which must be sent in the body
@type mime_params: dict of (content, keytype, headers)
@return: HTTP request headers and body
- @rtype: tuple (dict, str)
"""
# construct a MIME message containing all API key/values
container = MIMEMultipart(_subtype='form-data')
@@ -1694,11 +1666,10 @@
_logger)
return use_get, uri, body, headers
- def _http_request(self, use_get, uri, body, headers, paramstring):
+ def _http_request(self, use_get, uri, body, headers, paramstring) -> tuple:
"""Get or post a http request with exception handling.
@return: a tuple containing data from request and use_get value
- @rtype: tuple
"""
try:
data = http.request(
@@ -1729,13 +1700,11 @@
self.wait()
return None, use_get
- def _json_loads(self, data):
+ def _json_loads(self, data: str) -> dict:
"""Read source text and return a dict.
@param data: raw data string
- @type data: str
@return: a data dict
- @rtype: dict
@raises APIError: unknown action found
@raises APIError: unknown query result type
"""
@@ -1899,7 +1868,7 @@
'No rate limit found for action {}'.format(self.action))
self.wait(delay)
- def _bad_token(self, code):
+ def _bad_token(self, code) -> bool:
"""Check for bad token."""
if (code != 'badtoken' or self.site._loginstatus
== pywikibot.site.LoginStatus.IN_PROGRESS):
@@ -1940,12 +1909,11 @@
for e in user_tokens.items())))
return False
- def submit(self):
+ def submit(self) -> dict:
"""
Submit a query and parse the response.
@return: a dict containing data retrieved from api.php
- @rtype: dict
"""
self._add_defaults()
use_get = self._use_get()
@@ -2109,7 +2077,7 @@
@param expiry: either a number of days or a datetime.timedelta object
"""
assert expiry is not None
- super(CachedRequest, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if not isinstance(expiry, datetime.timedelta):
expiry = datetime.timedelta(expiry)
self.expiry = min(expiry, datetime.timedelta(config.API_config_expiry))
@@ -2122,14 +2090,13 @@
raise NotImplementedError('CachedRequest cannot be created simply.')
@classmethod
- def _get_cache_dir(cls):
+ def _get_cache_dir(cls) -> str:
"""
Return the base directory path for cache entries.
The directory will be created if it does not already exist.
@return: base directory path for cache entries
- @rtype: basestring
"""
path = os.path.join(pywikibot.config2.base_dir,
'apicache-py{0:d}'.format(PYTHON_VERSION[0]))
@@ -2138,33 +2105,24 @@
return path
@staticmethod
- def _make_dir(dir):
- """
- Create directory if it does not exist already.
+ def _make_dir(dir_name: str) -> str:
+ """Create directory if it does not exist already.
- The directory name (dir) is returned unmodified.
+ The directory name (dir_name) is returned unmodified.
- @param dir: directory path
- @type dir: basestring
-
+ @param dir_name: directory path
@return: directory name
- @rtype: basestring
"""
- try:
- os.makedirs(dir)
- except OSError:
- # directory already exists
- pass
- return dir
+ with suppress(OSError): # directory already exists
+ os.makedirs(dir_name)
+ return dir_name
- def _uniquedescriptionstr(self):
+ def _uniquedescriptionstr(self) -> str:
"""Return unique description for the cache entry.
If this is modified, please also update
scripts/maintenance/cache.py to support
the new key and all previous keys.
-
- @rtype: str
"""
login_status = self.site._loginstatus
@@ -2199,11 +2157,10 @@
def _expired(self, dt):
return dt + self.expiry < datetime.datetime.utcnow()
- def _load_cache(self):
+ def _load_cache(self) -> bool:
"""Load cache entry for request, if available.
@return: Whether the request was loaded from the cache
- @rtype: bool
"""
self._add_defaults()
try:
@@ -2214,9 +2171,9 @@
if self._expired(self._cachetime):
self._data = None
return False
- pywikibot.debug('%s: cache hit (%s) for API request: %s'
- % (self.__class__.__name__, filename, uniquedescr),
- _logger)
+ pywikibot.debug('{}: cache hit ({}) for API request: {}'
+ .format(self.__class__.__name__, filename,
+ uniquedescr), _logger)
return True
except IOError:
# file not found
@@ -2235,14 +2192,14 @@
"""Submit cached request."""
cached_available = self._load_cache()
if not cached_available:
- self._data = super(CachedRequest, self).submit()
+ self._data = super().submit()
self._write_cache(self._data)
else:
self._handle_warnings(self._data)
return self._data
-class _RequestWrapper(object):
+class _RequestWrapper:
"""A wrapper class to handle the usage of the C{parameters} parameter."""
@@ -2271,8 +2228,8 @@
after iterating that many values.
"""
- def __init__(self, action, continue_name='continue', limit_name='limit',
- data_name='data', **kwargs):
+ def __init__(self, action: str, continue_name: str = 'continue',
+ limit_name: str = 'limit', data_name: str = 'data', **kwargs):
"""
Initialize an APIGenerator object.
@@ -2280,13 +2237,9 @@
documentation for values.
@param action: API action name.
- @type action: str
@param continue_name: Name of the continue API parameter.
- @type continue_name: str
@param limit_name: Name of the limit API parameter.
- @type limit_name: str
@param data_name: Name of the data in API response.
- @type data_name: str
"""
kwargs = self._clean_kwargs(kwargs, action=action)
@@ -2303,7 +2256,7 @@
self.request = self.request_class(**kwargs)
self.request[self.limit_name] = self.query_increment
- def set_query_increment(self, value):
+ def set_query_increment(self, value: int):
"""
Set the maximum number of items to be retrieved per API query.
@@ -2311,7 +2264,6 @@
@param value: The value of maximum number of items to be retrieved
per API request to set.
- @type value: int
"""
self.query_increment = int(value)
self.request[self.limit_name] = self.query_increment
@@ -2319,7 +2271,7 @@
% (self.__class__.__name__, self.query_increment),
_logger)
- def set_maximum_items(self, value):
+ def set_maximum_items(self, value: Union[int, str, None]):
"""
Set the maximum number of items to be retrieved from the wiki.
@@ -2328,7 +2280,6 @@
@param value: The value of maximum number of items to be retrieved
in total to set. Ignores None value.
- @type value: int or str or None
"""
if value is not None and int(value) > 0:
self.limit = int(value)
@@ -2566,7 +2517,6 @@
If not called, the default is to ask for "max" items and let the
API decide how many to send.
-
"""
limit = int(value)
@@ -2579,7 +2529,7 @@
% (self.__class__.__name__, self.query_limit),
_logger)
- def set_maximum_items(self, value):
+ def set_maximum_items(self, value: Union[int, str, None]):
"""Set the maximum number of items to be retrieved from the wiki.
If not called, most queries will continue as long as there is
@@ -2592,7 +2542,6 @@
@param value: The value of maximum number of items to be retrieved
in total to set. Ignores None value.
- @type value: int or str or None
"""
if value is not None:
self.limit = int(value)
@@ -2612,7 +2561,7 @@
self.api_limit),
_logger)
- def support_namespace(self):
+ def support_namespace(self) -> bool:
"""Check if namespace is a supported parameter on this query.
Note: this function will be removed when self.set_namespace() will
@@ -2620,7 +2569,6 @@
See T196619.
@return: True if yes, False otherwise
- @rtype: bool
"""
assert(self.limited_module) # some modules do not have a prefix
return bool(
@@ -2631,9 +2579,9 @@
"""Set a namespace filter on this query.
@param namespaces: namespace identifiers to limit query results
- @type namespaces: iterable of basestring or Namespace key,
- or a single instance of those types. May be a '|' separated
- list of namespace identifiers. An empty iterator clears any
+ @type namespaces: iterable of str or Namespace key, or a single
+ instance of those types. May be a '|' separated list of
+ namespace identifiers. An empty iterator clears any
namespace restriction.
@raises KeyError: a namespace identifier was not resolved
@@ -2827,8 +2775,7 @@
else:
self.normalized = {}
try:
- for result in self._extract_results(resultdata):
- yield result
+ yield from self._extract_results(resultdata)
except RuntimeError:
return
# self.resultkey in data in last request.submit()
@@ -2873,7 +2820,7 @@
"""
- def __init__(self, generator, g_content=False, **kwargs):
+ def __init__(self, generator: str, g_content=False, **kwargs):
"""
Initializer.
@@ -2881,7 +2828,6 @@
action=query is assumed and generator is required.
@param generator: the "generator=" type from api.php
- @type generator: str
@param g_content: if True, retrieve the contents of the current
version of each Page (default False)
@@ -2963,7 +2909,7 @@
"""
- def __init__(self, prop, **kwargs):
+ def __init__(self, prop: str, **kwargs):
"""
Initializer.
@@ -2971,8 +2917,6 @@
action=query is assumed and prop is required.
@param prop: the "prop=" type from api.php
- @type prop: str
-
"""
kwargs = self._clean_kwargs(kwargs, prop=prop)
QueryGenerator.__init__(self, **kwargs)
@@ -2987,18 +2931,13 @@
def __iter__(self):
"""Yield results."""
self._previous_dicts = {}
- for result in super(PropertyGenerator, self).__iter__():
- yield result
- for result in self._previous_dicts.values():
- yield result
+ yield from super().__iter__()
+ yield from self._previous_dicts.values()
def _extract_results(self, resultdata):
"""Yield completed page_data of consecutive API requests."""
- for d in self._fully_retrieved_data_dicts(resultdata):
- yield d
- for data_dict in super(PropertyGenerator, self)._extract_results(
- resultdata
- ):
+ yield from self._fully_retrieved_data_dicts(resultdata)
+ for data_dict in super()._extract_results(resultdata):
if 'title' in data_dict:
d = self._previous_dicts.setdefault(data_dict['title'],
data_dict)
@@ -3046,7 +2985,7 @@
"""
- def __init__(self, listaction, **kwargs):
+ def __init__(self, listaction: str, **kwargs):
"""
Initializer.
@@ -3054,8 +2993,6 @@
action=query is assumed and listaction is required.
@param listaction: the "list=" type from api.php
- @type listaction: str
-
"""
kwargs = self._clean_kwargs(kwargs, list=listaction)
QueryGenerator.__init__(self, **kwargs)
@@ -3116,7 +3053,6 @@
future.
@return: empty string if successful, throws exception on failure
-
"""
if hasattr(self, '_waituntil'):
if datetime.datetime.now() < self._waituntil:
@@ -3220,13 +3156,12 @@
"""Ignore data; cookies are set by threadedhttp module."""
http.cookie_jar.save()
- def get_login_token(self):
+ def get_login_token(self) -> str:
"""Fetch login token from action=query&meta=tokens.
Requires MediaWiki >= 1.27.
@return: login token
- @rtype: str
"""
if self.site.mw_version < '1.27':
raise NotImplementedError('The method get_login_token() requires '
@@ -3239,7 +3174,7 @@
return login_token_result['query']['tokens'].get('logintoken')
-def encode_url(query):
+def encode_url(query) -> str:
"""
Encode parameters to pass with a url.
@@ -3251,7 +3186,6 @@
@param query: keys and values to be uncoded for passing with a url
@type query: mapping object or a sequence of two-element tuples
@return: encoded parameters with token parameters at the end
- @rtype: str
"""
if hasattr(query, 'items'):
query = list(query.items())
@@ -3263,7 +3197,7 @@
return urlencode(query)
-def _update_pageid(page, pagedict):
+def _update_pageid(page, pagedict: dict):
"""Update pageid."""
if 'pageid' in pagedict:
page._pageid = int(pagedict['pageid'])
@@ -3282,7 +3216,7 @@
.format(pagedict['title']))
-def _update_contentmodel(page, pagedict):
+def _update_contentmodel(page, pagedict: dict):
"""Update page content model."""
page._contentmodel = pagedict.get('contentmodel') # can be None
@@ -3293,7 +3227,7 @@
page._quality_text = pagedict['proofread']['quality_text']
-def _update_protection(page, pagedict):
+def _update_protection(page, pagedict: dict):
"""Update page protection."""
if 'restrictiontypes' in pagedict:
page._applicable_protections = set(pagedict['restrictiontypes'])
@@ -3361,13 +3295,12 @@
page._coords = coords
-def update_page(page, pagedict, props=[]):
+def update_page(page, pagedict: dict, props=[]):
"""Update attributes of Page object page, based on query data in pagedict.
@param page: object to be updated
@type page: pywikibot.page.Page
@param pagedict: the contents of a "page" element of a query response
- @type pagedict: dict
@param props: the property names which resulted in pagedict. If a missing
value in pagedict can indicate both 'false' and 'not present' the
property which would make the value present must be in the props
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/625361
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: If95de958d66cf46e30e96846537b4ac44aac2963
Gerrit-Change-Number: 625361
Gerrit-PatchSet: 7
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/625165 )
Change subject: [doc] Update ROADMAP.rst
......................................................................
[doc] Update ROADMAP.rst
Also increase version number
Change-Id: Ib1c5c40c97100d50f8c0f21458c6f87efcf24703
---
M ROADMAP.rst
M pywikibot/__metadata__.py
2 files changed, 4 insertions(+), 3 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/ROADMAP.rst b/ROADMAP.rst
index 9561aaa..898d948 100644
--- a/ROADMAP.rst
+++ b/ROADMAP.rst
@@ -1,17 +1,18 @@
Current release changes
~~~~~~~~~~~~~~~~~~~~~~~
-* (no changes yet)
+* Allow multiple types of contributors parameter given for Page.revision_count()
+* Deprecated tools.UnicodeMixin and tools.IteratorNextMixin has been removed
Future release notes
~~~~~~~~~~~~~~~~~~~~
+* 4.3.0: Deprecated Page.contributingUsers() will be removed
* 4.2.0: tools.StringTypes will be removed
* 4.1.0: Deprecated editor.command will be removed
* 4.1.0: tools.open_compressed, tools.UnicodeType and tools.signature will be removed
* 4.1.0: comms.PywikibotCookieJar and comms.mode_check_decorator will be removed
-* 4.0.0: Deprecated tools.UnicodeMixin and tools.IteratorNextMixin will be removed
* 4.0.0: Unused parameters of page methods like forceReload, insite, throttle, step will be removed
* 4.0.0: Methods deprecated for 6 years or longer will be removed
* 3.0.20200508: Page.getVersionHistory and Page.fullVersionHistory() methods will be removed (T136513, T151110)
diff --git a/pywikibot/__metadata__.py b/pywikibot/__metadata__.py
index c87a503..aae903c 100644
--- a/pywikibot/__metadata__.py
+++ b/pywikibot/__metadata__.py
@@ -6,7 +6,7 @@
# Distributed under the terms of the MIT license.
#
__name__ = 'pywikibot'
-__version__ = '4.3.1.dev0'
+__version__ = '4.4.0.dev0'
__description__ = 'Python MediaWiki Bot Framework'
__maintainer__ = 'The Pywikibot team'
__maintainer_email__ = 'pywikibot(a)lists.wikimedia.org'
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/625165
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ib1c5c40c97100d50f8c0f21458c6f87efcf24703
Gerrit-Change-Number: 625165
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/623108 )
Change subject: [doc] Add typing hints to pywikibot/page/__init__.py
......................................................................
[doc] Add typing hints to pywikibot/page/__init__.py
Change-Id: I0c241a2f8cab3b34a7d4a4571e71ab3cbfcd5330
---
M pywikibot/page/__init__.py
1 file changed, 149 insertions(+), 360 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page/__init__.py b/pywikibot/page/__init__.py
index 5ac4990..f7380d2 100644
--- a/pywikibot/page/__init__.py
+++ b/pywikibot/page/__init__.py
@@ -27,6 +27,7 @@
from collections.abc import MutableMapping
from html.entities import name2codepoint
from itertools import chain
+from typing import List, Optional, Tuple, Union
from urllib.parse import quote_from_bytes, unquote_to_bytes
from warnings import warn
@@ -267,12 +268,11 @@
return self._depth
@property
- def pageid(self):
+ def pageid(self) -> int:
"""
Return pageid of the page.
@return: pageid or 0 if page does not exist
- @rtype: int
"""
if not hasattr(self, '_pageid'):
self.site.loadpageinfo(self)
@@ -370,14 +370,12 @@
return title
@remove_last_args(('decode', 'underscore'))
- def section(self):
+ def section(self) -> Optional[str]:
"""
Return the name of the section this Page refers to.
The section is the part of the title following a '#' character, if
any. If no section is present, return None.
-
- @rtype: str or None
"""
try:
section = self._link.section
@@ -385,11 +383,11 @@
section = None
return section
- def __str__(self):
+ def __str__(self) -> str:
"""Return a string representation."""
return self.title(as_link=True, force_interwiki=True)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return a more complete string representation."""
return '{}({!r})'.format(self.__class__.__name__, self.title())
@@ -444,7 +442,7 @@
@deprecated_args(throttle=True,
change_edit_time=True,
expandtemplates=True)
- def get(self, force=False, get_redirect=False):
+ def get(self, force=False, get_redirect=False) -> str:
"""
Return the wiki-text of the page.
@@ -462,7 +460,6 @@
@param force: reload all page attributes, including errors.
@param get_redirect: return the redirect text, do not follow the
redirect, do not raise an exception.
- @rtype: str
"""
if force:
del self.latest_revision_id
@@ -509,12 +506,11 @@
@remove_last_args(['sysop'])
@deprecated_args(throttle=True, change_edit_time=True)
- def getOldVersion(self, oldid, force=False, get_redirect=False):
+ def getOldVersion(self, oldid, force=False, get_redirect=False) -> str:
"""
Return text of an old revision of this page; same options as get().
@param oldid: The revid of the revision desired.
- @rtype: str
"""
if force or oldid not in self._revisions \
or self._revisions[oldid].text is None:
@@ -524,7 +520,8 @@
# TODO: what about redirects, errors?
return self._revisions[oldid].text
- def permalink(self, oldid=None, percent_encoded=True, with_protocol=False):
+ def permalink(self, oldid=None, percent_encoded: bool = True,
+ with_protocol: bool = False) -> str:
"""Return the permalink URL of an old revision of this page.
@param oldid: The revid of the revision desired.
@@ -532,7 +529,6 @@
without title uncoded.
@param with_protocol: if true, http or https prefixes will be
included before the double slash.
- @rtype: str
"""
if percent_encoded:
title = self.title(as_url=True)
@@ -602,12 +598,11 @@
return next(self.revisions(content=True, total=1))
@property
- def text(self):
+ def text(self) -> str:
"""
Return the current (edited) wikitext, loading it if necessary.
@return: text of the page
- @rtype: str
"""
if getattr(self, '_text', None) is not None:
return self._text
@@ -638,7 +633,7 @@
if hasattr(self, '_raw_extracted_templates'):
del self._raw_extracted_templates
- def preloadText(self):
+ def preloadText(self) -> str:
"""
The text returned by EditFormPreloadText.
@@ -646,8 +641,6 @@
Application: on Wikisource wikis, text can be preloaded even if
a page does not exist, if an Index page is present.
-
- @rtype: str
"""
self.site.loadpageinfo(self, preload=True)
return self._preloadedtext
@@ -659,26 +652,22 @@
self._parsed_text = self.site.get_parsed_page(self)
return self._parsed_text
- def properties(self, force=False):
+ def properties(self, force=False) -> dict:
"""
Return the properties of the page.
@param force: force updating from the live site
-
- @rtype: dict
"""
if not hasattr(self, '_pageprops') or force:
self._pageprops = {} # page may not have pageprops (see T56868)
self.site.loadpageprops(self)
return self._pageprops
- def defaultsort(self, force=False):
+ def defaultsort(self, force=False) -> Optional[str]:
"""
Extract value of the {{DEFAULTSORT:}} magic word from the page.
@param force: force updating from the live site
-
- @rtype: str or None
"""
return self.properties(force=force).get('defaultsort')
@@ -702,20 +691,12 @@
includecomments=includecomments)
return self._expanded_text
- def userName(self):
- """
- Return name or IP address of last user to edit page.
-
- @rtype: str
- """
+ def userName(self) -> str:
+ """Return name or IP address of last user to edit page."""
return self.latest_revision.user
- def isIpEdit(self):
- """
- Return True if last editor was unregistered.
-
- @rtype: bool
- """
+ def isIpEdit(self) -> bool:
+ """Return True if last editor was unregistered."""
return self.latest_revision.anon
def lastNonBotUser(self):
@@ -753,39 +734,33 @@
@property
@deprecated('latest_revision.parent_id (0 instead of -1 when no parent)',
since='20150609')
- def previous_revision_id(self):
+ def previous_revision_id(self) -> int:
"""
Return the revision id for the previous revision of this Page.
If the page has only one revision, it shall return -1.
- @rtype: int
-
@raise AssertionError: Use on MediaWiki prior to v1.16.
"""
return self.latest_revision.parent_id or -1
@deprecated('latest_revision.parent_id (0 instead of -1 when no parent)',
since='20150609')
- def previousRevision(self):
+ def previousRevision(self) -> int:
"""
Return the revision id for the previous revision.
DEPRECATED: Use latest_revision.parent_id instead.
- @rtype: int
-
@raise AssertionError: Use on MediaWiki prior to v1.16.
"""
return self.latest_revision.parent_id or -1
- def exists(self):
+ def exists(self) -> bool:
"""Return True if page exists on the wiki, even if it's a redirect.
If the title includes a section, return False if this section isn't
found.
-
- @rtype: bool
"""
return self.pageid > 0
@@ -802,17 +777,14 @@
"""Return True if this is a redirect, False if not or not existing."""
return self.site.page_isredirect(self)
- def isStaticRedirect(self, force=False):
+ def isStaticRedirect(self, force: bool = False) -> bool:
"""
Determine whether the page is a static redirect.
- A static redirect must be a valid redirect, and contain the magic word
- __STATICREDIRECT__.
+ A static redirect must be a valid redirect, and contain the magic
+ word __STATICREDIRECT__.
@param force: Bypass local caching
- @type force: bool
-
- @rtype: bool
"""
found = False
if self.isRedirectPage():
@@ -825,12 +797,8 @@
break
return found
- def isCategoryRedirect(self):
- """
- Return True if this is a category redirect page, False otherwise.
-
- @rtype: bool
- """
+ def isCategoryRedirect(self) -> bool:
+ """Return True if this is a category redirect page, False otherwise."""
if not self.is_categorypage():
return False
if not hasattr(self, '_catredirect'):
@@ -867,14 +835,12 @@
raise pywikibot.IsNotRedirectPage(self)
@deprecated(since='20151207')
- def isEmpty(self):
+ def isEmpty(self) -> bool:
"""
Return True if the page text has less than 4 characters.
Character count ignores language links and category links.
Can raise the same exceptions as get().
-
- @rtype: bool
"""
txt = self.get()
txt = textlib.removeLanguageLinks(txt, site=self.site)
@@ -924,8 +890,8 @@
"""DEPRECATED: use is_filepage instead."""
return self.is_filepage()
- @remove_last_args(('get_Index', ))
- def isDisambig(self):
+ @remove_last_args(['get_Index'])
+ def isDisambig(self) -> bool:
"""
Return True if this is a disambiguation page, False otherwise.
@@ -941,8 +907,6 @@
exist, take the MediaWiki message. 'Template:Disambig' is always
assumed to be default, and will be appended regardless of its
existence.
-
- @rtype: bool
"""
if self.site.has_extension('Disambiguator'):
# If the Disambiguator extension is loaded, use it
@@ -1075,12 +1039,8 @@
content=content
)
- def protection(self):
- """
- Return a dictionary reflecting page protections.
-
- @rtype: dict
- """
+ def protection(self) -> dict:
+ """Return a dictionary reflecting page protections."""
return self.site.page_restrictions(self)
def applicable_protections(self) -> set:
@@ -1110,16 +1070,13 @@
p_types.remove('upload')
return p_types
- def has_permission(self, action='edit'):
+ def has_permission(self, action: str = 'edit') -> bool:
"""Determine whether the page can be modified.
Return True if the bot has the permission of needed restriction level
for the given action type.
@param action: a valid restriction type like 'edit', 'move'
- @type action: str
- @rtype: bool
-
@raises ValueError: invalid action parameter
"""
return self.site.page_can_be_edited(self, action)
@@ -1129,7 +1086,7 @@
"""DEPRECATED. Determine whether the page may be edited."""
return self.has_permission()
- def botMayEdit(self):
+ def botMayEdit(self) -> bool:
"""
Determine whether the active bot is allowed to edit the page.
@@ -1143,8 +1100,6 @@
The framework enforces this restriction by default. It is possible
to override this by setting ignore_bot_templates=True in
user-config.py, or using page.put(force=True).
-
- @rtype: bool
"""
if not hasattr(self, 'templatesWithParams'):
return True
@@ -1320,14 +1275,12 @@
if not quiet:
pywikibot.output('Page %s saved' % link)
- def _cosmetic_changes_hook(self, summary):
+ def _cosmetic_changes_hook(self, summary: str) -> str:
"""The cosmetic changes hook.
@param summary: The current edit summary.
- @type summary: str
@return: Modified edit summary if cosmetic changes has been done,
else the old edit summary.
- @rtype: str
"""
if self.isTalkPage() or self.content_model != 'wikitext' or \
pywikibot.calledModuleName() in config.cosmetic_changes_deny_script:
@@ -1396,15 +1349,12 @@
minor=minor, botflag=botflag, force=force,
asynchronous=True, callback=callback, **kwargs)
- def watch(self, unwatch=False):
+ def watch(self, unwatch: bool = False) -> bool:
"""
Add or remove this page to/from bot account's watchlist.
@param unwatch: True to unwatch, False (default) to watch.
- @type unwatch: bool
-
@return: True if successful, False otherwise.
- @rtype: bool
"""
return self.site.watch(self, unwatch)
@@ -1417,7 +1367,7 @@
except AttributeError:
pass
- def purge(self, **kwargs):
+ def purge(self, **kwargs) -> bool:
"""
Purge the server's cache for this page.
@@ -1432,7 +1382,6 @@
@keyword forcerecursivelinkupdate: Update the links table, and update
the links tables for any page that uses this page as a template.
@type forcerecursivelinkupdate: bool
- @rtype: bool
"""
self.clear_cache()
return self.site.purgepages([self], **kwargs)
@@ -1523,7 +1472,7 @@
# ignore any links with invalid contents
continue
- def langlinks(self, include_obsolete=False):
+ def langlinks(self, include_obsolete=False) -> list:
"""
Return a list of all inter-language Links on this page.
@@ -1532,7 +1481,6 @@
@type include_obsolete: bool
@return: list of Link objects.
- @rtype: list
"""
# Note: We preload a list of *all* langlinks, including links to
# obsolete sites, and store that in self._langlinks. We then filter
@@ -1868,14 +1816,13 @@
for user in contributors)
@deprecated('oldest_revision', since='20140421', future_warning=True)
- def getCreator(self):
+ def getCreator(self) -> Tuple[str, str]:
"""
Get the first revision of the page.
DEPRECATED: Use Page.oldest_revision.
@return: tuple of username and timestamp in isoformat
- @rtype: tuple[str]
"""
result = self.oldest_revision
return result.user, result.timestamp.isoformat()
@@ -2012,7 +1959,7 @@
yield rev['timestamp']
@deprecated_args(retrieveText='content')
- def getDeletedRevision(self, timestamp, content=False):
+ def getDeletedRevision(self, timestamp, content=False) -> list:
"""
Return a particular deleted revision by timestamp.
@@ -2020,7 +1967,6 @@
marker]. text will be None, unless content is True (or has
been retrieved earlier). If timestamp is not found, returns
empty list.
- @rtype: list
"""
if hasattr(self, '_deletedRevs'):
if timestamp in self._deletedRevs and (
@@ -2169,7 +2115,7 @@
def change_category(
self, old_cat, new_cat, summary=None, sort_key=None, in_place=True,
include=[]
- ):
+ ) -> bool:
"""
Remove page from oldCat and add it to newCat.
@@ -2192,7 +2138,6 @@
@type include: list
@return: True if page was saved changed, otherwise False.
- @rtype: bool
"""
# get list of Category objects the article is in and remove possible
# duplicates
@@ -2262,15 +2207,13 @@
"""DEPRECATED: use self.is_flow_page instead."""
return self.is_flow_page()
- def is_flow_page(self):
- """
- Whether a page is a Flow page.
-
- @rtype: bool
- """
+ def is_flow_page(self) -> bool:
+ """Whether a page is a Flow page."""
return self.content_model == 'flow-board'
- def create_short_link(self, permalink=False, with_protocol=True):
+ def create_short_link(self,
+ permalink: bool = False,
+ with_protocol: bool = True) -> str:
"""
Return a shortened link that points to that page.
@@ -2279,13 +2222,10 @@
@param permalink: If true, the link will point to the actual revision
of the page.
- @type permalink: bool
@param with_protocol: If true, and if it's not already included,
the link will have http(s) protocol prepended. On Wikimedia wikis
the protocol is already present.
- @type with_protocol: bool
@return: The reduced link.
- @rtype: str
"""
wiki = self.site
if self.site.family.shared_urlshortner_wiki:
@@ -2517,14 +2457,13 @@
oldest_ts = min(self._file_revisions)
return self._file_revisions[oldest_ts]
- def get_file_history(self):
+ def get_file_history(self) -> dict:
"""
Return the file's version history.
@return: dictionary with:
key: timestamp of the entry
value: instance of FileInfo()
- @rtype: dict
"""
if not len(self._file_revisions):
self.site.loadimageinfo(self, history=True)
@@ -2548,7 +2487,8 @@
"""Return the URL for the file described on this page."""
return self.latest_file_info.url
- def get_file_url(self, url_width=None, url_height=None, url_param=None):
+ def get_file_url(self, url_width=None, url_height=None,
+ url_param=None) -> str:
"""
Return the url or the thumburl of the file described on this page.
@@ -2567,7 +2507,6 @@
@param url_height: see iiurlheigth in [1]
@param url_param: see iiurlparam in [1]
@return: latest file url or thumburl
- @rtype: str
"""
# Plain url is requested.
if url_width is None and url_height is None and url_param is None:
@@ -2580,29 +2519,17 @@
return self.latest_file_info.thumburl
@deprecated('file_is_shared', since='20121101', future_warning=True)
- def fileIsOnCommons(self):
- """
- DEPRECATED. Check if the image is stored on Wikimedia Commons.
-
- @rtype: bool
- """
+ def fileIsOnCommons(self) -> bool:
+ """DEPRECATED. Check if the image is stored on Wikimedia Commons."""
return self.file_is_shared()
@deprecated('file_is_shared', since='20200618')
- def fileIsShared(self):
- """
- DEPRECATED. Check if the image is stored on Wikimedia Commons.
-
- @rtype: bool
- """
+ def fileIsShared(self) -> bool:
+ """DEPRECATED. Check if the image is stored on Wikimedia Commons."""
return self.file_is_shared()
- def file_is_shared(self):
- """
- Check if the file is stored on any known shared repository.
-
- @rtype: bool
- """
+ def file_is_shared(self) -> bool:
+ """Check if the file is stored on any known shared repository."""
# as of now, the only known repositories are commons and wikitravel
# TODO: put the URLs to family file
if not self.site.has_image_repository:
@@ -2653,7 +2580,7 @@
@deprecated('FilePage.get_file_history()', since='20141106',
future_warning=True)
- def getFileVersionHistory(self):
+ def getFileVersionHistory(self) -> list:
"""
Return the file's version history.
@@ -2661,7 +2588,6 @@
[comment, sha1, url, timestamp, metadata,
height, width, mime, user, descriptionurl, size]
- @rtype: list
"""
return self.site.loadimageinfo(self, history=True)
@@ -2690,14 +2616,13 @@
"""
return self.site.imageusage(self, total=total, content=content)
- def upload(self, source, **kwargs):
+ def upload(self, source: str, **kwargs) -> bool:
"""
Upload this file to the wiki.
keyword arguments are from site.upload() method.
@param source: Path or URL to the file to be uploaded.
- @type source: str
@keyword comment: Edit summary; if this is not provided, then
filepage.text will be used. An empty summary is not permitted.
@@ -2741,7 +2666,6 @@
otherwise. If it's True or None ignore_warnings must be a bool.
@return: It returns True if the upload was successful and False
otherwise.
- @rtype: bool
"""
filename = url = None
if '://' in source:
@@ -2897,11 +2821,16 @@
return
@deprecated_args(startFrom='startprefix', step=True)
- def articles(self, recurse=False, total=None,
- content=False, namespaces=None, sortby=None,
+ def articles(self,
+ recurse: Union[int, bool] = False,
+ total=None, content=False,
+ namespaces: Union[int, List[int]] = None,
+ sortby: str = None,
reverse=False, starttime=None, endtime=None,
- startsort=None, endsort=None,
- startprefix=None, endprefix=None,
+ startsort: Optional[str] = None,
+ endsort: Optional[str] = None,
+ startprefix: Optional[str] = None,
+ endprefix: Optional[str] = None,
):
"""
Yield all articles in the current category.
@@ -2913,18 +2842,15 @@
subcategories. If an int, limit recursion to this number of
levels. (Example: recurse=1 will iterate articles in first-level
subcats, but no deeper.)
- @type recurse: int or bool
@param total: iterate no more than this number of pages in
total (at all levels)
@param namespaces: only yield pages in the specified namespaces
- @type namespaces: int or list of ints
@param content: if True, retrieve the content of the current version
of each page (default False)
@param sortby: determines the order in which results are generated,
valid values are "sortkey" (default, results ordered by category
sort key) or "timestamp" (results ordered by time page was
added to the category). This applies recursively.
- @type sortby: str
@param reverse: if True, generate results in reverse order
(default False)
@param starttime: if provided, only generate pages added after this
@@ -2936,19 +2862,15 @@
@param startsort: if provided, only generate pages that have a
sortkey >= startsort; not valid if sortby="timestamp"
(Deprecated in MW 1.24)
- @type startsort: str
@param endsort: if provided, only generate pages that have a
sortkey <= endsort; not valid if sortby="timestamp"
(Deprecated in MW 1.24)
- @type endsort: str
@param startprefix: if provided, only generate pages >= this title
lexically; not valid if sortby="timestamp"; overrides "startsort"
(requires MW 1.18+)
- @type startprefix: str
@param endprefix: if provided, only generate pages < this title
lexically; not valid if sortby="timestamp"; overrides "endsort"
(requires MW 1.18+)
- @type endprefix: str
@rtype: typing.Iterable[pywikibot.Page]
"""
seen = set()
@@ -3024,33 +2946,23 @@
if total == 0:
return
- def isEmptyCategory(self):
- """
- Return True if category has no members (including subcategories).
-
- @rtype: bool
- """
+ def isEmptyCategory(self) -> bool:
+ """Return True if category has no members (including subcategories)."""
ci = self.categoryinfo
return sum(ci[k] for k in ['files', 'pages', 'subcats']) == 0
- def isHiddenCategory(self):
- """
- Return True if the category is hidden.
-
- @rtype: bool
- """
+ def isHiddenCategory(self) -> bool:
+ """Return True if the category is hidden."""
return 'hiddencat' in self.properties()
@property
- def categoryinfo(self):
+ def categoryinfo(self) -> dict:
"""
Return a dict containing information about the category.
The dict contains values for:
Numbers of pages, subcategories, files, and total contents.
-
- @rtype: dict
"""
return self.site.categoryinfo(self)
@@ -3161,32 +3073,28 @@
'This is an autoblock ID, you can only use to unblock it.')
@deprecated('User.username', since='20160504')
- def name(self):
+ def name(self) -> str:
"""
The username.
DEPRECATED: use username instead.
-
- @rtype: str
"""
return self.username
@property
- def username(self):
+ def username(self) -> str:
"""
The username.
Convenience method that returns the title of the page with
namespace prefix omitted, which is the username.
-
- @rtype: str
"""
if self._isAutoblock:
return '#' + self.title(with_ns=False)
else:
return self.title(with_ns=False)
- def isRegistered(self, force=False):
+ def isRegistered(self, force: bool = False) -> bool:
"""
Determine if the user is registered on the site.
@@ -3197,30 +3105,20 @@
True.
@param force: if True, forces reloading the data from API
- @type force: bool
-
- @rtype: bool
"""
# T135828: the registration timestamp may be None but the key exists
return (not self.isAnonymous()
and 'registration' in self.getprops(force))
- def isAnonymous(self):
- """
- Determine if the user is editing as an IP address.
-
- @rtype: bool
- """
+ def isAnonymous(self) -> bool:
+ """Determine if the user is editing as an IP address."""
return is_IP(self.username)
- def getprops(self, force=False):
+ def getprops(self, force: bool = False) -> dict:
"""
Return a properties about the user.
@param force: if True, forces reloading the data from API
- @type force: bool
-
- @rtype: dict
"""
if force and hasattr(self, '_userprops'):
del self._userprops
@@ -3248,73 +3146,58 @@
return pywikibot.Timestamp.fromISOformat(reg)
return None
- def editCount(self, force=False):
+ def editCount(self, force: bool = False) -> int:
"""
Return edit count for a registered user.
Always returns 0 for 'anonymous' users.
@param force: if True, forces reloading the data from API
- @type force: bool
-
- @rtype: int
"""
return self.getprops(force).get('editcount', 0)
- def isBlocked(self, force=False):
+ def isBlocked(self, force: bool = False) -> bool:
"""
Determine whether the user is currently blocked.
@param force: if True, forces reloading the data from API
- @type force: bool
-
- @rtype: bool
"""
return 'blockedby' in self.getprops(force)
- def isEmailable(self, force=False):
+ def isEmailable(self, force: bool = False) -> bool:
"""
Determine whether emails may be send to this user through MediaWiki.
@param force: if True, forces reloading the data from API
- @type force: bool
-
- @rtype: bool
"""
return (not self.isAnonymous() and 'emailable' in self.getprops(force))
- def groups(self, force=False):
+ def groups(self, force: bool = False) -> list:
"""
Return a list of groups to which this user belongs.
The list of groups may be empty.
@param force: if True, forces reloading the data from API
- @type force: bool
@return: groups property
- @rtype: list
"""
return self.getprops(force).get('groups', [])
- def gender(self, force=False):
+ def gender(self, force: bool = False) -> str:
"""Return the gender of the user.
@param force: if True, forces reloading the data from API
- @type force: bool
@return: return 'male', 'female', or 'unknown'
- @rtype: str
"""
if self.isAnonymous():
return 'unknown'
return self.getprops(force).get('gender', 'unknown')
- def rights(self, force=False):
+ def rights(self, force: bool = False) -> list:
"""Return user rights.
@param force: if True, forces reloading the data from API
- @type force: bool
@return: return user rights
- @rtype: list
"""
return self.getprops(force).get('rights', [])
@@ -3357,20 +3240,16 @@
return Page(Link(self.username + subpage,
self.site, default_namespace=3))
- def send_email(self, subject, text, ccme=False):
+ def send_email(self, subject: str, text: str, ccme: bool = False) -> bool:
"""
Send an email to this user via MediaWiki's email interface.
@param subject: the subject header of the mail
- @type subject: str
@param text: mail body
- @type text: str
@param ccme: if True, sends a copy of this email to the bot
- @type ccme: bool
@raises NotEmailableError: the user of this User is not emailable
@raises UserRightsError: logged in user does not have 'sendemail' right
@return: operation successful indicator
- @rtype: bool
"""
if not self.isEmailable():
raise NotEmailableError(self)
@@ -3552,7 +3431,7 @@
item.pageid() > 0)
@property
- def is_thankable(self):
+ def is_thankable(self) -> bool:
"""
Determine if the user has thanks notifications enabled.
@@ -3560,8 +3439,6 @@
Privacy of thanks preferences is under discussion, please see
https://phabricator.wikimedia.org/T57401#2216861, and
https://phabricator.wikimedia.org/T120753#1863894
-
- @rtype: bool
"""
return self.isRegistered() and 'bot' not in self.groups()
@@ -3609,12 +3486,8 @@
return key in self._data
@staticmethod
- def normalizeKey(key):
- """
- Helper function to replace site objects with their language codes.
-
- @rtype: str
- """
+ def normalizeKey(key) -> str:
+ """Helper function to return language codes of a site object."""
if isinstance(key, pywikibot.site.BaseSite):
key = key.lang
return key
@@ -3899,7 +3772,7 @@
return obj
@classmethod
- def normalizeData(cls, data):
+ def normalizeData(cls, data) -> dict:
"""
Helper function to expand data into the Wikibase API structure.
@@ -3907,7 +3780,6 @@
@type data: list or dict
@return: The dict with normalized data
- @rtype: dict
"""
norm_data = {}
if isinstance(data, dict):
@@ -3932,7 +3804,7 @@
norm_data[db_name] = json
return norm_data
- def toJSON(self, diffto=None):
+ def toJSON(self, diffto: Optional[dict] = None) -> dict:
"""
Create JSON suitable for Wikibase API.
@@ -3940,9 +3812,6 @@
to the provided data is created.
@param diffto: JSON containing entity data
- @type diffto: dict
-
- @rtype: dict
"""
data = {dbname: sitelink.toJSON()
for (dbname, sitelink) in self.items()}
@@ -4023,14 +3892,11 @@
self.__class__.__name__, self.repo)
@classmethod
- def is_valid_id(cls, entity_id):
+ def is_valid_id(cls, entity_id: str) -> bool:
"""
Whether the string can be a valid id of the entity type.
@param entity_id: The ID to test.
- @type entity_id: basestring
-
- @rtype: bool
"""
if not hasattr(cls, 'title_pattern'):
return True
@@ -4038,7 +3904,7 @@
# todo: use re.fullmatch when Python 3.4+ required
return bool(re.match(cls.title_pattern + '$', entity_id))
- def _defined_by(self, singular=False):
+ def _defined_by(self, singular: bool = False) -> dict:
"""
Internal function to provide the API parameters to identify the entity.
@@ -4046,9 +3912,7 @@
@param singular: Whether the parameter names should use the singular
form
- @type singular: bool
@return: API parameters
- @rtype: dict
"""
params = {}
if self.id != '-1':
@@ -4070,17 +3934,15 @@
else:
return self.id
- def get_data_for_new_entity(self):
+ def get_data_for_new_entity(self) -> dict:
"""
Return data required for creation of a new entity.
Override it if you need.
-
- @rtype: dict
"""
return {}
- def toJSON(self, diffto=None):
+ def toJSON(self, diffto: Optional[dict] = None) -> dict:
"""
Create JSON suitable for Wikibase API.
@@ -4088,9 +3950,6 @@
to the provided data is created.
@param diffto: JSON containing entity data
- @type diffto: dict
-
- @rtype: dict
"""
data = {}
for key in self.DATA_ATTRIBUTES:
@@ -4106,15 +3965,12 @@
return data
@classmethod
- def _normalizeData(cls, data):
+ def _normalizeData(cls, data: dict) -> dict:
"""
Helper function to expand data into the Wikibase API structure.
@param data: The dict to normalize
- @type data: dict
-
@return: The dict with normalized data
- @rtype: dict
"""
norm_data = {}
for key, attr in cls.DATA_ATTRIBUTES.items():
@@ -4122,12 +3978,8 @@
norm_data[key] = attr.normalizeData(data[key])
return norm_data
- def exists(self):
- """
- Determine if an entity exists in the data repository.
-
- @rtype: bool
- """
+ def exists(self) -> bool:
+ """Determine if an entity exists in the data repository."""
if not hasattr(self, '_content'):
try:
self.get()
@@ -4136,15 +3988,13 @@
return False
return 'missing' not in self._content
- def get(self, force=False):
+ def get(self, force: bool = False) -> dict:
"""
Fetch all entity data and cache it.
@param force: override caching
- @type force: bool
@raise NoWikibaseEntity: if this entity doesn't exist
@return: actual data which entity holds
- @rtype: dict
"""
if force or not hasattr(self, '_content'):
identification = self._defined_by()
@@ -4338,21 +4188,16 @@
attr = '_revid'
return super().__delattr__(attr)
- def namespace(self):
+ def namespace(self) -> int:
"""
Return the number of the namespace of the entity.
@return: Namespace id
- @rtype: int
"""
return self._namespace.id
- def exists(self):
- """
- Determine if an entity exists in the data repository.
-
- @rtype: bool
- """
+ def exists(self) -> bool:
+ """Determine if an entity exists in the data repository."""
if not hasattr(self, '_content'):
try:
self.get(get_redirect=True)
@@ -4361,7 +4206,7 @@
return False
return 'missing' not in self._content
- def botMayEdit(self):
+ def botMayEdit(self) -> bool:
"""
Return whether bots may edit this page.
@@ -4371,22 +4216,19 @@
template) doesn't apply.
@return: True
- @rtype: bool
"""
return True
- def get(self, force=False, *args, **kwargs):
+ def get(self, force: bool = False, *args, **kwargs) -> dict:
"""
Fetch all page data, and cache it.
@param force: override caching
- @type force: bool
@raise NotImplementedError: a value in args or kwargs
@return: actual data which entity holds
- @rtype: dict
- @note: dicts returned by this method are references to content of this
- entity and their modifying may indirectly cause unwanted change to
- the live content
+ @note: dicts returned by this method are references to content
+ of this entity and their modifying may indirectly cause
+ unwanted change to the live content
"""
if args or kwargs:
raise NotImplementedError(
@@ -4418,12 +4260,8 @@
return data
@property
- def latest_revision_id(self):
- """
- Get the revision identifier for the most recent revision of the entity.
-
- @rtype: int
- """
+ def latest_revision_id(self) -> int:
+ """Get revision identifier for the most recent revision of entity."""
if not hasattr(self, '_revid'):
self.get()
return self._revid
@@ -4607,7 +4445,7 @@
assert self.id == self._link.title
- def _defined_by(self, singular=False):
+ def _defined_by(self, singular: bool = False) -> dict:
"""
Internal function to provide the API parameters to identify the item.
@@ -4621,11 +4459,9 @@
An empty dict is returned if the ItemPage is instantiated without
either ID (internally it has id = '-1') or site&title.
- @param singular: Whether the parameter names should use the singular
- form
- @type singular: bool
+ @param singular: Whether the parameter names should use the
+ singular form
@return: API parameters
- @rtype: dict
"""
params = {}
if singular:
@@ -4780,7 +4616,7 @@
return item
- def get(self, force=False, get_redirect=False, *args, **kwargs):
+ def get(self, force=False, get_redirect=False, *args, **kwargs) -> dict:
"""
Fetch all item data, and cache it.
@@ -4791,7 +4627,6 @@
@type get_redirect: bool
@raise NotImplementedError: a value in args or kwargs
@return: actual data which entity holds
- @rtype: dict
@note: dicts returned by this method are references to content of this
entity and their modifying may indirectly cause unwanted change to
the live content
@@ -4833,7 +4668,7 @@
pg._item = self
yield pg
- def getSitelink(self, site, force=False):
+ def getSitelink(self, site, force=False) -> str:
"""
Return the title for the specific site.
@@ -4842,8 +4677,6 @@
@param site: Site to find the linked page of.
@type site: pywikibot.Site or database name
@param force: override caching
-
- @rtype: str
"""
if force or not hasattr(self, '_content'):
self.get(force=force)
@@ -5102,15 +4935,13 @@
ns=source.property_namespace)
Property.__init__(self, source, self.id)
- def get(self, force=False, *args, **kwargs):
+ def get(self, force: bool = False, *args, **kwargs) -> dict:
"""
Fetch the property entity, and cache it.
@param force: override caching
- @type force: bool
@raise NotImplementedError: a value in args or kwargs
@return: actual data which entity holds
- @rtype: dict
@note: dicts returned by this method are references to content of this
entity and their modifying may indirectly cause unwanted change to
the live content
@@ -5344,15 +5175,12 @@
return claim
@classmethod
- def referenceFromJSON(cls, site, data):
+ def referenceFromJSON(cls, site, data) -> dict:
"""
Create a dict of claims from reference JSON returned in the API call.
- Reference objects are represented a
- bit differently, and require some
- more handling.
-
- @rtype: dict
+ Reference objects are represented a bit differently, and require
+ some more handling.
"""
source = OrderedDict()
@@ -5389,12 +5217,8 @@
claim.isQualifier = True
return claim
- def toJSON(self):
- """
- Create dict suitable for the MediaWiki API.
-
- @rtype: dict
- """
+ def toJSON(self) -> dict:
+ """Create dict suitable for the MediaWiki API."""
data = {
'mainsnak': {
'snaktype': self.snaktype,
@@ -5483,12 +5307,11 @@
"""
return self.target
- def getSnakType(self):
+ def getSnakType(self) -> str:
"""
Return the type of snak.
@return: str ('value', 'somevalue' or 'novalue')
- @rtype: str
"""
return self.snaktype
@@ -5528,12 +5351,8 @@
self.setSnakType(value)
self.changeTarget(snaktype=self.getSnakType(), **kwargs)
- def getSources(self):
- """
- Return a list of sources, each being a list of Claims.
-
- @rtype: list
- """
+ def getSources(self) -> list:
+ """Return a list of sources, each being a list of Claims."""
return self.sources
def addSource(self, claim, **kwargs):
@@ -5632,7 +5451,7 @@
self.qualifiers[qualifier.getID()].remove(qualifier)
qualifier.on_item = None
- def target_equals(self, value):
+ def target_equals(self, value) -> bool:
"""
Check whether the Claim's target is equal to specified value.
@@ -5647,7 +5466,6 @@
@param value: the value to compare with
@return: true if the Claim's target is equal to the value provided,
false otherwise
- @rtype: bool
"""
if (isinstance(self.target, WikibasePage)
and isinstance(value, str)):
@@ -5679,15 +5497,13 @@
return self.target == value
- def has_qualifier(self, qualifier_id, target):
+ def has_qualifier(self, qualifier_id: str, target) -> bool:
"""
Check whether Claim contains specified qualifier.
@param qualifier_id: id of the qualifier
- @type qualifier_id: str
@param target: qualifier target to check presence of
@return: true if the qualifier was found, false otherwise
- @rtype: bool
"""
if self.isQualifier or self.isReference:
raise ValueError('Qualifiers and references cannot have '
@@ -5698,12 +5514,11 @@
return True
return False
- def _formatValue(self):
+ def _formatValue(self) -> dict:
"""
Format the target into the proper JSON value that Wikibase wants.
@return: JSON value
- @rtype: dict
"""
if self.type in ('wikibase-item', 'wikibase-property'):
value = {'entity-type': self.getTarget().entity_type,
@@ -5722,16 +5537,16 @@
value = self.getTarget().toWikibase()
return value
- def _formatDataValue(self):
+ def _formatDataValue(self) -> dict:
"""
Format the target into the proper JSON datavalue that Wikibase wants.
@return: Wikibase API representation with type and value.
- @rtype: dict
"""
- return {'value': self._formatValue(),
- 'type': self.value_types.get(self.type, self.type)
- }
+ return {
+ 'value': self._formatValue(),
+ 'type': self.value_types.get(self.type, self.type)
+ }
class Revision(DotReadableDict):
@@ -5809,14 +5624,13 @@
return cls._FullHistEntry
@property
- def parent_id(self):
+ def parent_id(self) -> int:
"""
Return id of parent/previous revision.
Returns 0 if there is no previous revision
@return: id of parent/previous revision
- @rtype: int
@raises AssertionError: parent id not supplied to the constructor
"""
assert self._parent_id is not None, (
@@ -5826,7 +5640,7 @@
return self._parent_id
@property
- def text(self):
+ def text(self) -> Optional[str]:
"""
Return text of this revision.
@@ -5835,14 +5649,13 @@
this returns the contents of the main slot.
@return: text of the revision
- @rtype: str or None if text not yet retrieved
"""
if self.slots is not None:
return self.slots.get('main', {}).get('*')
return self._text
@property
- def content_model(self):
+ def content_model(self) -> str:
"""
Return content model of the revision.
@@ -5851,7 +5664,6 @@
this returns the content model of the main slot.
@return: content model
- @rtype: str
@raises AssertionError: content model not supplied to the constructor
which always occurs for MediaWiki versions lower than 1.21.
"""
@@ -5866,7 +5678,7 @@
return self._content_model
@property
- def sha1(self):
+ def sha1(self) -> Optional[str]:
"""
Return and cache SHA1 checksum of the text.
@@ -5876,7 +5688,6 @@
first). That calculated checksum will be cached too and returned on
future calls. If the text is None (not queried) it will just return
None and does not cache anything.
- @rtype: str or None
"""
if self._sha1 is None and self.text is not None:
self._sha1 = hashlib.sha1(self.text.encode('utf8')).hexdigest()
@@ -6427,34 +6238,22 @@
return self._namespace
@property
- def title(self):
- """
- Return the title of the link.
-
- @rtype: str
- """
+ def title(self) -> str:
+ """Return the title of the link."""
if not hasattr(self, '_title'):
self.parse()
return self._title
@property
- def section(self):
- """
- Return the section of the link.
-
- @rtype: str
- """
+ def section(self) -> str:
+ """Return the section of the link."""
if not hasattr(self, '_section'):
self.parse()
return self._section
@property
- def anchor(self):
- """
- Return the anchor of the link.
-
- @rtype: str
- """
+ def anchor(self) -> str:
+ """Return the anchor of the link."""
if not hasattr(self, '_anchor'):
self.parse()
return self._anchor
@@ -6658,12 +6457,11 @@
return list(self._badges)
@classmethod
- def fromJSON(cls, data, site=None):
+ def fromJSON(cls, data: dict, site=None):
"""
Create a SiteLink object from JSON returned in the API call.
@param data: JSON containing SiteLink data
- @type data: dict
@param site: The Wikibase site
@type site: pywikibot.site.DataSite
@@ -6675,12 +6473,11 @@
sl._badges.add(pywikibot.ItemPage(repo, badge))
return sl
- def toJSON(self):
+ def toJSON(self) -> dict:
"""
Convert the SiteLink to a JSON object for the Wikibase API.
@return: Wikibase JSON
- @rtype: dict
"""
json = {
'site': self._sitekey,
@@ -6729,14 +6526,12 @@
}
-def html2unicode(text, ignore=None, exceptions=None):
+def html2unicode(text: str, ignore=None, exceptions=None) -> str:
"""
Replace HTML entities with equivalent unicode.
@param ignore: HTML entities to ignore
@param ignore: list of int
-
- @rtype: str
"""
if ignore is None:
ignore = []
@@ -6773,19 +6568,19 @@
return _ENTITY_SUB(handle_entity, text)
-def UnicodeToAsciiHtml(s):
+def UnicodeToAsciiHtml(string) -> str:
"""Convert unicode to a str using HTML entities."""
html = []
- for c in s:
+ for c in string:
cord = ord(c)
if 31 < cord < 127:
html.append(c)
else:
- html.append('&#%d;' % cord)
+ html.append('&#{};'.format(cord))
return ''.join(html)
-def unicode2html(x, encoding):
+def unicode2html(string: str, encoding: str) -> str:
"""
Convert unicode string to requested HTML encoding.
@@ -6793,32 +6588,26 @@
string into the desired format; if that doesn't work, encode the unicode
into HTML &#; entities. If it does work, return it unchanged.
- @param x: String to update
- @type x: str
+ @param string: String to update
@param encoding: Encoding to use
- @type encoding: str
-
- @rtype: str
"""
try:
- x.encode(encoding)
+ string.encode(encoding)
except UnicodeError:
- x = UnicodeToAsciiHtml(x)
- return x
+ string = UnicodeToAsciiHtml(string)
+ return string
@deprecated_args(site2=True, site='encodings')
-def url2unicode(title, encodings='utf-8'):
+def url2unicode(title: str, encodings='utf-8') -> str:
"""
Convert URL-encoded text to unicode using several encoding.
Uses the first encoding that doesn't cause an error.
@param title: URL-encoded character data to convert
- @type title: str
@param encodings: Encodings to attempt to use during conversion.
@type encodings: str, list or Site
- @rtype: str
@raise UnicodeError: Could not convert using any encoding.
"""
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/623108
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I0c241a2f8cab3b34a7d4a4571e71ab3cbfcd5330
Gerrit-Change-Number: 623108
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/621946 )
Change subject: [doc] add type hints to textlib.py
......................................................................
[doc] add type hints to textlib.py
+ some code improvements
Change-Id: I6ec1aab650dc6c3a1fb27c1812c4b277a1f17361
---
M pywikibot/textlib.py
1 file changed, 120 insertions(+), 159 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/textlib.py b/pywikibot/textlib.py
index dfd8486..972c290 100644
--- a/pywikibot/textlib.py
+++ b/pywikibot/textlib.py
@@ -16,7 +16,9 @@
from collections.abc import Sequence
from collections import OrderedDict, namedtuple
+from contextlib import suppress
from html.parser import HTMLParser
+from typing import Optional, Union
import pywikibot
from pywikibot.exceptions import InvalidTitle, SiteDefinitionError
@@ -139,7 +141,7 @@
TIMESTAMP_GAP_LIMIT = 10
-def to_local_digits(phrase, lang):
+def to_local_digits(phrase: Union[str, int], lang: str) -> str:
"""
Change Latin digits based on language to localized version.
@@ -149,30 +151,27 @@
@param phrase: The phrase to convert to localized numerical
@param lang: language code
@return: The localized version
- @rtype: str
"""
digits = NON_LATIN_DIGITS.get(lang)
- if not digits:
- return phrase
- phrase = '%s' % phrase
- for i in range(10):
- phrase = phrase.replace(str(i), digits[i])
+ if digits:
+ phrase = str(phrase)
+ for i, digit in enumerate(digits):
+ phrase = phrase.replace(str(i), digit)
return phrase
-def unescape(s):
+def unescape(s: str) -> str:
"""Replace escaped HTML-special characters by their originals."""
- if '&' not in s:
- return s
- s = s.replace('<', '<')
- s = s.replace('>', '>')
- s = s.replace(''', "'")
- s = s.replace('"', '"')
- s = s.replace('&', '&') # Must be last
+ if '&' in s:
+ s = s.replace('<', '<')
+ s = s.replace('>', '>')
+ s = s.replace(''', "'")
+ s = s.replace('"', '"')
+ s = s.replace('&', '&') # Must be last
return s
-class _MultiTemplateMatchBuilder(object):
+class _MultiTemplateMatchBuilder:
"""Build template matcher."""
@@ -191,12 +190,12 @@
old = template.title(with_ns=False)
else:
raise ValueError(
- '{0} is not a template Page object'.format(template))
+ '{} is not a template Page object'.format(template))
elif isinstance(template, str):
old = template
else:
raise ValueError(
- '{0!r} is not a valid template'.format(template))
+ '{!r} is not a valid template'.format(template))
if namespace.case == 'first-letter':
pattern = '[{}{}]{}'.format(re.escape(old[0].upper()),
@@ -220,14 +219,14 @@
return lambda text: any(predicate(text) for predicate in predicates)
-def _ignore_case(string):
+def _ignore_case(string: str) -> str:
"""Return a case-insensitive pattern for the string."""
return ''.join(
- '[' + c + s + ']' if c != s else c
+ '[{}{}]'.format(c, s) if c != s else c
for s, c in zip(string, string.swapcase()))
-def _tag_pattern(tag_name):
+def _tag_pattern(tag_name: str) -> str:
"""Return a tag pattern for the given tag name."""
return (
r'<{0}(?:>|\s+[^>]*(?<!/)>)' # start tag
@@ -236,7 +235,7 @@
.format(_ignore_case(tag_name)))
-def _tag_regex(tag_name):
+def _tag_regex(tag_name: str):
"""Return a compiled tag regex for the given tag name."""
return re.compile(_tag_pattern(tag_name))
@@ -300,44 +299,46 @@
result = []
for exc in keys:
- if isinstance(exc, str):
- # assume the string is a reference to a standard regex above,
- # which may not yet have a site specific re compiled.
- if exc in _regex_cache:
- if isinstance(_regex_cache[exc], tuple):
- if not site and exc in ('interwiki', 'property', 'invoke',
- 'category', 'file'):
- issue_deprecation_warning(
- 'site=None',
- "a valid site for '{}' regex".format(exc),
- since='20151006')
- site = pywikibot.Site()
-
- if (exc, site) not in _regex_cache:
- re_text, re_var = _regex_cache[exc]
- _regex_cache[(exc, site)] = re.compile(
- re_text % re_var(site), re.VERBOSE)
-
- result.append(_regex_cache[(exc, site)])
- else:
- result.append(_regex_cache[exc])
- else:
- # nowiki, noinclude, includeonly, timeline, math and other
- # extensions
- _regex_cache[exc] = _tag_regex(exc)
- result.append(_regex_cache[exc])
- # handle alias
- if exc == 'source':
- result.append(_tag_regex('syntaxhighlight'))
- else:
+ if not isinstance(exc, str):
# assume it's a regular expression
result.append(exc)
+ continue
+
+ # assume the string is a reference to a standard regex above,
+ # which may not yet have a site specific re compiled.
+ if exc in _regex_cache:
+ if isinstance(_regex_cache[exc], tuple):
+ if not site and exc in ('interwiki', 'property', 'invoke',
+ 'category', 'file'):
+ issue_deprecation_warning(
+ 'site=None',
+ "a valid site for '{}' regex".format(exc),
+ since='20151006')
+ site = pywikibot.Site()
+
+ if (exc, site) not in _regex_cache:
+ re_text, re_var = _regex_cache[exc]
+ _regex_cache[(exc, site)] = re.compile(
+ re_text % re_var(site), re.VERBOSE)
+
+ result.append(_regex_cache[(exc, site)])
+ else:
+ result.append(_regex_cache[exc])
+ else:
+ # nowiki, noinclude, includeonly, timeline, math and other
+ # extensions
+ _regex_cache[exc] = _tag_regex(exc)
+ result.append(_regex_cache[exc])
+ # handle alias
+ if exc == 'source':
+ result.append(_tag_regex('syntaxhighlight'))
return result
-def replaceExcept(text, old, new, exceptions, caseInsensitive=False,
- allowoverlap=False, marker='', site=None, count=0):
+def replaceExcept(text: str, old, new, exceptions: list,
+ caseInsensitive: bool = False, allowoverlap: bool = False,
+ marker: str = '', site=None, count: int = 0) -> str:
"""
Return text with 'old' replaced by 'new', ignoring specified types of text.
@@ -346,20 +347,19 @@
regex matching. If allowoverlap is true, overlapping occurrences are all
replaced (watch out when using this, it might lead to infinite loops!).
- @type text: str
+ @param text: text to be modified
@param old: a compiled or uncompiled regular expression
@param new: a unicode string (which can contain regular
expression references), or a function which takes
a match object as parameter. See parameter repl of
re.sub().
- @param exceptions: a list of strings which signal what to leave out,
- e.g. ['math', 'table', 'template']
- @type caseInsensitive: bool
+ @param exceptions: a list of strings or already compiled regex
+ objects which signal what to leave out. Strings might be like
+ ['math', 'table', 'template'] for example.
@param marker: a string that will be added to the last replacement;
if nothing is changed, it is added at the end
@param count: how many replacements to do at most. See parameter
count of re.sub().
- @type count: int
"""
# if we got a string, compile it as a regular expression
if isinstance(old, str):
@@ -420,10 +420,9 @@
last = 0
for group_match in group_regex.finditer(new):
group_id = group_match.group(1) or group_match.group(2)
- try:
+ with suppress(ValueError):
group_id = int(group_id)
- except ValueError:
- pass
+
try:
replacement += new[last:group_match.start()]
replacement += match.group(group_id) or ''
@@ -450,7 +449,7 @@
return text
-def removeDisabledParts(text, tags=None, include=[], site=None):
+def removeDisabledParts(text: str, tags=None, include=[], site=None) -> str:
"""
Return text without portions where wiki markup is disabled.
@@ -474,7 +473,6 @@
@type site: pywikibot.Site
@return: text stripped from disabled parts.
- @rtype: str
"""
if not tags:
tags = ('comment', 'includeonly', 'nowiki', 'pre', 'source')
@@ -485,7 +483,8 @@
return toRemoveR.sub('', text)
-def removeHTMLParts(text, keeptags=['tt', 'nowiki', 'small', 'sup']):
+def removeHTMLParts(text: str,
+ keeptags=['tt', 'nowiki', 'small', 'sup']) -> str:
"""
Return text without portions where HTML markup is disabled.
@@ -494,7 +493,6 @@
The exact set of parts which should NOT be removed can be passed as the
'keeptags' parameter, which defaults to ['tt', 'nowiki', 'small', 'sup'].
-
"""
# try to merge with 'removeDisabledParts()' above into one generic function
# thanks to:
@@ -538,7 +536,7 @@
self.textdata += '</{}>'.format(tag)
-def isDisabled(text, index, tags=None):
+def isDisabled(text: str, index: int, tags=None) -> bool:
"""
Return True if text[index] is disabled, e.g. by a comment or nowiki tags.
@@ -551,7 +549,8 @@
return (marker not in text)
-def findmarker(text, startwith='@@', append=None):
+def findmarker(text: str, startwith: str = '@@',
+ append: Optional[str] = None) -> str:
"""Find a string which is not part of text."""
if not append:
append = '@'
@@ -561,7 +560,7 @@
return mymarker
-def expandmarker(text, marker='', separator=''):
+def expandmarker(text: str, marker: str = '', separator: str = '') -> str:
"""
Return a marker expanded whitespace and the separator.
@@ -569,15 +568,11 @@
of the separator and whitespace directly before it.
@param text: the text which will be searched.
- @type text: str
@param marker: the marker to be searched.
- @type marker: str
@param separator: the separator string allowed before the marker. If empty
it won't include whitespace too.
- @type separator: str
@return: the marker with the separator and whitespace from the text in
front of it. It'll be just the marker if the separator is empty.
- @rtype: str
"""
# set to remove any number of separator occurrences plus arbitrary
# whitespace before, after, and between them,
@@ -601,7 +596,7 @@
return marker
-def replace_links(text, replace, site=None):
+def replace_links(text: str, replace, site=None) -> str:
"""
Replace wikilinks selectively.
@@ -646,10 +641,9 @@
"""Return the link from source when it's a Page otherwise itself."""
if isinstance(source, pywikibot.Page):
return source._link
- elif isinstance(source, str):
+ if isinstance(source, str):
return pywikibot.Link(source, site)
- else:
- return source
+ return source
def replace_callable(link, text, groups, rng):
if replace_list[0] == link:
@@ -664,7 +658,7 @@
'a sequence, a Link or a basestring but '
'is "{0}"'.format(type(replacement)))
- def title_section(link):
+ def title_section(link) -> str:
title = link.title
if link.section:
title += '#' + link.section
@@ -842,7 +836,7 @@
_Section = namedtuple('_Section', ('title', 'content'))
-def _extract_headings(text, site):
+def _extract_headings(text: str, site) -> list:
"""Return _Heading objects."""
headings = []
heading_regex = _get_regexes(['header'], site)[0]
@@ -853,7 +847,7 @@
return headings
-def _extract_sections(text, headings):
+def _extract_sections(text: str, headings) -> list:
"""Return _Section objects."""
if headings:
# Assign them their contents
@@ -870,7 +864,7 @@
return []
-def extract_sections(text, site=None):
+def extract_sections(text: str, site=None) -> tuple:
"""
Return section headings and contents found in text.
@@ -909,7 +903,8 @@
langlink_pattern = interwiki_regex.pattern.replace(':?', '')
last_section_content = sections[-1].content if sections else header
footer = re.search(
- r'(%s)*\Z' % r'|'.join((langlink_pattern, cat_regex.pattern, r'\s')),
+ r'({})*\Z'.format(r'|'.join((langlink_pattern,
+ cat_regex.pattern, r'\s'))),
last_section_content).group().lstrip()
if footer:
if sections:
@@ -943,7 +938,7 @@
# as in-line interwiki links (e.g., "[[:es:Articulo]]".
@deprecate_arg('pageLink', None)
-def getLanguageLinks(text, insite=None, template_subpage=False):
+def getLanguageLinks(text: str, insite=None, template_subpage=False) -> dict:
"""
Return a dict of inter-language links found in text.
@@ -952,7 +947,6 @@
Do not call this routine directly, use Page.interwiki() method
instead.
-
"""
if insite is None:
insite = pywikibot.Site()
@@ -1008,21 +1002,18 @@
return result
-def removeLanguageLinks(text, site=None, marker=''):
+def removeLanguageLinks(text: str, site=None, marker: str = '') -> str:
"""Return text with all inter-language links removed.
If a link to an unknown language is encountered, a warning
is printed.
@param text: The text that needs to be modified.
- @type text: str
@param site: The site that the text is coming from.
@type site: pywikibot.Site
@param marker: If defined, marker is placed after the last language
link, or at the end of text if there are no language links.
- @type marker: str
@return: The modified text.
- @rtype: str
"""
if site is None:
site = pywikibot.Site()
@@ -1041,7 +1032,8 @@
return text.strip()
-def removeLanguageLinksAndSeparator(text, site=None, marker='', separator=''):
+def removeLanguageLinksAndSeparator(text: str, site=None, marker: str = '',
+ separator: str = '') -> str:
"""
Return text with inter-language links and preceding separators removed.
@@ -1049,17 +1041,13 @@
is printed.
@param text: The text that needs to be modified.
- @type text: str
@param site: The site that the text is coming from.
@type site: pywikibot.Site
@param marker: If defined, marker is placed after the last language
link, or at the end of text if there are no language links.
- @type marker: str
@param separator: The separator string that will be removed
if followed by the language links.
- @type separator: str
@return: The modified text
- @rtype: str
"""
if separator:
mymarker = findmarker(text, '@L@')
@@ -1070,28 +1058,23 @@
return removeLanguageLinks(text, site, marker)
-def replaceLanguageLinks(oldtext, new, site=None, addOnly=False,
- template=False, template_subpage=False):
+def replaceLanguageLinks(oldtext: str, new: dict, site=None,
+ addOnly: bool = False, template: bool = False,
+ template_subpage: bool = False) -> str:
"""Replace inter-language links in the text with a new set of links.
@param oldtext: The text that needs to be modified.
- @type oldtext: str
@param new: A dict with the Site objects as keys, and Page or Link objects
as values (i.e., just like the dict returned by getLanguageLinks
function).
- @type new: dict
@param site: The site that the text is from.
@type site: pywikibot.Site
@param addOnly: If True, do not remove old language links, only add new
ones.
- @type addOnly: bool
@param template: Indicates if text belongs to a template page or not.
- @type template: bool
@param template_subpage: Indicates if text belongs to a template sub-page
or not.
- @type template_subpage: bool
@return: The modified text.
- @rtype: str
"""
# Find a marker that is not already in the text.
marker = findmarker(oldtext)
@@ -1194,7 +1177,7 @@
return newtext.strip()
-def interwikiFormat(links, insite=None):
+def interwikiFormat(links: dict, insite=None) -> str:
"""Convert interwiki link dict into a wikitext string.
@param links: interwiki links to be formatted
@@ -1205,13 +1188,13 @@
@type insite: BaseSite
@return: string including wiki links formatted for inclusion
in insite
- @rtype: str
"""
- if insite is None:
- insite = pywikibot.Site()
if not links:
return ''
+ if insite is None:
+ insite = pywikibot.Site()
+
ar = interwikiSort(list(links.keys()), insite)
s = []
for site in ar:
@@ -1235,6 +1218,7 @@
"""Sort sites according to local interwiki sort logic."""
if not sites:
return []
+
if insite is None:
insite = pywikibot.Site()
@@ -1258,13 +1242,12 @@
# Functions dealing with category links
# -------------------------------------
-def getCategoryLinks(text, site=None, include=[], expand_text=False):
+def getCategoryLinks(text: str, site=None, include: list = [],
+ expand_text: bool = False) -> list:
"""Return a list of category links found in text.
@param include: list of tags which should not be removed by
removeDisabledParts() and where CategoryLinks can be searched.
- @type include: list
-
@return: all category links found
@rtype: list of Category objects
"""
@@ -1302,18 +1285,15 @@
return result
-def removeCategoryLinks(text, site=None, marker=''):
+def removeCategoryLinks(text: str, site=None, marker: str = '') -> str:
"""Return text with all category links removed.
@param text: The text that needs to be modified.
- @type text: str
@param site: The site that the text is coming from.
@type site: pywikibot.Site
@param marker: If defined, marker is placed after the last category
link, or at the end of text if there are no category links.
- @type marker: str
@return: The modified text.
- @rtype: str
"""
# This regular expression will find every link that is possibly an
# interwiki link, plus trailing whitespace. The language code is grouped.
@@ -1335,22 +1315,19 @@
return text.strip()
-def removeCategoryLinksAndSeparator(text, site=None, marker='', separator=''):
+def removeCategoryLinksAndSeparator(text: str, site=None, marker: str = '',
+ separator: str = '') -> str:
"""
Return text with category links and preceding separators removed.
@param text: The text that needs to be modified.
- @type text: str
@param site: The site that the text is coming from.
@type site: pywikibot.Site
@param marker: If defined, marker is placed after the last category
link, or at the end of text if there are no category links.
- @type marker: str
@param separator: The separator string that will be removed
if followed by the category links.
- @type separator: str
@return: The modified text
- @rtype: str
"""
if site is None:
site = pywikibot.Site()
@@ -1364,7 +1341,7 @@
def replaceCategoryInPlace(oldtext, oldcat, newcat, site=None,
- add_only=False):
+ add_only: bool = False) -> str:
"""
Replace old category with new one and return the modified text.
@@ -1374,7 +1351,6 @@
@param add_only: If add_only is True, the old category won't
be replaced and the category given will be added after it.
@return: the modified text
- @rtype: str
"""
if site is None:
site = pywikibot.Site()
@@ -1422,12 +1398,12 @@
return text
-def replaceCategoryLinks(oldtext, new, site=None, addOnly=False):
+def replaceCategoryLinks(oldtext: str, new, site=None,
+ addOnly: bool = False) -> str:
"""
Replace all existing category links with new category links.
@param oldtext: The text that needs to be replaced.
- @type oldtext: str
@param new: Should be a list of Category objects or strings
which can be either the raw name or [[Category:..]].
@type new: iterable
@@ -1435,9 +1411,7 @@
@type site: pywikibot.Site
@param addOnly: If addOnly is True, the old category won't be deleted and
the category(s) given will be added (and they won't replace anything).
- @type addOnly: bool
@return: The modified text.
- @rtype: str
"""
# Find a marker that is not already in the text.
marker = findmarker(oldtext)
@@ -1518,7 +1492,7 @@
return newtext.strip()
-def categoryFormat(categories, insite=None):
+def categoryFormat(categories, insite=None) -> str:
"""Return a string containing links to all categories in a list.
@param categories: A list of Category or Page objects or strings which can
@@ -1527,10 +1501,10 @@
@param insite: Used to to localise the category namespace.
@type insite: pywikibot.Site
@return: String of categories
- @rtype: str
"""
if not categories:
return ''
+
if insite is None:
insite = pywikibot.Site()
@@ -1553,10 +1527,7 @@
link = category.aslink()
catLinks.append(link)
- if insite.category_on_one_line():
- sep = ' '
- else:
- sep = '\n'
+ sep = ' ' if insite.category_on_one_line() else '\n'
# Some people don't like the categories sorted
# catLinks.sort()
return sep.join(catLinks) + '\n'
@@ -1566,7 +1537,7 @@
# Functions dealing with external links
# -------------------------------------
-def compileLinkR(withoutBracketed=False, onlyBracketed=False):
+def compileLinkR(withoutBracketed=False, onlyBracketed: bool = False):
"""Return a regex that matches external links."""
# RFC 2396 says that URLs may only contain certain characters.
# For this regex we also accept non-allowed characters, so that the bot
@@ -1601,7 +1572,8 @@
# Functions dealing with templates
# --------------------------------
-def extract_templates_and_params(text, remove_disabled_parts=None, strip=None):
+def extract_templates_and_params(text: str, remove_disabled_parts=None,
+ strip=None):
"""Return a list of templates found in text.
Return value is a list of tuples. There is one tuple for each use of a
@@ -1629,7 +1601,6 @@
To replicate that behaviour, enable both remove_disabled_parts and strip.
@param text: The wikitext from which templates are extracted
- @type text: str
@param remove_disabled_parts: Remove disabled wikitext such as comments
and pre. If None (default), this is enabled when mwparserfromhell
is not available and disabled if mwparserfromhell is present.
@@ -1657,7 +1628,7 @@
return extract_templates_and_params_mwpfh(text, strip)
-def extract_templates_and_params_mwpfh(text, strip=False):
+def extract_templates_and_params_mwpfh(text: str, strip=False):
"""
Extract templates with params using mwparserfromhell.
@@ -1668,7 +1639,6 @@
mwparserfromhell package is installed.
@param text: The wikitext from which templates are extracted
- @type text: str
@return: list of template name and params
@rtype: list of tuple
"""
@@ -1695,8 +1665,9 @@
return result
-def extract_templates_and_params_regex(text, remove_disabled_parts=True,
- strip=True):
+def extract_templates_and_params_regex(text: str,
+ remove_disabled_parts: bool = True,
+ strip: bool = True):
"""
Extract templates with params using a regex with additional processing.
@@ -1707,7 +1678,6 @@
is not used.
@param text: The wikitext from which templates are extracted
- @type text: str
@return: list of template name and params
@rtype: list of tuple
"""
@@ -1850,7 +1820,7 @@
return result
-def extract_templates_and_params_regex_simple(text):
+def extract_templates_and_params_regex_simple(text: str):
"""
Extract top-level templates with params using only a simple regex.
@@ -1863,7 +1833,6 @@
argument value contains a '|', such as {{template|a={{b|c}} }}.
@param text: The wikitext from which templates are extracted
- @type text: str
@return: list of template name and params
@rtype: list of tuple of name and OrderedDict
"""
@@ -1891,17 +1860,17 @@
return result
-def glue_template_and_params(template_and_params):
+def glue_template_and_params(template_and_params) -> str:
"""Return wiki text of template glued from params.
You can use items from extract_templates_and_params here to get
an equivalent template wiki text (it may happen that the order
of the params changes).
"""
- (template, params) = template_and_params
+ template, params = template_and_params
text = ''
- for item in params:
- text += '|%s=%s\n' % (item, params[item])
+ for items in params.items():
+ text += '|{}={}\n'.format(*items)
return '{{%s\n%s}}' % (template, text)
@@ -1910,7 +1879,7 @@
# Page parsing functionality
# --------------------------
-def does_text_contain_section(pagetext, section):
+def does_text_contain_section(pagetext: str, section: str) -> bool:
"""
Determine whether the page text contains the given section title.
@@ -1922,10 +1891,7 @@
text link e.g. for categories and files.
@param pagetext: The wikitext of a page
- @type pagetext: str
@param section: a section of a page including wikitext markups
- @type section: str
-
"""
# match preceding colon for text links
section = re.sub(r'\\\[\\\[(\\?:)?', r'\[\[\:?', re.escape(section))
@@ -1935,15 +1901,13 @@
return bool(m)
-def reformat_ISBNs(text, match_func):
+def reformat_ISBNs(text: str, match_func) -> str:
"""Reformat ISBNs.
@param text: text containing ISBNs
- @type text: str
@param match_func: function to reformat matched ISBNs
@type match_func: callable
@return: reformatted text
- @rtype: str
"""
isbnR = re.compile(r'(?<=ISBN )(?P<code>[\d\-]+[\dXx])')
text = isbnR.sub(match_func, text)
@@ -1963,7 +1927,7 @@
@param name: a string with name of the timezone
"""
- def __init__(self, offset, name):
+ def __init__(self, offset: int, name: str):
"""Initializer."""
self.__offset = datetime.timedelta(minutes=offset)
self.__name = name
@@ -1982,23 +1946,20 @@
def __repr__(self):
"""Return the internal representation of the timezone."""
- return '%s(%s, %s)' % (
+ return '{}({}, {})'.format(
self.__class__.__name__,
self.__offset.days * 86400 + self.__offset.seconds,
self.__name
)
-class TimeStripper(object):
+class TimeStripper:
"""Find timestamp in page and return it as pywikibot.Timestamp object."""
def __init__(self, site=None):
"""Initializer."""
- if site is None:
- self.site = pywikibot.Site()
- else:
- self.site = site
+ self.site = pywikibot.Site() if site is None else site
self.origNames2monthNum = {}
for n, (_long, _short) in enumerate(self.site.months_names, start=1):
@@ -2074,7 +2035,7 @@
return self._comment_pat
@deprecated('module function', since='20151118')
- def findmarker(self, text, base='@@', delta='@'):
+ def findmarker(self, text: str, base: str = '@@', delta: str = '@'):
"""Find a string which is not part of text."""
return findmarker(text, base, delta)
@@ -2085,7 +2046,7 @@
line = line.replace(system[i], str(i))
return line
- def _last_match_and_replace(self, txt, pat):
+ def _last_match_and_replace(self, txt: str, pat):
"""
Take the rightmost match and replace with marker.
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/621946
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I6ec1aab650dc6c3a1fb27c1812c4b277a1f17361
Gerrit-Change-Number: 621946
Gerrit-PatchSet: 9
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Dvorapa <dvorapa(a)seznam.cz>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged