jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152407?usp=email )
Change subject: tests: update type hints and use a constant for several identical messages
......................................................................
tests: update type hints and use a constant for several identical messages
Change-Id: Iad6de389babccc28964c29106d3e67a695d0a197
---
M tests/aspects.py
1 file changed, 14 insertions(+), 7 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/tests/aspects.py b/tests/aspects.py
index 27799fd..0bca615 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -57,6 +57,9 @@
OSWIN32 = (sys.platform == 'win32')
+SIZED_ERROR = 'seq argument is not a Sized class containing __len__'
+
+
pywikibot.bot.set_interface('buffer')
@@ -90,7 +93,7 @@
def assertIsEmpty(self, seq, msg=None):
"""Check that the sequence is empty."""
self.assertIsInstance(
- seq, Sized, 'seq argument is not a Sized class containing __len__')
+ seq, Sized, SIZED_ERROR)
if seq:
msg = self._formatMessage(msg, f'{safe_repr(seq)} is not empty')
self.fail(msg)
@@ -98,7 +101,7 @@
def assertIsNotEmpty(self, seq, msg=None):
"""Check that the sequence is not empty."""
self.assertIsInstance(
- seq, Sized, 'seq argument is not a Sized class containing __len__')
+ seq, Sized, SIZED_ERROR)
if not seq:
msg = self._formatMessage(msg, f'{safe_repr(seq)} is empty')
self.fail(msg)
@@ -107,7 +110,7 @@
"""Verify that a sequence seq has the length of other."""
# the other parameter may be given as a sequence too
self.assertIsInstance(
- seq, Sized, 'seq argument is not a Sized class containing __len__')
+ seq, Sized, SIZED_ERROR)
first_len = len(seq)
try:
second_len = len(other)
@@ -132,9 +135,11 @@
self.assertIn(page.namespace(), namespaces,
f'{page} not in namespace {namespaces!r}')
- def _get_gen_pages(self,
- gen: Iterable[pywikibot.Page],
- site: pywikibot.site.APISite = None) -> None:
+ def _get_gen_pages(
+ self,
+ gen: Iterable[pywikibot.Page],
+ site: pywikibot.site.APISite | None = None
+ ) -> list[pywikibot.Page]:
"""Get pages from gen, asserting they are Page from site.
.. versionchanged:: 9.3
@@ -154,7 +159,9 @@
return gen_pages
- def _get_gen_titles(self, gen, site=None) -> list[str]:
+ def _get_gen_titles(self,
+ gen: list[pywikibot.Page],
+ site=None) -> list[str]:
"""Return a list of page titles of given iterable."""
return [page.title() for page in self._get_gen_pages(gen, site)]
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152407?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Iad6de389babccc28964c29106d3e67a695d0a197
Gerrit-Change-Number: 1152407
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1149831?usp=email )
Change subject: IMPR: replace codecs.open with pathlib.Path in LoginManager.readPassword
......................................................................
IMPR: replace codecs.open with pathlib.Path in LoginManager.readPassword
- use pathlib.Path in LoginManager.readPassword
- update login_tests
- update type hint for file_mode_checker
Bug: T395187
Change-Id: I25af80fd37b55e84f33fc78ad86ceacfacd6476b
---
M pywikibot/login.py
M pywikibot/tools/__init__.py
M tests/login_tests.py
3 files changed, 55 insertions(+), 25 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/pywikibot/login.py b/pywikibot/login.py
index b73776f..f24b4fc 100644
--- a/pywikibot/login.py
+++ b/pywikibot/login.py
@@ -1,17 +1,17 @@
"""Library to log the bot in to a wiki account."""
#
-# (C) Pywikibot team, 2003-2024
+# (C) Pywikibot team, 2003-2025
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations
-import codecs
import datetime
import os
import re
import webbrowser
from enum import IntEnum
+from pathlib import Path
from typing import Any
from warnings import warn
@@ -19,7 +19,12 @@
from pywikibot import __url__, config
from pywikibot.comms import http
from pywikibot.exceptions import APIError, NoUsernameError
-from pywikibot.tools import deprecated, file_mode_checker, normalize_username
+from pywikibot.tools import (
+ PYTHON_VERSION,
+ deprecated,
+ file_mode_checker,
+ normalize_username,
+)
try:
@@ -208,23 +213,41 @@
('wikipedia', 'my_wikipedia_user', 'my_wikipedia_pass')
('en', 'wikipedia', 'my_en_wikipedia_user', 'my_en_wikipedia_pass')
('my_username', BotPassword('my_suffix', 'my_password'))
+
+ .. versionchanged:: 10.2
+ raises ValueError instead of AttributeError if password_file
+ is not set
+
+ :raises ValueError: `password_file` is not set in the user-config.py
+ :raises FileNotFoundError: password file does not exist
"""
+ if config.password_file is None:
+ raise ValueError('password_file is not set in the user-config.py')
+
# Set path to password file relative to the user_config
# but fall back on absolute path for backwards compatibility
- assert config.base_dir is not None and config.password_file is not None
- password_file = os.path.join(config.base_dir, config.password_file)
- if not os.path.isfile(password_file):
- password_file = config.password_file
+ password_path = Path(config.base_dir, config.password_file)
+
+ params = {} if PYTHON_VERSION < (3, 13) else {'follow_symlinks': False}
+ # test for symlink required for Python < 3.13
+ if not password_path.is_file(**params) or password_path.is_symlink:
+ password_path = Path(config.password_file)
+
+ # ignore this check when running tests
+ if os.environ.get('PYWIKIBOT_TEST_RUNNING', '0') == '0':
+ if not password_path.is_file(**params) or password_path.is_symlink:
+ raise FileNotFoundError(
+ f'Password file {password_path.name} does not exist in '
+ f'{password_path.parent}'
+ )
# We fix password file permission first.
- file_mode_checker(password_file, mode=config.private_files_permission)
+ file_mode_checker(password_path, mode=config.private_files_permission)
- with codecs.open(password_file, encoding='utf-8') as f:
- lines = f.readlines()
+ lines = password_path.read_text('utf-8').splitlines()
+ line_len = len(lines)
- line_nr = len(lines) + 1
- for line in reversed(lines):
- line_nr -= 1
+ for n, line in enumerate(reversed(lines)):
if not line.strip() or line.startswith('#'):
continue
@@ -234,17 +257,19 @@
entry = None
if not isinstance(entry, tuple):
- warn(f'Invalid tuple in line {line_nr}',
+ warn(f'Invalid tuple in line {line_len - n}',
_PasswordFileWarning)
continue
- if not 2 <= len(entry) <= 4:
- warn(f'The length of tuple in line {line_nr} should be 2 to 4 '
- f'({entry} given)', _PasswordFileWarning)
+ if not 2 <= (entry_len := len(entry)) <= 4:
+ warn(f'The length of tuple in line {line_len - n} should be 2 '
+ f'to 4, {entry_len} given ({entry})',
+ _PasswordFileWarning)
continue
code, family, username, password = (
- self.site.code, self.site.family.name)[:4 - len(entry)] + entry
+ self.site.code, self.site.family.name)[:4 - entry_len] + entry
+
if (normalize_username(username) == self.username
and family == self.site.family.name
and code == self.site.code):
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index 6eb0fe8..6e9bc35 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -659,7 +659,7 @@
def file_mode_checker(
- filename: str,
+ filename: str | bytes | os.PathLike,
mode: int = 0o600,
quiet: bool = False,
create: bool = False
diff --git a/tests/login_tests.py b/tests/login_tests.py
index 0e95d18..48d7fd1 100755
--- a/tests/login_tests.py
+++ b/tests/login_tests.py
@@ -4,7 +4,7 @@
e.g. used to test password-file based login.
"""
#
-# (C) Pywikibot team, 2012-2022
+# (C) Pywikibot team, 2012-2025
#
# Distributed under the terms of the MIT license.
#
@@ -12,10 +12,12 @@
from collections import defaultdict
from io import StringIO
+from pathlib import Path
from unittest import mock
from pywikibot.exceptions import NoUsernameError
from pywikibot.login import LoginManager
+from pywikibot.tools import PYTHON_VERSION
from tests.aspects import DefaultDrySiteTestCase, unittest
@@ -105,26 +107,29 @@
self.stat = self.patch('os.stat')
self.stat.return_value.st_mode = 0o100600
-
self.chmod = self.patch('os.chmod')
- self.open = self.patch('codecs.open')
+ if PYTHON_VERSION[:2] == (3, 10):
+ self.open = self.patch('pathlib.Path._accessor.open')
+ else:
+ self.open = self.patch('io.open')
+
self.open.return_value = StringIO()
def test_auto_chmod_OK(self):
"""Do not chmod files that have mode private_files_permission."""
self.stat.return_value.st_mode = 0o100600
LoginManager()
- self.stat.assert_called_with(self.config.password_file)
+ self.stat.assert_called_with(Path(self.config.password_file))
self.assertFalse(self.chmod.called)
def test_auto_chmod_not_OK(self):
"""Chmod files that do not have mode private_files_permission."""
self.stat.return_value.st_mode = 0o100644
LoginManager()
- self.stat.assert_called_with(self.config.password_file)
+ self.stat.assert_called_with(Path(self.config.password_file))
self.chmod.assert_called_once_with(
- self.config.password_file,
+ Path(self.config.password_file),
0o600
)
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1149831?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I25af80fd37b55e84f33fc78ad86ceacfacd6476b
Gerrit-Change-Number: 1149831
Gerrit-PatchSet: 13
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152405?usp=email )
Change subject: typing: add type annotations to test methods
......................................................................
typing: add type annotations to test methods
Change-Id: Idb3aca4992893d558b014827c85360ca1fc9fcf6
---
M tests/add_text_tests.py
M tests/api_tests.py
M tests/archivebot_tests.py
M tests/basesite_tests.py
M tests/bot_tests.py
M tests/cache_tests.py
M tests/category_bot_tests.py
M tests/category_tests.py
M tests/checkimages_tests.py
M tests/collections_tests.py
M tests/cosmetic_changes_tests.py
M tests/data_ingestion_tests.py
M tests/datasite_tests.py
M tests/date_tests.py
M tests/deletionbot_tests.py
M tests/diff_tests.py
M tests/djvu_tests.py
M tests/dry_api_tests.py
M tests/dry_site_tests.py
M tests/echo_tests.py
M tests/edit_failure_tests.py
M tests/edit_tests.py
M tests/eventstreams_tests.py
M tests/family_tests.py
M tests/file_tests.py
M tests/fixes_tests.py
M tests/fixing_redirects_tests.py
M tests/generate_family_file_tests.py
M tests/generate_user_files_tests.py
M tests/gui_tests.py
M tests/harvest_template_tests.py
M tests/http_tests.py
M tests/i18n_tests.py
M tests/interwiki_graph_tests.py
M tests/interwiki_link_tests.py
M tests/interwikidata_tests.py
M tests/interwikimap_tests.py
M tests/l10n_tests.py
M tests/link_tests.py
M tests/linter_tests.py
M tests/logentries_tests.py
M tests/login_tests.py
M tests/make_dist_tests.py
M tests/mediawikiversion_tests.py
M tests/memento_tests.py
M tests/mysql_tests.py
M tests/namespace_tests.py
M tests/noreferences_tests.py
M tests/oauth_tests.py
M tests/page_tests.py
M tests/pagegenerators_tests.py
M tests/paraminfo_tests.py
M tests/patrolbot_tests.py
M tests/plural_tests.py
M tests/proofreadpage_tests.py
M tests/protectbot_tests.py
M tests/pwb_tests.py
M tests/redirect_bot_tests.py
M tests/reflinks_tests.py
M tests/replacebot_tests.py
M tests/script_tests.py
M tests/setup_tests.py
M tests/site_decorators_tests.py
M tests/site_detect_tests.py
M tests/site_generators_tests.py
M tests/site_login_logout_tests.py
M tests/site_obsoletesites_tests.py
M tests/site_tests.py
M tests/siteinfo_tests.py
M tests/sparql_tests.py
M tests/superset_tests.py
M tests/template_bot_tests.py
M tests/tests_tests.py
M tests/textlib_tests.py
M tests/thanks_tests.py
M tests/time_tests.py
M tests/timestripper_tests.py
M tests/token_tests.py
M tests/tools_chars_tests.py
M tests/tools_deprecate_tests.py
M tests/tools_formatter_tests.py
M tests/tools_tests.py
M tests/tools_threading_tests.py
M tests/ui_options_tests.py
M tests/ui_tests.py
M tests/upload_tests.py
M tests/uploadbot_tests.py
M tests/uploadscript_tests.py
M tests/user_tests.py
M tests/version_tests.py
M tests/wbtypes_tests.py
M tests/wikibase_edit_tests.py
M tests/wikibase_tests.py
M tests/wikiblame_tests.py
M tests/wikistats_tests.py
M tests/xmlreader_tests.py
96 files changed, 1,503 insertions(+), 1,503 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152405?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Idb3aca4992893d558b014827c85360ca1fc9fcf6
Gerrit-Change-Number: 1152405
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152404?usp=email )
Change subject: IMPR: decrease deeply nested flow in _category.py and _wikibase.py
......................................................................
IMPR: decrease deeply nested flow in _category.py and _wikibase.py
Change-Id: Ifdd280016011bd8ce47c4d2fcfbb21b73af7b67b
---
M pywikibot/page/_category.py
M pywikibot/page/_wikibase.py
2 files changed, 40 insertions(+), 33 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page/_category.py b/pywikibot/page/_category.py
index 95b0993..f352394 100644
--- a/pywikibot/page/_category.py
+++ b/pywikibot/page/_category.py
@@ -1,6 +1,6 @@
"""Object representing a MediaWiki category page."""
#
-# (C) Pywikibot team, 2008-2023
+# (C) Pywikibot team, 2008-2025
#
# Distributed under the terms of the MIT license.
#
@@ -249,18 +249,20 @@
if total == 0:
return
- if recurse:
- if not isinstance(recurse, bool):
- recurse -= 1
+ if not recurse:
+ return
- for subcat in self.subcategories():
- for member in subcat.members(
- recurse=recurse, total=total, **kwargs):
- yield member
- if total is not None:
- total -= 1
- if total == 0:
- return
+ if not isinstance(recurse, bool):
+ recurse -= 1
+
+ for subcat in self.subcategories():
+ for member in subcat.members(
+ recurse=recurse, total=total, **kwargs):
+ yield member
+ if total is not None:
+ total -= 1
+ if total == 0:
+ return
def isEmptyCategory(self) -> bool: # noqa: N802
"""Return True if category has no members (including subcategories)."""
diff --git a/pywikibot/page/_wikibase.py b/pywikibot/page/_wikibase.py
index 560bbdb..4860083 100644
--- a/pywikibot/page/_wikibase.py
+++ b/pywikibot/page/_wikibase.py
@@ -327,27 +327,32 @@
if hasattr(self, '_content'):
del self._content
self.latest_revision_id = updates['entity'].get('lastrevid')
- if update_self and 'claims' in updates['entity']:
- updated_claims = updates['entity']['claims']
- for claim_prop_id, statements in updated_claims.items():
- for claim_index, statement in enumerate(statements):
- claim = self.claims[claim_prop_id][claim_index]
- claim.snak = statement['id']
- claim.on_item = self
- updated_qualifiers = statement.get('qualifiers', {})
- for qual_propid, qualifier in updated_qualifiers.items():
- for qual_index, qual_statement in enumerate(qualifier):
- target_qual_prop = claim.qualifiers[qual_propid]
- target_qual = target_qual_prop[qual_index]
- target_qual.hash = qual_statement['hash']
- updated_references = statement.get('references', [])
- for ref_grp_idx, ref_grp in enumerate(updated_references):
- for ref_propid, reference in ref_grp['snaks'].items():
- for ref_index, ref_stat in enumerate(reference):
- target_ref_grp = claim.sources[ref_grp_idx]
- target_ref_prop = target_ref_grp[ref_propid]
- target_ref = target_ref_prop[ref_index]
- target_ref.hash = ref_stat['hash']
+
+ if not update_self or 'claims' not in updates['entity']:
+ return
+
+ updated_claims = updates['entity']['claims']
+ for claim_prop_id, statements in updated_claims.items():
+ for claim_index, statement in enumerate(statements):
+ claim = self.claims[claim_prop_id][claim_index]
+ claim.snak = statement['id']
+ claim.on_item = self
+
+ updated_qualifiers = statement.get('qualifiers', {})
+ for qual_propid, qualifier in updated_qualifiers.items():
+ for qual_index, qual_statement in enumerate(qualifier):
+ target_qual_prop = claim.qualifiers[qual_propid]
+ target_qual = target_qual_prop[qual_index]
+ target_qual.hash = qual_statement['hash']
+
+ updated_references = statement.get('references', [])
+ for ref_grp_idx, ref_grp in enumerate(updated_references):
+ for ref_propid, reference in ref_grp['snaks'].items():
+ for ref_index, ref_stat in enumerate(reference):
+ target_ref_grp = claim.sources[ref_grp_idx]
+ target_ref_prop = target_ref_grp[ref_propid]
+ target_ref = target_ref_prop[ref_index]
+ target_ref.hash = ref_stat['hash']
def concept_uri(self) -> str:
"""Return the full concept URI.
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152404?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ifdd280016011bd8ce47c4d2fcfbb21b73af7b67b
Gerrit-Change-Number: 1152404
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152074?usp=email )
Change subject: IMPR: Only show the description passed to the if it is to be verified
......................................................................
IMPR: Only show the description passed to the if it is to be verified
Bug: T394895
Change-Id: I4c05695640e28992a35ee93f99b4084e19d3bf7f
---
M pywikibot/specialbots/_upload.py
1 file changed, 7 insertions(+), 4 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/specialbots/_upload.py b/pywikibot/specialbots/_upload.py
index 69abe2a..acbf01c 100644
--- a/pywikibot/specialbots/_upload.py
+++ b/pywikibot/specialbots/_upload.py
@@ -3,7 +3,7 @@
Do not import classes directly from here but from specialbots.
"""
#
-# (C) Pywikibot team, 2003-2024
+# (C) Pywikibot team, 2003-2025
#
# Distributed under the terms of the MIT license.
#
@@ -238,6 +238,10 @@
def process_filename(self, file_url: str) -> str | None:
"""Return base filename portion of *file_url*.
+ .. versionchanged:: 10.2
+ no longer shows the description if UploadRobot's parameter
+ *verify_description* is set to False.
+
:param file_url: either a URL or a local file path
"""
# Isolate the pure name
@@ -331,9 +335,8 @@
continue
break
- # A proper description for the submission.
- # Empty descriptions are not accepted.
- if self.description:
+ # Show the description to verify it for the submission
+ if self.description and self.verify_description:
pywikibot.info(
f'The suggested description is:\n{self.description}')
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1152074?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I4c05695640e28992a35ee93f99b4084e19d3bf7f
Gerrit-Change-Number: 1152074
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot