From 192c430137d478f6c4ac96227f470e2e60f6a686 Mon Sep 17 00:00:00 2001 From: xqt Date: Sat, 2 Nov 2024 13:42:09 +0100 Subject: [PATCH] Convert format strings to f-strings Change-Id: Ie72ee5c4d04c3b149b87a36e41a2cd0e32717f33 --- pywikibot/__init__.py | 9 ++- pywikibot/_wbtypes.py | 5 +- pywikibot/bot.py | 34 ++++----- pywikibot/bot_choice.py | 17 ++--- pywikibot/comms/eventstreams.py | 12 ++-- pywikibot/comms/http.py | 7 +- pywikibot/config.py | 22 +++--- pywikibot/cosmetic_changes.py | 22 +++--- pywikibot/data/api/_generators.py | 59 +++++++-------- pywikibot/data/api/_paraminfo.py | 9 +-- pywikibot/data/memento.py | 6 +- pywikibot/date.py | 12 ++-- pywikibot/diff.py | 17 ++--- pywikibot/family.py | 9 ++- pywikibot/flow.py | 3 +- pywikibot/i18n.py | 27 +++---- pywikibot/interwiki_graph.py | 5 +- pywikibot/logentries.py | 8 +-- pywikibot/logging.py | 4 +- pywikibot/login.py | 67 ++++++++--------- pywikibot/page/_basepage.py | 37 ++++------ pywikibot/page/_collections.py | 11 ++- pywikibot/page/_filepage.py | 4 +- pywikibot/page/_links.py | 33 +++++---- pywikibot/page/_revision.py | 7 +- pywikibot/page/_toolforge.py | 5 +- pywikibot/page/_wikibase.py | 24 ++++--- pywikibot/pagegenerators/_filters.py | 9 +-- pywikibot/pagegenerators/_generators.py | 5 +- pywikibot/proofreadpage.py | 29 ++++---- pywikibot/scripts/generate_family_file.py | 10 +-- pywikibot/scripts/generate_user_files.py | 21 +++--- pywikibot/scripts/login.py | 21 +++--- pywikibot/scripts/wrapper.py | 32 ++++----- pywikibot/site/_apisite.py | 21 +++--- pywikibot/site/_basesite.py | 30 ++++---- pywikibot/site/_datasite.py | 12 ++-- pywikibot/site/_extensions.py | 5 +- pywikibot/site/_generators.py | 5 +- pywikibot/site/_interwikimap.py | 6 +- pywikibot/site/_namespace.py | 23 +++--- pywikibot/site/_upload.py | 29 ++++---- pywikibot/site_detect.py | 15 ++-- pywikibot/specialbots/_upload.py | 2 +- pywikibot/textlib.py | 71 +++++++++---------- pywikibot/time.py | 8 +-- pywikibot/titletranslate.py | 5 +- pywikibot/tools/__init__.py | 12 ++-- pywikibot/tools/collections.py | 6 +- pywikibot/tools/djvu.py | 5 +- pywikibot/tools/itertools.py | 6 +- pywikibot/tools/threading.py | 3 +- pywikibot/userinterfaces/buffer_interface.py | 7 +- pywikibot/userinterfaces/gui.py | 7 +- .../userinterfaces/terminal_interface_base.py | 15 ++-- pywikibot/version.py | 8 ++- tests/link_tests.py | 2 +- 57 files changed, 449 insertions(+), 456 deletions(-) diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index 7cd07b5855..2375309712 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -244,8 +244,8 @@ def Site(code: str | None = None, # noqa: N802 debug(f"Instantiated {interface.__name__} object '{_sites[key]}'") if _sites[key].code != code: - warn('Site {} instantiated using different code "{}"' - .format(_sites[key], code), UserWarning, 2) + warn(f'Site {_sites[key]} instantiated using different code ' + f'"{code}"', UserWarning, 2) return _sites[key] @@ -337,9 +337,8 @@ def remaining() -> tuple[int, datetime.timedelta]: num, sec = remaining() if num > 0 and sec.total_seconds() > _config.noisysleep: - output('<>Waiting for {num} pages to be put. ' - 'Estimated time remaining: {sec}<>' - .format(num=num, sec=sec)) + output(f'<>Waiting for {num} pages to be put. ' + f'Estimated time remaining: {sec}<>') exit_queue = None if _putthread is not threading.current_thread(): diff --git a/pywikibot/_wbtypes.py b/pywikibot/_wbtypes.py index ee06fcc126..19317be18e 100644 --- a/pywikibot/_wbtypes.py +++ b/pywikibot/_wbtypes.py @@ -1032,9 +1032,8 @@ def _validate(page: pywikibot.Page, data_site: BaseSite, ending: str, # check should be enough. if not page.title().startswith('Data:') \ or not page.title().endswith(ending): - raise ValueError( - "Page must be in 'Data:' namespace and end in '{}' " - 'for {}.'.format(ending, label)) + raise ValueError(f"Page must be in 'Data:' namespace and end in " + f"'{ending}' for {label}.") def __init__(self, page: pywikibot.Page, diff --git a/pywikibot/bot.py b/pywikibot/bot.py index c5227e7a1b..15d425e4bf 100644 --- a/pywikibot/bot.py +++ b/pywikibot/bot.py @@ -296,8 +296,8 @@ def set_interface(module_name: str) -> None: """ global ui - ui_module = __import__('pywikibot.userinterfaces.{}_interface' - .format(module_name), fromlist=['UI']) + ui_module = __import__(f'pywikibot.userinterfaces.{module_name}_interface', + fromlist=['UI']) ui = ui_module.UI() assert ui is not None atexit.register(ui.flush) @@ -1327,8 +1327,8 @@ def _save_page(self, page: pywikibot.page.BasePage, pywikibot.info( f'Skipping {page.title()} because of edit conflict') elif isinstance(e, SpamblacklistError): - pywikibot.info('Cannot change {} because of blacklist ' - 'entry {}'.format(page.title(), e.url)) + pywikibot.info(f'Cannot change {page.title()} because of ' + f'blacklist entry {e.url}') elif isinstance(e, LockedPageError): pywikibot.info(f'Skipping {page.title()} (locked page)') else: @@ -1390,8 +1390,8 @@ def exit(self) -> None: for op, count in self.counter.items(): if not count or op == 'read': continue - pywikibot.info('{} operation time: {:.1f} seconds' - .format(op.capitalize(), write_seconds / count)) + pywikibot.info(f'{op.capitalize()} operation time: ' + f'{write_seconds / count:.1f} seconds') # exc_info contains exception from self.run() while terminating exc_info = sys.exc_info() @@ -1454,8 +1454,8 @@ def treat(self, page: Any) -> None: :class:`page.BasePage`. For other page types the :attr:`treat_page_type` must be set. """ - raise NotImplementedError('Method {}.treat() not implemented.' - .format(self.__class__.__name__)) + raise NotImplementedError( + f'Method {type(self).__name__}.treat() not implemented.') def setup(self) -> None: """Some initial setup before :meth:`run` operation starts. @@ -1814,8 +1814,8 @@ class CurrentPageBot(BaseBot): def treat_page(self) -> None: """Process one page (Abstract method).""" - raise NotImplementedError('Method {}.treat_page() not implemented.' - .format(self.__class__.__name__)) + raise NotImplementedError( + f'Method {type(self).__name__}.treat_page() not implemented.') def treat(self, page: pywikibot.page.BasePage) -> None: """Set page to current page and treat that page.""" @@ -1874,8 +1874,9 @@ def summary_parameters(self) -> dict[str, str]: def summary_parameters(self, value: dict[str, str]) -> None: """Set the i18n dictionary.""" if not isinstance(value, dict): - raise TypeError('"value" must be a dict but {} was found.' - .format(type(value).__name__)) + raise TypeError( + f'"value" must be a dict but {type(value).__name__} was found.' + ) self._summary_parameters = value @summary_parameters.deleter @@ -2250,8 +2251,8 @@ def create_item_for_page(self, page: pywikibot.page.BasePage, :return: pywikibot.ItemPage or None """ if not summary: - summary = 'Bot: New item with sitelink from {}'.format( - page.title(as_link=True, insite=self.repo)) + summary = ('Bot: New item with sitelink from ' + f'{page.title(as_link=True, insite=self.repo)}') if data is None: data = {} @@ -2327,9 +2328,8 @@ def treat_page_and_item(self, page: pywikibot.page.BasePage, Must be implemented in subclasses. """ - raise NotImplementedError('Method {}.treat_page_and_item() not ' - 'implemented.' - .format(self.__class__.__name__)) + raise NotImplementedError(f'Method {type(self).__name__}.' + 'treat_page_and_item() not implemented.') set_interface(config.userinterface) diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py index b7dd803544..eacc8ae188 100644 --- a/pywikibot/bot_choice.py +++ b/pywikibot/bot_choice.py @@ -181,9 +181,8 @@ def format(self, default: str | None = None) -> str: if self.shortcut == default: shortcut = self.shortcut.upper() if index >= 0: - return '{}[{}]{}'.format( - self.option[:index], shortcut, - self.option[index + len(self.shortcut):]) + return (f'{self.option[:index]}[{shortcut}]' + f'{self.option[index + len(self.shortcut):]}') return f'{self.option} [{shortcut}]' def result(self, value: str) -> Any: @@ -592,11 +591,9 @@ def out(self) -> str: """Highlighted output section of the text.""" start = max(0, self.start - self.context) end = min(len(self.text), self.end + self.context) - return '{}<<{color}>>{}<>{}'.format( - self.text[start:self.start], - self.text[self.start:self.end], - self.text[self.end:end], - color=self.color) + return (f'{self.text[start:self.start]}<<{self.color}>>' + f'{self.text[self.start:self.end]}<>' + f'{self.text[self.end:end]}') class UnhandledAnswer(Exception): # noqa: N818 @@ -775,8 +772,8 @@ def handle_link(self) -> Any: if self._new is False: question += 'be unlinked?' else: - question += 'target to <>{}<>?'.format( - self._new.canonical_title()) + question += (f'target to <>' + f'{self._new.canonical_title()}<>?') choice = pywikibot.input_choice(question, choices, default=self._default, diff --git a/pywikibot/comms/eventstreams.py b/pywikibot/comms/eventstreams.py index 5f46511f1f..a8a83cf8e4 100644 --- a/pywikibot/comms/eventstreams.py +++ b/pywikibot/comms/eventstreams.py @@ -186,8 +186,8 @@ def url(self): :raises NotImplementedError: no stream types specified """ if self._streams is None: - raise NotImplementedError('No streams specified for class {}' - .format(self.__class__.__name__)) + raise NotImplementedError( + f'No streams specified for class {type(self).__name__}') return '{host}{path}/{streams}{since}'.format( host=self._site.eventstreams_host(), path=self._site.eventstreams_path(), @@ -205,8 +205,8 @@ def set_maximum_items(self, value: int) -> None: """ if value is not None: self._total = int(value) - debug('{}: Set limit (maximum_items) to {}.' - .format(self.__class__.__name__, self._total)) + debug(f'{type(self).__name__}: Set limit (maximum_items) to ' + f'{self._total}.') def register_filter(self, *args, **kwargs): """Register a filter. @@ -363,8 +363,8 @@ def generator(self): else: warning(f'Unknown event {event.event} occurred.') - debug('{}: Stopped iterating due to exceeding item limit.' - .format(self.__class__.__name__)) + debug(f'{type(self).__name__}: Stopped iterating due to exceeding item' + ' limit.') del self.source diff --git a/pywikibot/comms/http.py b/pywikibot/comms/http.py index ceb6d6288c..71d629b7d1 100644 --- a/pywikibot/comms/http.py +++ b/pywikibot/comms/http.py @@ -400,7 +400,7 @@ def assign_fake_user_agent(use_fake_user_agent, uri): if use_fake_user_agent and isinstance(use_fake_user_agent, str): return use_fake_user_agent # Custom UA. raise ValueError('Invalid parameter: ' - 'use_fake_user_agent={}'.format(use_fake_user_agent)) + f'use_fake_user_agent={use_fake_user_agent}') def assign_user_agent(user_agent_format_string): if not user_agent_format_string or '{' in user_agent_format_string: @@ -554,8 +554,9 @@ def _try_decode(content: bytes, encoding: str | None) -> str | None: if header_codecs and charset_codecs and header_codecs != charset_codecs: pywikibot.warning( - 'Encoding "{}" requested but "{}" received in the ' - 'response header.'.format(charset, header_encoding)) + f'Encoding "{charset}" requested but "{header_encoding}" received' + ' in the response header.' + ) _encoding = _try_decode(response.content, header_encoding) \ or _try_decode(response.content, charset) diff --git a/pywikibot/config.py b/pywikibot/config.py index 1e45f5b989..c2089459f4 100644 --- a/pywikibot/config.py +++ b/pywikibot/config.py @@ -397,9 +397,10 @@ def exists(directory: str) -> bool: if __no_user_config is None: assert get_base_dir.__doc__ is not None exc_text += ( - '\nPlease check that {0} is stored in the correct location.' - '\nDirectory where {0} is searched is determined as follows:' - '\n\n '.format(config_file) + f'\nPlease check that {config_file} is stored in the correct' + ' location.' + f'\nDirectory where {config_file} is searched is determined as' + ' follows:\n\n ' ) + get_base_dir.__doc__ raise RuntimeError(exc_text) @@ -997,9 +998,10 @@ def _assert_types( DEPRECATED_VARIABLE = ( - '"{{}}" present in our {} is no longer a supported configuration variable ' - 'and should be removed. Please inform the maintainers if you depend on it.' - .format(user_config_file)) + f'"{{}}" present in our {user_config_file} is no longer a supported' + ' configuration variable and should be removed. Please inform the' + ' maintainers if you depend on it.' +) def _check_user_config_types( @@ -1025,10 +1027,10 @@ def _check_user_config_types( warn('\n' + fill(DEPRECATED_VARIABLE.format(name)), _ConfigurationDeprecationWarning) elif name not in _future_variables: - warn('\n' + fill('Configuration variable "{}" is defined in ' - 'your {} but unknown. It can be a misspelled ' - 'one or a variable that is no longer ' - 'supported.'.format(name, user_config_file)), + warn('\n' + fill(f'Configuration variable "{name}" is defined ' + f'in your {user_config_file} but unknown. It' + ' can be a misspelled one or a variable that' + ' is no longer supported.'), UserWarning) diff --git a/pywikibot/cosmetic_changes.py b/pywikibot/cosmetic_changes.py index c2337fe5fa..effc1cdac4 100644 --- a/pywikibot/cosmetic_changes.py +++ b/pywikibot/cosmetic_changes.py @@ -318,8 +318,9 @@ def change(self, text: str) -> bool | str: new_text = self._change(text) except Exception as e: if self.ignore == CANCEL.PAGE: - pywikibot.warning('Skipped "{}", because an error occurred.' - .format(self.title)) + pywikibot.warning( + f'Skipped "{self.title}", because an error occurred.' + ) pywikibot.error(e) return False raise @@ -334,8 +335,9 @@ def fixSelfInterwiki(self, text: str) -> str: Remove their language code prefix. """ if not self.talkpage and pywikibot.calledModuleName() != 'interwiki': - interwikiR = re.compile(r'\[\[(?: *:)? *{} *: *([^\[\]\n]*)\]\]' - .format(self.site.code)) + interwikiR = re.compile( + rf'\[\[(?: *:)? *{self.site.code} *: *([^\[\]\n]*)\]\]' + ) text = interwikiR.sub(r'[[\1]]', text) return text @@ -640,8 +642,8 @@ def handleOneLink(match: Match[str]) -> str: # instead of a pipelink elif (firstcase_label.startswith(firstcase_title) and trailR.sub('', label[len(titleWithSection):]) == ''): - newLink = '[[{}]]{}'.format(label[:len(titleWithSection)], - label[len(titleWithSection):]) + newLink = (f'[[{label[:len(titleWithSection)]}]]' + f'{label[len(titleWithSection):]}') else: # Try to capitalize the first letter of the title. @@ -891,9 +893,8 @@ def replace_link(match: Match[str]) -> str: # Match first a non space in the title to prevent that multiple # spaces at the end without title will be matched by it - title_regex = (r'(?P[^{sep}]+?)' - r'(\s+(?P[^\s].*?))' - .format(sep=separator)) + title_regex = (rf'(?P<link>[^{separator}]+?)' + r'(\s+(?P<title>[^\s].*?))') url_regex = fr'\[\[?{url}?\s*\]\]?' text = textlib.replaceExcept( text, @@ -1043,8 +1044,7 @@ def fixArabicLetters(self, text: str) -> str: faChrs = 'ءاآأإئؤبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهیةيك' + digits['fa'] # not to let bot edits in latin content - exceptions.append(re.compile('[^{fa}] *?"*? *?, *?[^{fa}]' - .format(fa=faChrs))) + exceptions.append(re.compile(f'[^{faChrs}] *?"*? *?, *?[^{faChrs}]')) text = textlib.replaceExcept(text, ',', '،', exceptions, site=self.site) if self.site.code == 'ckb': diff --git a/pywikibot/data/api/_generators.py b/pywikibot/data/api/_generators.py index 006f8d024d..1a316e94b2 100644 --- a/pywikibot/data/api/_generators.py +++ b/pywikibot/data/api/_generators.py @@ -131,8 +131,8 @@ def set_query_increment(self, value: int) -> None: """ self.query_increment = int(value) self.request[self.limit_name] = self.query_increment - pywikibot.debug('{}: Set query_increment to {}.' - .format(type(self).__name__, self.query_increment)) + pywikibot.debug(f'{type(self).__name__}: Set query_increment to ' + f'{self.query_increment}.') def set_maximum_items(self, value: int | str | None) -> None: """Set the maximum number of items to be retrieved from the wiki. @@ -147,10 +147,10 @@ def set_maximum_items(self, value: int | str | None) -> None: self.limit = int(value) if self.query_increment and self.limit < self.query_increment: self.request[self.limit_name] = self.limit - pywikibot.debug('{}: Set request item limit to {}' - .format(type(self).__name__, self.limit)) - pywikibot.debug('{}: Set limit (maximum_items) to {}.' - .format(type(self).__name__, self.limit)) + pywikibot.debug(f'{type(self).__name__}: Set request item ' + f'limit to {self.limit}') + pywikibot.debug(f'{type(self).__name__}: Set limit ' + f'(maximum_items) to {self.limit}.') @property def generator(self): @@ -176,14 +176,15 @@ def generator(self): yield item n += 1 if self.limit is not None and n >= self.limit: - pywikibot.debug('{}: Stopped iterating due to ' - 'exceeding item limit.' - .format(type(self).__name__)) + pywikibot.debug( + f'{type(self).__name__}: Stopped iterating due to' + ' exceeding item limit.' + ) return offset += n_items else: - pywikibot.debug('{}: Stopped iterating due to empty list in ' - 'response.'.format(type(self).__name__)) + pywikibot.debug(f'{type(self).__name__}: Stopped iterating' + ' due to empty list in response.') break @@ -238,8 +239,8 @@ def __init__(self, **kwargs) -> None: self.modules = parameters[modtype].split('|') break else: - raise Error('{}: No query module name found in arguments.' - .format(self.__class__.__name__)) + raise Error(f'{type(self).__name__}: No query module name found' + ' in arguments.') parameters['indexpageids'] = True # always ask for list of pageids self.continue_name = 'continue' @@ -266,10 +267,11 @@ def __init__(self, **kwargs) -> None: self.limited_module = module limited_modules.remove(module) break - pywikibot.log('{}: multiple requested query modules support limits' - "; using the first such module '{}' of {!r}" - .format(self.__class__.__name__, self.limited_module, - self.modules)) + pywikibot.log( + f'{type(self).__name__}: multiple requested query modules' + ' support limits; using the first such module ' + f"{self.limited_module}' of {self.modules!r}" + ) # Set limits for all remaining limited modules to max value. # Default values will only cause more requests and make the query @@ -379,8 +381,9 @@ def set_query_increment(self, value) -> None: self.query_limit = limit else: self.query_limit = min(self.api_limit, limit) - pywikibot.debug('{}: Set query_limit to {}.' - .format(type(self).__name__, self.query_limit)) + pywikibot.debug( + f'{type(self).__name__}: Set query_limit to {self.query_limit}.' + ) def set_maximum_items(self, value: int | str | None) -> None: """Set the maximum number of items to be retrieved from the wiki. @@ -410,8 +413,9 @@ def _update_limit(self) -> None: limit = int(param['max']) if self.api_limit is None or limit < self.api_limit: self.api_limit = limit - pywikibot.debug('{}: Set query_limit to {}.' - .format(type(self).__name__, self.api_limit)) + pywikibot.debug( + f'{type(self).__name__}: Set query_limit to {self.api_limit}.' + ) def support_namespace(self) -> bool: """Check if namespace is a supported parameter on this query. @@ -446,8 +450,8 @@ def set_namespace(self, namespaces): param = self.site._paraminfo.parameter('query+' + self.limited_module, 'namespace') if not param: - pywikibot.warning('{} module does not support a namespace ' - 'parameter'.format(self.limited_module)) + pywikibot.warning(f'{self.limited_module} module does not support' + ' a namespace parameter') warn('set_namespace() will be modified to raise TypeError ' 'when namespace parameter is not supported. ' 'It will be a Breaking Change, please update your code ' @@ -468,8 +472,8 @@ def set_namespace(self, namespaces): if 'multi' not in param and len(namespaces) != 1: if self._check_result_namespace is NotImplemented: - raise TypeError('{} module does not support multiple ' - 'namespaces'.format(self.limited_module)) + raise TypeError(f'{self.limited_module} module does not' + ' support multiple namespaces') self._namespaces = set(namespaces) namespaces = None @@ -608,9 +612,8 @@ def generator(self): self.data = self.request.submit() if not self.data or not isinstance(self.data, dict): - pywikibot.debug( - '{}: stopped iteration because no dict retrieved from api.' - .format(type(self).__name__)) + pywikibot.debug(f'{type(self).__name__}: stopped iteration' + ' because no dict retrieved from api.') break if 'query' in self.data and self.resultkey in self.data['query']: diff --git a/pywikibot/data/api/_paraminfo.py b/pywikibot/data/api/_paraminfo.py index 7d95660e9d..b4d7788582 100644 --- a/pywikibot/data/api/_paraminfo.py +++ b/pywikibot/data/api/_paraminfo.py @@ -193,10 +193,11 @@ def module_generator(): if len(missing_modules) == 1 and len(normalized_result) == 1: # Okay it's possible to recover normalized_result = next(iter(normalized_result.values())) - pywikibot.warning('The module "{0[name]}" ("{0[path]}") ' - 'was returned as path even though "{1}" ' - 'was requested'.format(normalized_result, - missing_modules[0])) + pywikibot.warning( + f'The module "{normalized_result["name"]}" ' + f'("{normalized_result["path"]}") was returned as path ' + f'even though "{missing_modules[0]}" was requested' + ) normalized_result['path'] = missing_modules[0] normalized_result['name'] = missing_modules[0].rsplit('+')[0] normalized_result = {missing_modules[0]: normalized_result} diff --git a/pywikibot/data/memento.py b/pywikibot/data/memento.py index 1a26d5b70d..9bd5110dda 100644 --- a/pywikibot/data/memento.py +++ b/pywikibot/data/memento.py @@ -9,7 +9,7 @@ # Parts of MementoClient class codes are # licensed under the BSD open source software license. # -# (C) Pywikibot team, 2015-2023 +# (C) Pywikibot team, 2015-2024 # # Distributed under the terms of the MIT license. # @@ -179,8 +179,8 @@ def get_native_timegate_uri(self, ) except (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError): # pragma: no cover - warning('Could not connect to URI {}, returning no native ' - 'URI-G'.format(original_uri)) + warning(f'Could not connect to URI {original_uri}, returning' + 'no native URI-G') return None debug('Request headers sent to search for URI-G: ' diff --git a/pywikibot/date.py b/pywikibot/date.py index 3a97e7579f..17f917c943 100644 --- a/pywikibot/date.py +++ b/pywikibot/date.py @@ -508,8 +508,9 @@ def dh(value: int, pattern: str, encf: encf_type, decf: decf_type, if isinstance(params, (tuple, list)): assert len(params) == len(decoders), ( - 'parameter count ({}) does not match decoder count ({})' - .format(len(params), len(decoders))) + f'parameter count ({len(params)}) does not match decoder count ' + f'({len(decoders)})' + ) # convert integer parameters into their textual representation str_params = tuple(_make_parameter(decoders[i], param) for i, param in enumerate(params)) @@ -1859,9 +1860,10 @@ def makeMonthNamedList(lang: str, pattern: str = '%s', # for all other days formats[dayMnthFmts[i]]['br'] = eval( 'lambda m: multi(m, [' - '(lambda v: dh_dayOfMnth(v, "%dañ {mname}"), lambda p: p == 1), ' - '(lambda v: dh_dayOfMnth(v, "%d {mname}"), alwaysTrue)])' - .format(mname=brMonthNames[i])) + f'(lambda v: dh_dayOfMnth(v, "%dañ {brMonthNames[i]}"),' + ' lambda p: p == 1), ' + f'(lambda v: dh_dayOfMnth(v, "%d {brMonthNames[i]}"), alwaysTrue)])' + ) # # Month of the Year: "en:May 1976" diff --git a/pywikibot/diff.py b/pywikibot/diff.py index c5c1ab3c1c..d1df913040 100644 --- a/pywikibot/diff.py +++ b/pywikibot/diff.py @@ -62,22 +62,16 @@ def __init__(self, a: str | Sequence[str], '+': 'lightgreen', '-': 'lightred', } - self.diff = list(self.create_diff()) self.diff_plain_text = ''.join(self.diff) self.diff_text = ''.join(self.format_diff()) - first, last = self.group[0], self.group[-1] self.a_rng = (first[1], last[2]) self.b_rng = (first[3], last[4]) - self.header = self.get_header() - self.diff_plain_text = '{hunk.header}\n{hunk.diff_plain_text}' \ - .format(hunk=self) + self.diff_plain_text = f'{self.header}\n{self.diff_plain_text}' self.diff_text = str(self.diff_text) - self.reviewed = self.PENDING - self.pre_context = 0 self.post_context = 0 @@ -91,7 +85,7 @@ def get_header_text(a_rng: tuple[int, int], b_rng: tuple[int, int], """Provide header for any ranges.""" a_rng = _format_range_unified(*a_rng) b_rng = _format_range_unified(*b_rng) - return '{0} -{1} +{2} {0}'.format(affix, a_rng, b_rng) + return f'{affix} -{a_rng} +{b_rng} {affix}' def create_diff(self) -> Iterable[str]: """Generator of diff text for this hunk, without formatting. @@ -382,9 +376,10 @@ def extend_context(start: int, end: int) -> str: context_range = self._get_context_range(hunks) - output = '<<aqua>>{}<<default>>\n{}'.format( - Hunk.get_header_text(*context_range), - extend_context(context_range[0][0], hunks[0].a_rng[0])) + output = ( + f'<<aqua>>{Hunk.get_header_text(*context_range)}<<default>>\n' + f'{extend_context(context_range[0][0], hunks[0].a_rng[0])}' + ) previous_hunk = None for hunk in hunks: if previous_hunk: diff --git a/pywikibot/family.py b/pywikibot/family.py index 673f1715a8..b96bf1402c 100644 --- a/pywikibot/family.py +++ b/pywikibot/family.py @@ -377,17 +377,16 @@ def load(fam: str | None = None): # codes can accept also underscore/dash. if not all(x in NAME_CHARACTERS for x in cls.name): warnings.warn( - 'Name of family {} must be ASCII letters and digits ' - '[a-zA-Z0-9]'.format(cls.name), + f'Name of family {cls.name} must be ASCII letters and digits' + ' [a-zA-Z0-9]', FamilyMaintenanceWarning, stacklevel=2, ) for code in cls.langs: if not all(x in CODE_CHARACTERS for x in code): warnings.warn( - 'Family {} code {} must be ASCII lowercase letters and ' - 'digits [a-z0-9] or underscore/dash [_-]' - .format(cls.name, code), + f'Family {cls.name} code {code} must be ASCII lowercase' + ' letters and digits [a-z0-9] or underscore/dash [_-]', FamilyMaintenanceWarning, stacklevel=2, ) diff --git a/pywikibot/flow.py b/pywikibot/flow.py index 614800d456..e5b136563b 100644 --- a/pywikibot/flow.py +++ b/pywikibot/flow.py @@ -109,7 +109,8 @@ def get(self, force: bool = False, get_redirect: bool = False if get_redirect or force: raise NotImplementedError( "Neither 'force' nor 'get_redirect' parameter is implemented " - 'in {}.get()'.format(self.__class__.__name__)) + f'in {self.__class__.__name__}.get()' + ) # TODO: Return more useful data return getattr(self, '_data', {}) diff --git a/pywikibot/i18n.py b/pywikibot/i18n.py index e07f0ddc3a..0c683af039 100644 --- a/pywikibot/i18n.py +++ b/pywikibot/i18n.py @@ -464,8 +464,8 @@ def replace_plural(match: Match[str]) -> str: variants = match[2] num = parameters[selector] if not isinstance(num, int): - raise ValueError("'{}' must be a number, not a {} ({})" - .format(selector, num, type(num).__name__)) + raise ValueError(f"'{selector}' must be a number, not a {num} " + f'({type(num).__name__})') plural_entries = [] specific_entries = {} @@ -635,8 +635,9 @@ def translate(code: str | pywikibot.site.BaseSite, return trans if not isinstance(parameters, Mapping): - raise ValueError('parameters should be a mapping, not {}' - .format(type(parameters).__name__)) + raise ValueError( + f'parameters should be a mapping, not {type(parameters).__name__}' + ) # else we check for PLURAL variants trans = _extract_plural(code, trans, parameters) @@ -776,10 +777,10 @@ def twtranslate( return fallback_prompt raise pywikibot.exceptions.TranslationError( - 'Unable to load messages package {} for bundle {}' - '\nIt can happen due to lack of i18n submodule or files. ' - 'See {}/i18n' - .format(_messages_package_name, twtitle, __url__)) + f'Unable to load messages package {_messages_package_name} for ' + f' bundle {twtitle}\nIt can happen due to lack of i18n submodule ' + f'or files. See {__url__}/i18n' + ) # if source is a site then use its lang attribute, otherwise it's a str lang = getattr(source, 'lang', source) @@ -810,8 +811,9 @@ def twtranslate( trans = _extract_plural(alt, trans, parameters) if parameters is not None and not isinstance(parameters, Mapping): - raise ValueError('parameters should be a mapping, not {}' - .format(type(parameters).__name__)) + raise ValueError( + f'parameters should be a mapping, not {type(parameters).__name__}' + ) if not only_plural and parameters: trans = trans % parameters @@ -949,8 +951,9 @@ def input(twtitle: str, prompt = fallback_prompt else: raise pywikibot.exceptions.TranslationError( - 'Unable to load messages package {} for bundle {}' - .format(_messages_package_name, twtitle)) + f'Unable to load messages package {_messages_package_name} for ' + f'bundle {twtitle}' + ) return pywikibot.input(prompt, password) diff --git a/pywikibot/interwiki_graph.py b/pywikibot/interwiki_graph.py index e19c8a546c..7925679679 100644 --- a/pywikibot/interwiki_graph.py +++ b/pywikibot/interwiki_graph.py @@ -110,9 +110,8 @@ def addNode(self, page: pywikibot.page.Page) -> None: """Add a node for page.""" assert self.graph is not None node = pydot.Node(self.getLabel(page), shape='rectangle') - node.set_URL('"http://{}{}"' - .format(page.site.hostname(), - page.site.get_address(page.title(as_url=True)))) + node.set_URL(f'"http://{page.site.hostname()}' + f'{page.site.get_address(page.title(as_url=True))}"') node.set_style('filled') node.set_fillcolor('white') node.set_fontsize('11') diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py index 642e974982..9b25276368 100644 --- a/pywikibot/logentries.py +++ b/pywikibot/logentries.py @@ -39,8 +39,8 @@ def __init__(self, apidata: dict[str, Any], self.site = site expected_type = self._expected_type if expected_type is not None and expected_type != self.type(): - raise Error('Wrong log type! Expecting {}, received {} instead.' - .format(expected_type, self.type())) + raise Error(f'Wrong log type! Expecting {expected_type}, received ' + f'{self.type()} instead.') def __missing__(self, key: str) -> None: """Debug when the key is missing. @@ -79,8 +79,8 @@ def __hash__(self) -> int: def __eq__(self, other: Any) -> bool: """Compare if self is equal to other.""" if not isinstance(other, LogEntry): - pywikibot.debug("'{}' cannot be compared with '{}'" - .format(type(self).__name__, type(other).__name__)) + pywikibot.debug(f"'{type(self).__name__}' cannot be compared with " + f"'{type(other).__name__}'") return False return self.logid() == other.logid() and self.site == other.site diff --git a/pywikibot/logging.py b/pywikibot/logging.py index 02bbce90a0..ef028cea8a 100644 --- a/pywikibot/logging.py +++ b/pywikibot/logging.py @@ -138,8 +138,8 @@ def logoutput(msg: Any, f'keyword argument "{key}={arg}"', since='7.2.0') if key in kwargs: - warning('{!r} is given as keyword argument {!r} already; ignoring ' - '{!r}'.format(key, arg, kwargs[key])) + warning(f'{key!r} is given as keyword argument {arg!r} already; ' + f'ignoring {kwargs[key]!r}') else: kwargs[key] = arg diff --git a/pywikibot/login.py b/pywikibot/login.py index 2c9c8c6d27..b670af633a 100644 --- a/pywikibot/login.py +++ b/pywikibot/login.py @@ -100,12 +100,13 @@ def __init__(self, password: str | None = None, user = code_to_usr.get(site.code) or code_to_usr['*'] except KeyError: raise NoUsernameError( - 'ERROR: ' - 'username for {site.family.name}:{site.code} is undefined.' - '\nIf you have a username for that site, please add a ' - 'line to user config file (user-config.py) as follows:\n' - "usernames['{site.family.name}']['{site.code}'] = " - "'myUsername'".format(site=site)) + f'ERROR: username for {site.family.name}:{site.code} is' + ' undefined.\nIf you have a username for that site,' + ' please add a line to user config file (user-config.py)' + ' as follows:\n' + f"usernames['{site.family.name}']['{site.code}'] =" + " 'myUsername'" + ) self.password = password self.login_name = self.username = user if getattr(config, 'password_file', ''): @@ -122,11 +123,10 @@ def check_user_exists(self) -> None: # convert any Special:BotPassword usernames to main account equivalent main_username = self.username if '@' in self.username: - warn( - 'When using BotPasswords it is recommended that you store ' - 'your login credentials in a password_file instead. See ' - '{}/BotPasswords for instructions and more information.' - .format(__url__)) + warn('When using BotPasswords it is recommended that you store' + ' your login credentials in a password_file instead. See ' + f'{__url__}/BotPasswords for instructions and more' + ' information.') main_username = self.username.partition('@')[0] try: @@ -134,15 +134,16 @@ def check_user_exists(self) -> None: user = next(data, {'name': None}) except APIError as e: if e.code == 'readapidenied': - pywikibot.warning("Could not check user '{}' exists on {}" - .format(main_username, self.site)) + pywikibot.warning(f"Could not check user '{main_username}' " + f'exists on {self.site}') return raise if user['name'] != main_username: # Report the same error as server error code NotExists - raise NoUsernameError("Username '{}' does not exist on {}" - .format(main_username, self.site)) + raise NoUsernameError( + f"Username '{main_username}' does not exist on {self.site}" + ) def botAllowed(self) -> bool: """Check whether the bot is listed on a specific page. @@ -280,9 +281,10 @@ def login(self, retry: bool = False, autocreate: bool = False) -> bool: # As we don't want the password to appear on the screen, we set # password = True self.password = pywikibot.input( - 'Password for user {name} on {site} (no characters will be ' - 'shown):'.format(name=self.login_name, site=self.site), - password=True) + f'Password for user {self.login_name} on {self.site}' + ' (no characters will be shown):', + password=True + ) else: pywikibot.info(f'Logging in to {self.site} as {self.login_name}') @@ -293,8 +295,8 @@ def login(self, retry: bool = False, autocreate: bool = False) -> bool: # TODO: investigate other unhandled API codes if error_code in self._api_error: - error_msg = 'Username {!r} {} on {}'.format( - self.login_name, self._api_error[error_code], self.site) + error_msg = (f'Username {self.login_name!r} ' + f'{self._api_error[error_code]} on {self.site}') if error_code in ('Failed', 'FAIL'): error_msg += f'.\n{e.info}' raise NoUsernameError(error_msg) @@ -527,10 +529,11 @@ def __init__(self, password: str | None = None, assert password is not None and user is not None super().__init__(password=None, site=site, user=None) if self.password: - pywikibot.warn('Password exists in password file for {login.site}:' - '{login.username}. Password is unnecessary and ' - 'should be removed if OAuth enabled.' - .format(login=self)) + pywikibot.warn( + f'Password exists in password file for {self.site}: ' + f'{self.username}. Password is unnecessary and should be' + ' removed if OAuth enabled.' + ) self._consumer_token = (user, password) self._access_token: tuple[str, str] | None = None @@ -544,9 +547,8 @@ def login(self, retry: bool = False, force: bool = False) -> bool: :param force: force to re-authenticate """ if self.access_token is None or force: - pywikibot.info( - 'Logging in to {site} via OAuth consumer {key}' - .format(key=self.consumer_token[0], site=self.site)) + pywikibot.info(f'Logging in to {self.site} via OAuth consumer ' + f'{self.consumer_token[0]}') consumer_token = mwoauth.ConsumerToken(*self.consumer_token) handshaker = mwoauth.Handshaker( self.site.base_url(self.site.path()), consumer_token) @@ -554,9 +556,10 @@ def login(self, retry: bool = False, force: bool = False) -> bool: redirect, request_token = handshaker.initiate() pywikibot.stdout('Authenticate via web browser..') webbrowser.open(redirect) - pywikibot.stdout('If your web browser does not open ' - 'automatically, please point it to: {}' - .format(redirect)) + pywikibot.stdout( + 'If your web browser does not open automatically, please ' + f'point it to: {redirect}' + ) request_qs = pywikibot.input('Response query string: ') access_token = handshaker.complete(request_token, request_qs) self._access_token = (access_token.key, access_token.secret) @@ -567,8 +570,8 @@ def login(self, retry: bool = False, force: bool = False) -> bool: return self.login(retry=True, force=force) return False else: - pywikibot.info('Logged in to {site} via consumer {key}' - .format(key=self.consumer_token[0], site=self.site)) + pywikibot.info(f'Logged in to {self.site} via consumer ' + f'{self.consumer_token[0]}') return True @property diff --git a/pywikibot/page/_basepage.py b/pywikibot/page/_basepage.py index 85d255a68c..27ca737cbb 100644 --- a/pywikibot/page/_basepage.py +++ b/pywikibot/page/_basepage.py @@ -129,9 +129,8 @@ def __init__(self, source, title: str = '', ns=0) -> None: self._link = source self._revisions = {} else: - raise Error( - "Invalid argument type '{}' in Page initializer: {}" - .format(type(source), source)) + raise Error(f"Invalid argument type '{type(source)}' in Page " + f'initializer: {source}') @property def site(self): @@ -257,8 +256,7 @@ def title( or self.site.code != target_code)): if self.site.family.name not in ( target_family, self.site.code): - title = '{site.family.name}:{site.code}:{title}'.format( - site=self.site, title=title) + title = f'{self.site.family.name}:{self.site.code}:{title}' else: # use this form for sites like commons, where the # code is the same as the family name @@ -672,9 +670,8 @@ def extract(self, variant: str = 'plain', *, extract = shorten(extract, chars, break_long_words=False, placeholder='…') else: - raise ValueError( - 'variant parameter must be "plain", "html" or "wiki", not "{}"' - .format(variant)) + raise ValueError('variant parameter must be "plain", "html" or ' + f'"wiki", not "{variant}"') if not lines: return extract @@ -847,9 +844,9 @@ def isCategoryRedirect(self) -> bool: self._catredirect = p.title() else: pywikibot.warning( - 'Category redirect target {} on {} is not a ' - 'category'.format(p.title(as_link=True), - self.title(as_link=True))) + f'Category redirect target {p.title(as_link=True)}' + f' on {self.title(as_link=True)} is not a category' + ) else: pywikibot.warning( 'No target found for category redirect on ' @@ -1533,9 +1530,8 @@ def linkedPages( f'keyword argument "{key}={arg}"', since='7.0.0') if key in kwargs: - pywikibot.warning('{!r} is given as keyword argument {!r} ' - 'already; ignoring {!r}' - .format(key, arg, kwargs[key])) + pywikibot.warning(f'{key!r} is given as keyword argument ' + f'{arg!r} already; ignoring {kwargs[key]!r}') else: kwargs[key] = arg @@ -1867,8 +1863,8 @@ def getVersionHistoryTable(self, result += '! oldid || date/time || username || edit summary\n' for entry in self.revisions(reverse=reverse, total=total): result += '|----\n' - result += ('| {r.revid} || {r.timestamp} || {r.user} || ' - '<nowiki>{r.comment}</nowiki>\n'.format(r=entry)) + result += (f'| {entry.revid} || {entry.timestamp} || {entry.user} ' + f'|| <nowiki>{entry.comment}</nowiki>\n') result += '|}\n' return result @@ -2233,12 +2229,9 @@ def change_category(self, old_cat, new_cat, return False if old_cat not in cats: - if self.namespace() != 10: - pywikibot.error( - f'{self} is not in category {old_cat.title()}!') - else: - pywikibot.info('{} is not in category {}, skipping...' - .format(self, old_cat.title())) + pywikibot.info( + f'{self} is not in category {old_cat.title()}, skipping...' + ) return False # This prevents the bot from adding new_cat if it is already present. diff --git a/pywikibot/page/_collections.py b/pywikibot/page/_collections.py index 17c0ad826e..5f6822f11b 100644 --- a/pywikibot/page/_collections.py +++ b/pywikibot/page/_collections.py @@ -444,7 +444,7 @@ def normalizeData(cls, data) -> dict: if not isinstance(json, dict): raise ValueError( "Couldn't determine the site and title of the value: " - '{!r}'.format(json)) + f'{json!r}') db_name = json['site'] norm_data[db_name] = json return norm_data @@ -511,11 +511,10 @@ def __init__(self, repo, data=None): def _validate_isinstance(self, obj): if not isinstance(obj, self.type_class): raise TypeError( - '{} should only hold instances of {}, ' - 'instance of {} was provided' - .format(self.__class__.__name__, - self.type_class.__name__, - obj.__class__.__name__)) + f'{type(self).__name__} should only hold instances of ' + f'{self.type_class.__name__}, instance of ' + f'{type(obj).__name__} was provided' + ) def __getitem__(self, index): if isinstance(index, str): diff --git a/pywikibot/page/_filepage.py b/pywikibot/page/_filepage.py index d8b1b303d4..703d178707 100644 --- a/pywikibot/page/_filepage.py +++ b/pywikibot/page/_filepage.py @@ -155,8 +155,8 @@ def getImagePageHtml(self) -> str: # noqa: N802 same FilePage object, the page will only be downloaded once. """ if not hasattr(self, '_imagePageHtml'): - path = '{}/index.php?title={}'.format(self.site.scriptpath(), - self.title(as_url=True)) + path = (f'{self.site.scriptpath()}/index.php?' + f'title={self.title(as_url=True)}') self._imagePageHtml = http.request(self.site, path).text return self._imagePageHtml diff --git a/pywikibot/page/_links.py b/pywikibot/page/_links.py index 335067415f..fe24dacf93 100644 --- a/pywikibot/page/_links.py +++ b/pywikibot/page/_links.py @@ -6,7 +6,7 @@ its contents. """ # -# (C) Pywikibot team, 2008-2023 +# (C) Pywikibot team, 2008-2024 # # Distributed under the terms of the MIT license. # @@ -155,8 +155,9 @@ def ns_title(self, onsite=None): break else: raise InvalidTitleError( - 'No corresponding title found for namespace {} on {}.' - .format(self.namespace, onsite)) + 'No corresponding title found for namespace ' + f'{self.namespace} on {onsite}.' + ) if self.namespace != Namespace.MAIN: return f'{name}:{self.title}' @@ -308,9 +309,10 @@ def __init__(self, text, source=None, default_namespace=0) -> None: # Cleanup whitespace sep = self._source.family.title_delimiter_and_aliases[0] t = re.sub( - '[{}\xa0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+' - .format(self._source.family.title_delimiter_and_aliases), - sep, t) + f'[{self._source.family.title_delimiter_and_aliases}' + '\xa0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+', + sep, t + ) # Strip spaces at both ends t = t.strip() # Remove left-to-right and right-to-left markers. @@ -395,16 +397,18 @@ def parse(self): break # text before : doesn't match any known prefix except SiteDefinitionError as e: raise SiteDefinitionError( - '{} is not a local page on {}, and the interwiki ' - 'prefix {} is not supported by Pywikibot!\n{}' - .format(self._text, self._site, prefix, e)) + f'{self._text} is not a local page on {self._site}, and ' + f'the interwiki prefix {prefix} is not supported by ' + f'Pywikibot!\n{e}' + ) else: if first_other_site: if not self._site.local_interwiki(prefix): raise InvalidTitleError( - '{} links to a non local site {} via an ' - 'interwiki link to {}.'.format( - self._text, newsite, first_other_site)) + f'{self._text} links to a non local site ' + f'{newsite} via an interwiki link to ' + f'{first_other_site}.' + ) elif newsite != self._source: first_other_site = newsite self._site = newsite @@ -435,8 +439,9 @@ def parse(self): next_ns = t[:t.index(':')] if self._site.namespaces.lookup_name(next_ns): raise InvalidTitleError( - "The (non-)talk page of '{}' is a valid title " - 'in another namespace.'.format(self._text)) + f"The (non-)talk page of '{self._text}' is a valid" + ' title in another namespace.' + ) # Reject illegal characters. m = Link.illegal_titles_pattern.search(t) diff --git a/pywikibot/page/_revision.py b/pywikibot/page/_revision.py index 27d077cc2f..8d9075cd68 100644 --- a/pywikibot/page/_revision.py +++ b/pywikibot/page/_revision.py @@ -1,6 +1,6 @@ """Object representing page revision.""" # -# (C) Pywikibot team, 2008-2022 +# (C) Pywikibot team, 2008-2024 # # Distributed under the terms of the MIT license. # @@ -95,5 +95,6 @@ def __str__(self) -> str: def __missing__(self, key): """Provide backward compatibility for exceptions.""" # raise AttributeError instead of KeyError for backward compatibility - raise AttributeError("'{}' object has no attribute '{}'" - .format(self.__class__.__name__, key)) + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{key}'" + ) diff --git a/pywikibot/page/_toolforge.py b/pywikibot/page/_toolforge.py index 29b22e9595..1d7aa8aa81 100644 --- a/pywikibot/page/_toolforge.py +++ b/pywikibot/page/_toolforge.py @@ -50,9 +50,8 @@ def _check_wh_supported(self): 'main_authors method is implemented for wikipedia family only') if self.site.code not in self.WIKIBLAME_CODES: - raise NotImplementedError( - 'main_authors method is not implemented for wikipedia:{}' - .format(self.site.code)) + raise NotImplementedError('main_authors method is not implemented ' + f'for wikipedia:{self.site.code}') if self.namespace() != pywikibot.site.Namespace.MAIN: raise NotImplementedError( diff --git a/pywikibot/page/_wikibase.py b/pywikibot/page/_wikibase.py index bea70cd926..c6d860f625 100644 --- a/pywikibot/page/_wikibase.py +++ b/pywikibot/page/_wikibase.py @@ -113,8 +113,8 @@ def __init__(self, repo, id_: str | None = None) -> None: def __repr__(self) -> str: if self.id != '-1': - return 'pywikibot.page.{}({!r}, {!r})'.format( - self.__class__.__name__, self.repo, self.id) + return (f'pywikibot.page.{type(self).__name__}' + f'({self.repo!r}, {self.id!r})') return f'pywikibot.page.{self.__class__.__name__}({self.repo!r})' @classmethod @@ -135,8 +135,9 @@ def __getattr__(self, name): return getattr(self, name) return self.get()[name] - raise AttributeError("'{}' object has no attribute '{}'" - .format(self.__class__.__name__, name)) + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) def _initialize_empty(self): for key, cls in self.DATA_ATTRIBUTES.items(): @@ -609,9 +610,8 @@ def __init__(self, site, title: str = '', **kwargs) -> None: if not isinstance(site, pywikibot.site.DataSite): raise TypeError('site must be a pywikibot.site.DataSite object') if title and ('ns' not in kwargs and 'entity_type' not in kwargs): - pywikibot.debug('{}.__init__: {} title {!r} specified without ' - 'ns or entity_type' - .format(type(self).__name__, site, title)) + pywikibot.debug(f'{type(self).__name__}.__init__: {site} title ' + f'{title!r} specified without ns or entity_type') self._namespace = None @@ -658,8 +658,9 @@ def __init__(self, site, title: str = '', **kwargs) -> None: if self._namespace: if self._link.namespace != self._namespace.id: - raise ValueError("'{}' is not in the namespace {}" - .format(title, self._namespace.id)) + raise ValueError( + f"'{title}' is not in the namespace {self._namespace.id}" + ) else: # Neither ns or entity_type was provided. # Use the _link to determine entity type. @@ -723,8 +724,9 @@ def get(self, force: bool = False, *args, **kwargs) -> dict: """ if args or kwargs: raise NotImplementedError( - '{}.get does not implement var args: {!r} and {!r}'.format( - self.__class__.__name__, args, kwargs)) + f'{type(self).__name__}.get does not implement var args: ' + f'{args!r} and {kwargs!r}' + ) # todo: this variable is specific to ItemPage lazy_loading_id = not hasattr(self, 'id') and hasattr(self, '_site') diff --git a/pywikibot/pagegenerators/_filters.py b/pywikibot/pagegenerators/_filters.py index a9b3203799..381f598d29 100644 --- a/pywikibot/pagegenerators/_filters.py +++ b/pywikibot/pagegenerators/_filters.py @@ -1,6 +1,6 @@ """Page filter generators provided by the pagegenerators module.""" # -# (C) Pywikibot team, 2008-2022 +# (C) Pywikibot team, 2008-2024 # # Distributed under the terms of the MIT license. # @@ -387,11 +387,8 @@ def to_be_yielded(edit: _Edit, edit_time = rev.timestamp # type: ignore[attr-defined] - msg = '{prefix} edit on {page} was on {time}.\n' \ - 'Too {{when}}. Skipping.' \ - .format(prefix=type(edit).__name__, - page=page, - time=edit_time.isoformat()) + msg = (f'{type(edit).__name__} edit on {page} was on ' + f'{edit_time.isoformat()}.\nToo {{when}}. Skipping.') if edit_time < edit.edit_start: _output_if(show_filtered, msg.format(when='old')) diff --git a/pywikibot/pagegenerators/_generators.py b/pywikibot/pagegenerators/_generators.py index d15ca69607..e50cc546c8 100644 --- a/pywikibot/pagegenerators/_generators.py +++ b/pywikibot/pagegenerators/_generators.py @@ -143,9 +143,8 @@ def LogeventsPageGenerator(logtype: str | None = None, try: yield entry.page() except KeyError as e: - pywikibot.warning('LogeventsPageGenerator: ' - 'failed to load page for {!r}; skipping' - .format(entry.data)) + pywikibot.warning('LogeventsPageGenerator: failed to load page ' + f'for {entry.data!r}; skipping') pywikibot.error(e) diff --git a/pywikibot/proofreadpage.py b/pywikibot/proofreadpage.py index 06f4146182..a4526036bc 100644 --- a/pywikibot/proofreadpage.py +++ b/pywikibot/proofreadpage.py @@ -442,13 +442,13 @@ def __init__(self, source: PageSourceType, title: str = '') -> None: site = source super().__init__(source, title) if self.namespace() != site.proofread_page_ns: - raise ValueError('Page {} must belong to {} namespace' - .format(self.title(), site.proofread_page_ns)) + raise ValueError(f'Page {self.title()} must belong to ' + f'{site.proofread_page_ns} namespace') # Ensure that constants are in line with Extension values. level_list = list(self.site.proofread_levels) if level_list != self.PROOFREAD_LEVELS: - raise ValueError('QLs do not match site values: {} != {}' - .format(level_list, self.PROOFREAD_LEVELS)) + raise ValueError(f'QLs do not match site values: {level_list} != ' + f'{self.PROOFREAD_LEVELS}') self._base, self._base_ext, self._num = self._parse_title() self._multi_page = self._base_ext in self._MULTI_PAGE_EXT @@ -587,8 +587,8 @@ def ql(self) -> int: @decompose def ql(self, value: int) -> None: if value not in self.site.proofread_levels: - raise ValueError('Not valid QL value: {} (legal values: {})' - .format(value, list(self.site.proofread_levels))) + raise ValueError(f'Not valid QL value: {value} (legal values: ' + f'{list(self.site.proofread_levels)})') # TODO: add logic to validate ql value change, considering # site.proofread_levels. self._full_header.ql = value @@ -611,8 +611,10 @@ def status(self) -> str | None: try: return self.site.proofread_levels[self.ql] except KeyError: - pywikibot.warning('Not valid status set for {}: quality level = {}' - .format(self.title(as_link=True), self.ql)) + pywikibot.warning( + f'Not valid status set for {self.title(as_link=True)}: ' + f'quality level = {self.ql}' + ) return None def without_text(self) -> None: @@ -1047,8 +1049,8 @@ def __init__(self, source: PageSourceType, title: str = '') -> None: site = source super().__init__(source, title) if self.namespace() != site.proofread_index_ns: - raise ValueError('Page {} must belong to {} namespace' - .format(self.title(), site.proofread_index_ns)) + raise ValueError(f'Page {self.title()} must belong to ' + f'{site.proofread_index_ns} namespace') self._all_page_links = {} @@ -1181,7 +1183,8 @@ def _get_page_mappings(self) -> None: if not self._soup.find_all('a', attrs=attrs): raise ValueError( 'Missing class="qualityN prp-pagequality-N" or ' - 'class="new" in: {}.'.format(self)) + f'class="new" in: {self}.' + ) page_cnt = 0 for a_tag in self._soup.find_all('a', attrs=attrs): @@ -1267,8 +1270,8 @@ def page_gen( end = self.num_pages if not 1 <= start <= end <= self.num_pages: - raise ValueError('start={}, end={} are not in valid range (1, {})' - .format(start, end, self.num_pages)) + raise ValueError(f'start={start}, end={end} are not in valid ' + f'range (1, {self.num_pages})') # All but 'Without Text' if filter_ql is None: diff --git a/pywikibot/scripts/generate_family_file.py b/pywikibot/scripts/generate_family_file.py index 3ec90561ba..c2b2e00681 100755 --- a/pywikibot/scripts/generate_family_file.py +++ b/pywikibot/scripts/generate_family_file.py @@ -118,8 +118,8 @@ def get_params(self) -> bool: # pragma: no cover return False if any(x not in NAME_CHARACTERS for x in self.name): - print('ERROR: Name of family "{}" must be ASCII letters and ' - 'digits [a-zA-Z0-9]'.format(self.name)) + print(f'ERROR: Name of family "{self.name}" must be ASCII letters' + ' and digits [a-zA-Z0-9]') return False return True @@ -155,9 +155,9 @@ def run(self) -> None: self.wikis[w.lang] = w print('\n==================================' - '\nAPI url: {w.api}' - '\nMediaWiki version: {w.version}' - '\n==================================\n'.format(w=w)) + f'\nAPI url: {w.api}' + f'\nMediaWiki version: {w.version}' + '\n==================================\n') self.getlangs(w) self.getapis() diff --git a/pywikibot/scripts/generate_user_files.py b/pywikibot/scripts/generate_user_files.py index f0e0a98898..022471a6ce 100755 --- a/pywikibot/scripts/generate_user_files.py +++ b/pywikibot/scripts/generate_user_files.py @@ -86,12 +86,12 @@ def change_base_dir(): # config would find that file return new_base - msg = fill("""WARNING: Your user files will be created in the directory + msg = fill(f"""WARNING: Your user files will be created in the directory '{new_base}' you have chosen. To access these files, you will either have to use the argument "-dir:{new_base}" every time you run the bot, or set the environment variable "PYWIKIBOT_DIR" equal to this directory name in your operating system. See your operating system documentation for how to -set environment variables.""".format(new_base=new_base), width=76) +set environment variables.""", width=76) pywikibot.info(msg) if pywikibot.input_yn('Is this OK?', default=False, automatic_quit=False): return new_base @@ -166,8 +166,8 @@ def get_site_and_lang( mycode = pywikibot.input(message, default=default_lang, force=force) if known_langs and mycode and mycode not in known_langs \ and not pywikibot.input_yn( - fill('The site code {!r} is not in the list of known sites. ' - 'Do you want to continue?'.format(mycode)), + fill(f'The site code {mycode!r} is not in the list of known' + ' sites. Do you want to continue?'), default=False, automatic_quit=False): mycode = None @@ -382,16 +382,16 @@ def create_user_config( botpasswords = [] userset = {user.name for user in userlist} for username in userset: - if pywikibot.input_yn('Do you want to add a BotPassword for {}?' - .format(username), force=force, default=False): + if pywikibot.input_yn('Do you want to add a BotPassword for ' + f'{username}?', force=force, default=False): if msg: pywikibot.info(msg) msg = None message = f'BotPassword\'s "bot name" for {username}' botpasswordname = pywikibot.input(message, force=force) - message = 'BotPassword\'s "password" for "{}" ' \ + message = f'BotPassword\'s "password" for "{botpasswordname}" ' \ '(no characters will be shown)' \ - .format(botpasswordname) + botpasswordpass = pywikibot.input(message, force=force, password=True) if botpasswordname and botpasswordpass: @@ -403,8 +403,9 @@ def create_user_config( f"# usernames['{main_family}']['{main_code}'] = 'MyUsername'") else: usernames = '\n'.join( - "usernames['{user.family}']['{user.code}'] = '{user.name}'" - .format(user=user) for user in userlist) + f"usernames['{user.family}']['{user.code}'] = '{user.name}'" + for user in userlist + ) # Arbitrarily use the first key as default settings main_family, main_code = userlist[0].family, userlist[0].code botpasswords = '\n'.join( diff --git a/pywikibot/scripts/login.py b/pywikibot/scripts/login.py index 75c6088224..e47f01f1ab 100755 --- a/pywikibot/scripts/login.py +++ b/pywikibot/scripts/login.py @@ -44,7 +44,7 @@ moved to :mod:`pywikibot.scripts` folder """ # -# (C) Pywikibot team, 2003-2023 +# (C) Pywikibot team, 2003-2024 # # Distributed under the terms of the MIT license. # @@ -83,11 +83,11 @@ def _oauth_login(site) -> None: else: oauth_token = login_manager.consumer_token + login_manager.access_token pywikibot.info( - 'Logged in on {site} as {username} via OAuth consumer {consumer}\n' - 'NOTE: To use OAuth, you need to copy the following line to your ' - 'user config file:\n authenticate[{hostname!r}] = {oauth_token}' - .format(site=site, username=site.username(), consumer=consumer_key, - hostname=site.hostname(), oauth_token=oauth_token)) + f'Logged in on {site} as {site.username()} via OAuth consumer ' + f'{consumer_key}\nNOTE: To use OAuth, you need to copy the' + ' following line to your user config file:\n' + f'authenticate[{site.hostname()!r}] = {oauth_token}' + ) def login_one_site(code, family, oauth, logout, autocreate): @@ -95,9 +95,8 @@ def login_one_site(code, family, oauth, logout, autocreate): try: site = pywikibot.Site(code, family) except SiteDefinitionError: - pywikibot.error('{}:{} is not a valid site, ' - 'please remove it from your user-config' - .format(family, code)) + pywikibot.error(f'{family}:{code} is not a valid site, ' + 'please remove it from your user-config') return if oauth: @@ -172,5 +171,5 @@ def main(*args: str) -> None: start = datetime.datetime.now() with suppress(KeyboardInterrupt): main() - pywikibot.info('\nExecution time: {} seconds' - .format((datetime.datetime.now() - start).seconds)) + pywikibot.info('\nExecution time: ' + f'{(datetime.datetime.now() - start).seconds} seconds') diff --git a/pywikibot/scripts/wrapper.py b/pywikibot/scripts/wrapper.py index fb64afd088..3a16b28396 100755 --- a/pywikibot/scripts/wrapper.py +++ b/pywikibot/scripts/wrapper.py @@ -77,25 +77,26 @@ def check_pwb_versions(package: str): wikibot_version = Version(pwb.__version__) if scripts_version.release > wikibot_version.release: # pragma: no cover - print('WARNING: Pywikibot version {} is behind scripts package ' - 'version {}.\nYour Pywikibot may need an update or be ' - 'misconfigured.\n'.format(wikibot_version, scripts_version)) + print(f'WARNING: Pywikibot version {wikibot_version} is behind ' + f'scripts package version {scripts_version}.\n' + 'Your Pywikibot may need an update or be misconfigured.\n') # calculate previous minor release if wikibot_version.minor > 0: # pragma: no cover - prev_wikibot = Version('{v.major}.{}.{v.micro}' - .format(wikibot_version.minor - 1, - v=wikibot_version)) + prev_wikibot = Version( + f'{wikibot_version.major}.{wikibot_version.minor - 1}.' + f'{wikibot_version.micro}' + ) if scripts_version.release < prev_wikibot.release: - print('WARNING: Scripts package version {} is behind legacy ' - 'Pywikibot version {} and current version {}\nYour scripts ' - 'may need an update or be misconfigured.\n' - .format(scripts_version, prev_wikibot, wikibot_version)) + print(f'WARNING: Scripts package version {scripts_version} is ' + f'behind legacy Pywikibot version {prev_wikibot} and ' + f'current version {wikibot_version}\n' + 'Your scripts may need an update or be misconfigured.\n') elif scripts_version.release < wikibot_version.release: # pragma: no cover - print('WARNING: Scripts package version {} is behind current version ' - '{}\nYour scripts may need an update or be misconfigured.\n' - .format(scripts_version, wikibot_version)) + print(f'WARNING: Scripts package version {scripts_version} is behind ' + f'current version {wikibot_version}\n' + 'Your scripts may need an update or be misconfigured.\n') del Version @@ -359,9 +360,8 @@ def find_alternates(filename, script_paths): script = similar_scripts[0] wait_time = config.pwb_autostart_waittime info('NOTE: Starting the most similar script ' - '<<lightyellow>>{}.py<<default>>\n' - ' in {} seconds; type CTRL-C to stop.' - .format(script, wait_time)) + f'<<lightyellow>>{script}.py<<default>>\n' + f' in {wait_time} seconds; type CTRL-C to stop.') try: sleep(wait_time) # Wait a bit to let it be cancelled except KeyboardInterrupt: diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py index 9e819e3b8b..81c3a85e5a 100644 --- a/pywikibot/site/_apisite.py +++ b/pywikibot/site/_apisite.py @@ -717,8 +717,8 @@ def get_globaluserinfo(self, elif isinstance(user, int): param = {'guiid': user} else: - raise TypeError("Inappropriate argument type of 'user' ({})" - .format(type(user).__name__)) + raise TypeError("Inappropriate argument type of 'user' " + f'({type(user).__name__})') if force or user not in self._globaluserinfo: param.update( @@ -1564,9 +1564,10 @@ def page_can_be_edited( :raises ValueError: invalid action parameter """ if action not in self.siteinfo.get('restrictions')['types']: - raise ValueError('{}.page_can_be_edited(): Invalid value "{}" for ' - '"action" parameter' - .format(self.__class__.__name__, action)) + raise ValueError( + f'{type(self).__name__}.page_can_be_edited(): ' + f'Invalid value "{action}" for "action" parameter' + ) prot_rights = { '': action, 'autoconfirmed': 'editsemiprotected', @@ -2494,8 +2495,9 @@ def movepage( # TODO: Check for talkmove-error messages if 'talkmove-error-code' in result['move']: pywikibot.warning( - 'movepage: Talk page {} not moved' - .format(page.toggleTalkPage().title(as_link=True))) + 'movepage: Talk page ' + f'{page.toggleTalkPage().title(as_link=True)} not moved' + ) return pywikibot.Page(page, newtitle) # catalog of rollback errors for use in error messages @@ -2619,8 +2621,9 @@ def delete( """ if oldimage and isinstance(page, pywikibot.page.BasePage) \ and not isinstance(page, pywikibot.FilePage): - raise TypeError("'page' must be a FilePage not a '{}'" - .format(page.__class__.__name__)) + raise TypeError( + f"'page' must be a FilePage not a '{page.__class__.__name__}'" + ) token = self.tokens['csrf'] params = { diff --git a/pywikibot/site/_basesite.py b/pywikibot/site/_basesite.py index bf65bd21dd..fb3029687e 100644 --- a/pywikibot/site/_basesite.py +++ b/pywikibot/site/_basesite.py @@ -72,8 +72,8 @@ def __init__(self, code: str, fam=None, user=None) -> None: else: # no such language anymore self.obsolete = True - pywikibot.log('Site {} instantiated and marked "obsolete" ' - 'to prevent access'.format(self)) + pywikibot.log(f'Site {self} instantiated and marked "obsolete"' + ' to prevent access') elif self.__code not in self.languages(): if self.__family.name in self.__family.langs \ and len(self.__family.langs) == 1: @@ -82,11 +82,11 @@ def __init__(self, code: str, fam=None, user=None) -> None: and code == pywikibot.config.mylang: pywikibot.config.mylang = self.__code warn('Global configuration variable "mylang" changed to ' - '"{}" while instantiating site {}' - .format(self.__code, self), UserWarning) + f'"{self.__code}" while instantiating site {self}', + UserWarning) else: - error_msg = ("Language '{}' does not exist in family {}" - .format(self.__code, self.__family.name)) + error_msg = (f"Language '{self.__code}' does not exist in " + f'family {self.__family.name}') raise UnknownSiteError(error_msg) self._username = normalize_username(user) @@ -150,15 +150,14 @@ def doc_subpage(self) -> tuple: # should it just raise an Exception and fail? # this will help to check the dictionary ... except KeyError: - warn('Site {} has no language defined in ' - 'doc_subpages dict in {}_family.py file' - .format(self, self.family.name), - FamilyMaintenanceWarning, 2) + warn(f'Site {self} has no language defined in ' + f'doc_subpages dict in {self.family.name}_family.py ' + 'file', FamilyMaintenanceWarning, 2) # doc_subpages not defined in x_family.py file except AttributeError: doc = () # default - warn('Site {} has no doc_subpages dict in {}_family.py file' - .format(self, self.family.name), + warn(f'Site {self} has no doc_subpages dict in ' + f'{self.family.name}_family.py file', FamilyMaintenanceWarning, 2) return doc @@ -332,10 +331,9 @@ def disambcategory(self): try: item = self.family.disambcatname[repo.code] except KeyError: - raise Error( - 'No {repo} qualifier found for disambiguation category ' - 'name in {fam}_family file'.format(repo=repo_name, - fam=self.family.name)) + raise Error(f'No {repo_name} qualifier found for' + ' disambiguation category name in ' + f'{self.family.name}_family file') dp = pywikibot.ItemPage(repo, item) try: diff --git a/pywikibot/site/_datasite.py b/pywikibot/site/_datasite.py index 802f5fe309..c8221532e1 100644 --- a/pywikibot/site/_datasite.py +++ b/pywikibot/site/_datasite.py @@ -95,9 +95,9 @@ def get_namespace_for_entity_type(self, entity_type): if entity_type in self._entity_namespaces: return self._entity_namespaces[entity_type] raise EntityTypeUnknownError( - '{!r} does not support entity type "{}" ' - "or it doesn't have its own namespace" - .format(self, entity_type)) + f'{self!r} does not support entity type "{entity_type}" ' + " or it doesn't have its own namespace" + ) @property def item_namespace(self): @@ -893,7 +893,7 @@ def parsevalue(self, datatype: str, values: list[str], if 'value' not in result_hash: # There should be an APIError occurred already raise RuntimeError("Unexpected missing 'value' in query data:" - '\n{}'.format(result_hash)) + f'\n{result_hash}') results.append(result_hash['value']) return results @@ -1007,8 +1007,8 @@ def prepare_data(action, data): if arg in ['summary', 'tags']: params[arg] = kwargs[arg] else: - warn('Unknown parameter {} for action {}, ignored' - .format(arg, action), UserWarning, 2) + warn(f'Unknown parameter {arg} for action {action}, ignored', + UserWarning, 2) req = self.simple_request(**params) return req.submit() diff --git a/pywikibot/site/_extensions.py b/pywikibot/site/_extensions.py index 77a8344032..4b6243aaa7 100644 --- a/pywikibot/site/_extensions.py +++ b/pywikibot/site/_extensions.py @@ -246,9 +246,8 @@ def globalusage(self, page, total=None): try: gu_site = pywikibot.Site(url=entry['url']) except SiteDefinitionError: - pywikibot.warning( - 'Site could not be defined for global' - ' usage for {}: {}.'.format(page, entry)) + pywikibot.warning('Site could not be defined for global ' + f'usage for {page}: {entry}.') continue gu_page = pywikibot.Page(gu_site, entry['title']) yield gu_page diff --git a/pywikibot/site/_generators.py b/pywikibot/site/_generators.py index 5c08ebf963..ba5d464a98 100644 --- a/pywikibot/site/_generators.py +++ b/pywikibot/site/_generators.py @@ -1984,8 +1984,9 @@ def patrol( if err.code in self._patrol_errors: raise Error(self._patrol_errors[err.code] .format_map(errdata)) - pywikibot.debug("protect: Unexpected error code '{}' received." - .format(err.code)) + pywikibot.debug( + f"protect: Unexpected error code '{err.code}' received." + ) raise yield result['patrol'] diff --git a/pywikibot/site/_interwikimap.py b/pywikibot/site/_interwikimap.py index aa4da13eb5..f62accf113 100644 --- a/pywikibot/site/_interwikimap.py +++ b/pywikibot/site/_interwikimap.py @@ -1,6 +1,6 @@ """Objects representing interwiki map of MediaWiki site.""" # -# (C) Pywikibot team, 2015-2022 +# (C) Pywikibot team, 2015-2024 # # Distributed under the terms of the MIT license. # @@ -75,8 +75,8 @@ def __getitem__(self, prefix): return self._iw_sites[prefix] if isinstance(self._iw_sites[prefix].site, Exception): raise self._iw_sites[prefix].site - raise TypeError('_iw_sites[{}] is wrong type: {}' - .format(prefix, type(self._iw_sites[prefix].site))) + raise TypeError(f'_iw_sites[{prefix}] is wrong type: ' + f'{type(self._iw_sites[prefix].site)}') def get_by_url(self, url: str) -> set[str]: """Return a set of prefixes applying to the URL. diff --git a/pywikibot/site/_namespace.py b/pywikibot/site/_namespace.py index b1d0568053..dd282cb916 100644 --- a/pywikibot/site/_namespace.py +++ b/pywikibot/site/_namespace.py @@ -1,6 +1,6 @@ """Objects representing Namespaces of MediaWiki site.""" # -# (C) Pywikibot team, 2008-2023 +# (C) Pywikibot team, 2008-2024 # # Distributed under the terms of the MIT license. # @@ -273,14 +273,10 @@ def __repr__(self) -> str: else: kwargs = '' - return '{}(id={}, custom_name={!r}, canonical_name={!r}, ' \ - 'aliases={!r}{})' \ - .format(self.__class__.__name__, - self.id, - self.custom_name, - self.canonical_name, - self.aliases, - kwargs) + return (f'{self.__class__.__name__}(id={self.id}, ' + f'custom_name={self.custom_name!r}, ' + f'canonical_name={self.canonical_name!r}, ' + f'aliases={self.aliases!r}{kwargs})') @staticmethod def default_case(id, default_case=None): @@ -355,8 +351,8 @@ def __getitem__(self, key: Namespace | int | str) -> Namespace: try: return self._namespaces[key] except KeyError: - raise KeyError('{} is not a known namespace. Maybe you should ' - 'clear the api cache.'.format(key)) + raise KeyError(f'{key} is not a known namespace. Maybe you' + ' should clear the api cache.') namespace = self.lookup_name(key) if namespace: @@ -438,8 +434,9 @@ def resolve(self, identifiers) -> list[Namespace]: for ns in identifiers] if NotImplemented in result: - raise TypeError('identifiers contains inappropriate types: {!r}' - .format(identifiers)) + raise TypeError( + f'identifiers contains inappropriate types: {identifiers!r}' + ) # Namespace.lookup_name returns None if the name is not recognised if None in result: diff --git a/pywikibot/site/_upload.py b/pywikibot/site/_upload.py index 7adc5fc7e8..d5320eb4b4 100644 --- a/pywikibot/site/_upload.py +++ b/pywikibot/site/_upload.py @@ -199,9 +199,9 @@ def ignore_warnings(warnings): if (offset is not False and offset is not True and offset > file_size): raise ValueError( - 'For the file key "{}" the offset was set to {} ' - 'while the file is only {} bytes large.' - .format(file_key, offset, file_size)) + f'For the file key "{file_key}" the offset was set to ' + f'{offset} while the file is only {file_size} bytes large.' + ) if verify_stash or offset is True: if not file_key: @@ -338,10 +338,10 @@ def ignore_warnings(warnings): # every time ApiError. if offset != new_offset: pywikibot.log( - 'Old offset: {}; Returned ' - 'offset: {}; Chunk size: {}' - .format(offset, new_offset, - len(chunk))) + f'Old offset: {offset}; Returned ' + f'offset: {new_offset}; Chunk size: ' + f'{len(chunk)}' + ) pywikibot.warning('Attempting to correct ' 'automatically from ' 'offset mismatch error.') @@ -390,11 +390,11 @@ def ignore_warnings(warnings): if 'offset' in data: new_offset = int(data['offset']) if offset + len(chunk) != new_offset: - pywikibot.log('Old offset: {}; Returned ' - 'offset: {}; Chunk size: {}' - .format(offset, - new_offset, - len(chunk))) + pywikibot.log( + f'Old offset: {offset}; Returned ' + f'offset: {new_offset}; Chunk size: ' + f'{len(chunk)}' + ) pywikibot.warning('Unexpected offset.') offset = new_offset else: @@ -427,9 +427,8 @@ def ignore_warnings(warnings): else: # upload by URL if not self.site.has_right('upload_by_url'): - raise Error( - "User '{}' is not authorized to upload by URL on site {}." - .format(self.site.user(), self)) + raise Error(f"User '{self.site.user()}' is not authorized to " + f'upload by URL on site {self}.') final_request = self.site.simple_request( action='upload', filename=file_page_title, url=self.url, comment=self.comment, text=self.text, token=token) diff --git a/pywikibot/site_detect.py b/pywikibot/site_detect.py index cbaa93faa3..940139ee0d 100644 --- a/pywikibot/site_detect.py +++ b/pywikibot/site_detect.py @@ -239,10 +239,10 @@ def set_api_url(self, url) -> None: if not new_parsed_url.scheme or not new_parsed_url.netloc: new_parsed_url = urlparse( - '{}://{}{}'.format( - new_parsed_url.scheme or self.url.scheme, - new_parsed_url.netloc or self.url.netloc, - new_parsed_url.path)) + f'{new_parsed_url.scheme or self.url.scheme}://' + f'{new_parsed_url.netloc or self.url.netloc}' + f'{new_parsed_url.path}' + ) else: if self._parsed_url: # allow upgrades to https, but not downgrades @@ -255,12 +255,11 @@ def set_api_url(self, url) -> None: or self._parsed_url.netloc in new_parsed_url.netloc): return - assert new_parsed_url == self._parsed_url, '{} != {}'.format( - self._parsed_url, new_parsed_url) + assert new_parsed_url == self._parsed_url, \ + f'{self._parsed_url} != {new_parsed_url}' self._parsed_url = new_parsed_url - self.server = '{url.scheme}://{url.netloc}'.format( - url=self._parsed_url) + self.server = f'{self._parsed_url.scheme}://{self._parsed_url.netloc}' self.scriptpath = self._parsed_url.path def handle_starttag(self, tag, attrs) -> None: diff --git a/pywikibot/specialbots/_upload.py b/pywikibot/specialbots/_upload.py index c3396edb8e..3654d394cb 100644 --- a/pywikibot/specialbots/_upload.py +++ b/pywikibot/specialbots/_upload.py @@ -216,7 +216,7 @@ def _handle_warning(self, warning: str) -> bool | None: return None if self.aborts is not True else False def _handle_warnings(self, warnings): - messages = '\n'.join('{0.code}: {0.info}'.format(warning) + messages = '\n'.join(f'{warning.code}: {warning.info}' for warning in sorted(warnings, key=lambda w: w.code)) if len(warnings) > 1: diff --git a/pywikibot/textlib.py b/pywikibot/textlib.py index b25ce1c1fd..dc4d46e418 100644 --- a/pywikibot/textlib.py +++ b/pywikibot/textlib.py @@ -235,10 +235,10 @@ def ignore_case(string: str) -> str: def _tag_pattern(tag_name: str) -> str: """Return a tag pattern for the given tag name.""" return ( - r'<{0}(?:>|\s+[^>]*(?<!/)>)' # start tag + rf'<{ignore_case(tag_name)}(?:>|\s+[^>]*(?<!/)>)' # start tag r'[\s\S]*?' # contents - r'</{0}\s*>' # end tag - .format(ignore_case(tag_name))) + rf'</{ignore_case(tag_name)}\s*>' # end tag + ) def _tag_regex(tag_name: str): @@ -732,9 +732,9 @@ def replace_callable(link, text, groups, rng): def check_classes(replacement): """Normalize the replacement into a list.""" if not isinstance(replacement, (pywikibot.Page, pywikibot.Link)): - raise ValueError('The replacement must be None, False, ' - 'a sequence, a Link or a str but ' - 'is "{}"'.format(type(replacement))) + raise ValueError('The replacement must be None, False, a' + ' sequence, a Link or a str but is ' + f'"{type(replacement)}"') def title_section(link) -> str: title = link.title @@ -743,8 +743,8 @@ def title_section(link) -> str: return title if not isinstance(site, pywikibot.site.BaseSite): - raise ValueError('The "site" argument must be a BaseSite not {}.' - .format(type(site).__name__)) + raise ValueError('The "site" argument must be a BaseSite not ' + f'{type(site).__name__}.') if isinstance(replace, Sequence): if len(replace) != 2: @@ -753,8 +753,8 @@ def title_section(link) -> str: replace_list = [to_link(replace[0]), replace[1]] if not isinstance(replace_list[0], pywikibot.Link): raise ValueError( - 'The original value must be either str, Link or Page ' - 'but is "{}"'.format(type(replace_list[0]))) + 'The original value must be either str, Link or Page but is ' + f'"{type(replace_list[0])}"') if replace_list[1] is not False and replace_list[1] is not None: if isinstance(replace_list[1], str): replace_list[1] = pywikibot.Page(site, replace_list[1]) @@ -764,7 +764,8 @@ def title_section(link) -> str: linktrail = site.linktrail() link_pattern = re.compile( r'\[\[(?P<title>.*?)(#(?P<section>.*?))?(\|(?P<label>.*?))?\]\]' - r'(?P<linktrail>{})'.format(linktrail)) + rf'(?P<linktrail>{linktrail})' + ) extended_label_pattern = re.compile(fr'(.*?\]\])({linktrail})') linktrail = re.compile(linktrail) curpos = 0 @@ -1234,8 +1235,8 @@ def removeLanguageLinks(text: str, site=None, marker: str = '') -> str: + list(site.family.obsolete.keys())) if not languages: return text - interwikiR = re.compile(r'\[\[({})\s?:[^\[\]\n]*\]\][\s]*' - .format(languages), re.IGNORECASE) + interwikiR = re.compile(rf'\[\[({languages})\s?:[^\[\]\n]*\]\][\s]*', + re.IGNORECASE) text = replaceExcept(text, interwikiR, '', ['comment', 'math', 'nowiki', 'pre', 'syntaxhighlight'], @@ -1467,8 +1468,9 @@ def getCategoryLinks(text: str, site=None, # and HTML comments text = removeDisabledParts(text, include=include or []) catNamespace = '|'.join(site.namespaces.CATEGORY) - R = re.compile(r'\[\[\s*(?P<namespace>{})\s*:\s*(?P<rest>.+?)\]\]' - .format(catNamespace), re.I) + R = re.compile( + rf'\[\[\s*(?P<namespace>{catNamespace})\s*:\s*(?P<rest>.+?)\]\]', re.I + ) for match in R.finditer(text): match_rest = match['rest'] if expand_text and '{{' in match_rest: @@ -1510,8 +1512,7 @@ def removeCategoryLinks(text: str, site=None, marker: str = '') -> str: if site is None: site = pywikibot.Site() catNamespace = '|'.join(site.namespaces.CATEGORY) - categoryR = re.compile(r'\[\[\s*({})\s*:.*?\]\]\s*' - .format(catNamespace), re.I) + categoryR = re.compile(rf'\[\[\s*({catNamespace})\s*:.*?\]\]\s*', re.I) text = replaceExcept(text, categoryR, '', ['comment', 'includeonly', 'math', 'nowiki', 'pre', 'syntaxhighlight'], @@ -1568,13 +1569,12 @@ def replaceCategoryInPlace(oldtext, oldcat, newcat, site=None, # title might contain regex special characters title = case_escape(site.namespaces[14].case, title, underscore=True) - categoryR = re.compile(r'\[\[\s*({})\s*:\s*{}[\s\u200e\u200f]*' - r'((?:\|[^]]+)?\]\])' - .format(catNamespace, title), re.I) + categoryR = re.compile( + rf'\[\[\s*({catNamespace})\s*:\s*{title}[\s\u200e\u200f]*' + r'((?:\|[^]]+)?\]\])', re.I) categoryRN = re.compile( - r'^[^\S\n]*\[\[\s*({})\s*:\s*{}[\s\u200e\u200f]*' - r'((?:\|[^]]+)?\]\])[^\S\n]*\n' - .format(catNamespace, title), re.I | re.M) + rf'^[^\S\n]*\[\[\s*({catNamespace})\s*:\s*{title}[\s\u200e\u200f]*' + r'((?:\|[^]]+)?\]\])[^\S\n]*\n', re.I | re.M) exceptions = ['comment', 'math', 'nowiki', 'pre', 'syntaxhighlight'] if newcat is None: # First go through and try the more restrictive regex that removes @@ -1587,16 +1587,16 @@ def replaceCategoryInPlace(oldtext, oldcat, newcat, site=None, elif add_only: text = replaceExcept( oldtext, categoryR, - '{}\n{}'.format( - oldcat.title(as_link=True, allow_interwiki=False), - newcat.title(as_link=True, allow_interwiki=False)), - exceptions, site=site) + f'{oldcat.title(as_link=True, allow_interwiki=False)}\n' + f'{newcat.title(as_link=True, allow_interwiki=False)}', + exceptions, site=site + ) else: - text = replaceExcept(oldtext, categoryR, - '[[{}:{}\\2' - .format(site.namespace(14), - newcat.title(with_ns=False)), - exceptions, site=site) + text = replaceExcept( + oldtext, categoryR, + f'[[{site.namespace(14)}:{newcat.title(with_ns=False)}\\2', + exceptions, site=site + ) return text @@ -1756,10 +1756,9 @@ def compileLinkR(withoutBracketed: bool = False, onlyBracketed: bool = False): # not allowed inside links. For example, in this wiki text: # ''Please see https://www.example.org.'' # .'' shouldn't be considered as part of the link. - regex = r'(?P<url>http[s]?://[^{notInside}]*?[^{notAtEnd}]' \ - r'(?=[{notAtEnd}]*\'\')|http[s]?://[^{notInside}]*' \ - r'[^{notAtEnd}])'.format(notInside=notInside, - notAtEnd=notAtEnd) + regex = rf'(?P<url>http[s]?://[^{notInside}]*?[^{notAtEnd}]' \ + rf'(?=[{notAtEnd}]*\'\')|http[s]?://[^{notInside}]*' \ + rf'[^{notAtEnd}])' if withoutBracketed: regex = r'(?<!\[)' + regex diff --git a/pywikibot/time.py b/pywikibot/time.py index 4f4e002dbd..e8910c871f 100644 --- a/pywikibot/time.py +++ b/pywikibot/time.py @@ -527,11 +527,9 @@ def dst(self, dt: datetime.datetime | None) -> datetime.timedelta: def __repr__(self) -> str: """Return the internal representation of the timezone.""" - return '{}({}, {})'.format( - self.__class__.__name__, - self._offset.days * 86400 + self._offset.seconds, - self._name - ) + return (f'{type(self).__name__}' + f'({self._offset.days * 86400 + self._offset.seconds}, ' + f'{self._name})') def str2timedelta( diff --git a/pywikibot/titletranslate.py b/pywikibot/titletranslate.py index 4004fb84e8..e349c74390 100644 --- a/pywikibot/titletranslate.py +++ b/pywikibot/titletranslate.py @@ -68,9 +68,8 @@ def translate( sitelang = page.site.lang dict_name, value = date.getAutoFormat(sitelang, page.title()) if dict_name: - pywikibot.info( - 'TitleTranslate: {} was recognized as {} with value {}' - .format(page.title(), dict_name, value)) + pywikibot.info(f'TitleTranslate: {page.title()} was recognized as ' + f'{dict_name} with value {value}') for entry_lang, entry in date.formats[dict_name].items(): if entry_lang not in site.languages(): continue diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py index 641e14a4fc..3a782b75c2 100644 --- a/pywikibot/tools/__init__.py +++ b/pywikibot/tools/__init__.py @@ -150,8 +150,8 @@ def has_module(module: str, version: str | None = None) -> bool: module_version = packaging.version.Version(metadata_version) if module_version < required_version: - warn('Module version {} is lower than requested version {}' - .format(module_version, required_version), ImportWarning) + warn(f'Module version {module_version} is lower than requested ' + f'version {required_version}', ImportWarning) return False return True @@ -491,8 +491,8 @@ def from_generator(generator: str) -> MediaWikiVersion: prefix = 'MediaWiki ' if not generator.startswith(prefix): - raise ValueError('Generator string ({!r}) must start with ' - '"{}"'.format(generator, prefix)) + raise ValueError(f'Generator string ({generator!r}) must start ' + f'with "{prefix}"') return MediaWikiVersion(generator[len(prefix):]) @@ -513,8 +513,8 @@ def __lt__(self, other: Any) -> bool: if isinstance(other, str): other = MediaWikiVersion(other) elif not isinstance(other, MediaWikiVersion): - raise TypeError("Comparison between 'MediaWikiVersion' and '{}' " - 'unsupported'.format(type(other).__name__)) + raise TypeError(f"Comparison between 'MediaWikiVersion' and " + f"'{type(other).__name__}' unsupported") if self.version != other.version: return self.version < other.version diff --git a/pywikibot/tools/collections.py b/pywikibot/tools/collections.py index 3a1570f870..5ef1cb9c12 100644 --- a/pywikibot/tools/collections.py +++ b/pywikibot/tools/collections.py @@ -1,6 +1,6 @@ """Collections datatypes.""" # -# (C) Pywikibot team, 2014-2023 +# (C) Pywikibot team, 2014-2024 # # Distributed under the terms of the MIT license. # @@ -270,8 +270,8 @@ def send(self, value: Any) -> Any: :raises TypeError: generator property is not a generator """ if not isinstance(self.generator, GeneratorType): - raise TypeError('generator property is not a generator but {}' - .format(type(self.generator).__name__)) + raise TypeError('generator property is not a generator but ' + f'{type(self.generator).__name__}') if not hasattr(self, '_started_gen'): # start the generator self._started_gen = self.generator diff --git a/pywikibot/tools/djvu.py b/pywikibot/tools/djvu.py index 24b76b44ae..aa5fc7aef1 100644 --- a/pywikibot/tools/djvu.py +++ b/pywikibot/tools/djvu.py @@ -98,9 +98,8 @@ def wrapper(obj, *args, **kwargs): n = args[0] force = kwargs.get('force', False) if not 1 <= n <= obj.number_of_images(force=force): - raise ValueError('Page {} not in file {} [{}-{}]' - .format(int(n), obj.file, int(n), - int(obj.number_of_images()))) + raise ValueError(f'Page {int(n)} not in file {obj.file} ' + f'[{int(n)}-{int(obj.number_of_images())}]') return fn(obj, *args, **kwargs) return wrapper diff --git a/pywikibot/tools/itertools.py b/pywikibot/tools/itertools.py index df3fffddb0..e5911ba265 100644 --- a/pywikibot/tools/itertools.py +++ b/pywikibot/tools/itertools.py @@ -4,7 +4,7 @@ in :mod:`backports` """ # -# (C) Pywikibot team, 2008-2023 +# (C) Pywikibot team, 2008-2024 # # Distributed under the terms of the MIT license. # @@ -140,8 +140,8 @@ def intersect_generators(*iterables, allow_duplicates: bool = False): # If any iterable is empty, no pages are going to be returned for source in iterables: if not source: - debug('At least one iterable ({!r}) is empty and execution was ' - 'skipped immediately.'.format(source)) + debug(f'At least one iterable ({source!r}) is empty and execution' + ' was skipped immediately.') return # Item is cached to check that it is found n_gen times diff --git a/pywikibot/tools/threading.py b/pywikibot/tools/threading.py index b4820ff30b..75395614e7 100644 --- a/pywikibot/tools/threading.py +++ b/pywikibot/tools/threading.py @@ -66,7 +66,8 @@ def __repr__(self) -> str: """Representation of tools.RLock instance.""" return repr(self._lock).replace( '_thread.RLock', - '{cls.__module__}.{cls.__class__.__name__}'.format(cls=self)) + f'{self.__module__}.{type(self).__name__}' + ) @property def count(self): diff --git a/pywikibot/userinterfaces/buffer_interface.py b/pywikibot/userinterfaces/buffer_interface.py index 97d7237682..2f508c7656 100644 --- a/pywikibot/userinterfaces/buffer_interface.py +++ b/pywikibot/userinterfaces/buffer_interface.py @@ -3,7 +3,7 @@ .. versionadded:: 6.4 """ # -# (C) Pywikibot team, 2021-2022 +# (C) Pywikibot team, 2021-2024 # # Distributed under the terms of the MIT license. # @@ -75,9 +75,8 @@ def pop_output(self): elif isinstance(record, logging.LogRecord): output.append(record.getMessage()) else: - raise ValueError( - 'BUG: buffer can only contain logs and strings, had {}' - .format(type(record).__name__)) + raise ValueError('Buffer can only contain logs and strings, ' + f'had {type(record).__name__}') return output diff --git a/pywikibot/userinterfaces/gui.py b/pywikibot/userinterfaces/gui.py index 6a18d84f4d..7cd42132e8 100644 --- a/pywikibot/userinterfaces/gui.py +++ b/pywikibot/userinterfaces/gui.py @@ -13,7 +13,7 @@ .. seealso:: :mod:`editor` """ # -# (C) Pywikibot team, 2003-2023 +# (C) Pywikibot team, 2003-2024 # # Distributed under the terms of the MIT license. # @@ -465,9 +465,8 @@ def __init__(self, photo_description, photo, filename) -> None: self.root = tkinter.Tk() # "%dx%d%+d%+d" % (width, height, xoffset, yoffset) - self.root.geometry('{}x{}+10-10' - .format(int(pywikibot.config.tkhorsize), - int(pywikibot.config.tkvertsize))) + self.root.geometry(f'{int(pywikibot.config.tkhorsize)}x' + f'{int(pywikibot.config.tkvertsize)}+10-10') self.root.title(filename) self.photo_description = photo_description diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py index 800148b3bd..4f68dfe6d6 100644 --- a/pywikibot/userinterfaces/terminal_interface_base.py +++ b/pywikibot/userinterfaces/terminal_interface_base.py @@ -139,8 +139,8 @@ def init_handlers( def encounter_color(self, color, target_stream): """Abstract method to handle the next color encountered.""" - raise NotImplementedError('The {} class does not support ' - 'colors.'.format(self.__class__.__name__)) + raise NotImplementedError(f'The {type(self).__name__} class does not' + ' support colors.') @classmethod def divide_color(cls, color): @@ -180,9 +180,8 @@ def _write(self, text: str, target_stream) -> None: out, err = self.stdout.name, self.stderr.name except AttributeError: out, err = self.stdout, self.stderr - raise OSError( - 'Target stream {} is neither stdin ({}) nor stderr ({})' - .format(target_stream.name, out, err)) + raise OSError(f'Target stream {target_stream.name} is neither ' + f'stdin ({out}) nor stderr ({err})') def support_color(self, target_stream) -> bool: """Return whether the target stream does support colors.""" @@ -479,8 +478,8 @@ def output_option(option, before_question) -> None: for i, option in enumerate(options): if not isinstance(option, Option): if len(option) != 2: - raise ValueError('Option #{} does not consist of an ' - 'option and shortcut.'.format(i)) + raise ValueError(f'Option #{i} does not consist of an ' + 'option and shortcut.') options[i] = StandardOption(*option) # TODO: Test for uniquity @@ -625,7 +624,7 @@ def emit(self, record) -> None: self.UI.output(msg, targetStream=self.stream) -class MaxLevelFilter(): +class MaxLevelFilter: """Filter that only passes records at or below a specific level. diff --git a/pywikibot/version.py b/pywikibot/version.py index 67cd656953..4daf5a5108 100644 --- a/pywikibot/version.py +++ b/pywikibot/version.py @@ -409,9 +409,11 @@ def package_versions( path = _file info['path'] = path - assert path not in paths, \ - 'Path {} of the package {} is in defined paths as {}' \ - .format(path, name, paths[path]) + assert path not in paths, ( + f'Path {path} of the package {name} is in defined paths as ' + f'{paths[path]}' + ) + paths[path] = name if '__version__' in package.__dict__: diff --git a/tests/link_tests.py b/tests/link_tests.py index 8134cd65f8..256347efc3 100755 --- a/tests/link_tests.py +++ b/tests/link_tests.py @@ -163,7 +163,7 @@ def generate_has_no_title_exc_regex(text): # A link is invalid if their (non-)talk page would be in another # namespace than the link's "other" namespace (['Talk:File:Example.svg'], - r'The \(non-\)talk page of (u|)\'Talk:File:Example.svg\'' + r"The \(non-\)talk page of 'Talk:File:Example.svg'" r' is a valid title in another namespace.'), (['.', '..', './Sandbox', '../Sandbox', 'Foo/./Sandbox',