Xqt has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1342387?usp=email )
Change subject: Use pathlib for configuration and supporting file operations
......................................................................
Use pathlib for configuration and supporting file operations
Compose internal paths with Path and use its filesystem methods in
configuration loading, family discovery, translation lookup, Git version
reading and user-file generation. Annotate configuration path inputs
with os.PathLike[str] while retaining public string results.
Keep lexical normalization and separator-preserving joins where needed
for makepath directory creation and existing symlink behavior.
Change-Id: I4ae84a1a35ba29abf5b56573670de3b4de23a5aa
---
M pywikibot/config.py
M pywikibot/i18n.py
M pywikibot/scripts/generate_user_files.py
M pywikibot/version.py
4 files changed, 52 insertions(+), 51 deletions(-)
Approvals:
Xqt: Verified; Looks good to me, approved
diff --git a/pywikibot/config.py b/pywikibot/config.py
index 647339b..eafa564 100644
--- a/pywikibot/config.py
+++ b/pywikibot/config.py
@@ -276,9 +276,9 @@
# #############################################
-def user_home_path(path: str) -> str:
+def user_home_path(path: str | os.PathLike[str]) -> str:
"""Return a file path to a file in the user home."""
- return os.path.join(os.path.expanduser('~'), path)
+ return str(Path(os.path.expanduser('~')) / path)
def get_user_config_file() -> str:
@@ -295,7 +295,7 @@
return 'user-config.py'
-def get_base_dir(test_directory: str | None = None,
+def get_base_dir(test_directory: str | os.PathLike[str] | None = None,
config_file: str = 'user-config.py') -> str:
r"""Return the directory in which user-specific information is stored.
@@ -324,11 +324,11 @@
directory will cause it to be selected as the base directory.
:param config_file: Filename of the user config file
"""
- def exists(directory: str) -> bool:
+ def exists(directory: str | os.PathLike[str]) -> bool:
directory = os.path.abspath(directory)
if directory == test_directory:
return True
- return os.path.exists(os.path.join(directory, config_file))
+ return (Path(directory) / config_file).exists()
if test_directory is not None:
test_directory = os.path.abspath(test_directory)
@@ -366,19 +366,19 @@
base_dir_cand.append([home, '.pywikibot'])
for dir_ in base_dir_cand:
- dir_s = os.path.join(*dir_)
+ dir_s = Path(*dir_)
try:
- os.makedirs(dir_s, mode=private_folder_permission)
+ dir_s.mkdir(mode=private_folder_permission, parents=True)
except OSError: # PermissionError or already exists
if exists(dir_s):
- base_dir = dir_s
+ base_dir = str(dir_s)
break
if not os.path.isabs(base_dir):
base_dir = os.path.normpath(os.path.join(os.getcwd(), base_dir))
# make sure this path is valid and that it contains user-config file
- if not os.path.isdir(base_dir):
+ if not Path(base_dir).is_dir():
raise RuntimeError(f"Directory '{base_dir}' does not exist.")
# check if config_file is in base_dir
@@ -414,7 +414,7 @@
family_files: dict[str, str] = {}
-def register_families_folder(folder_path: str,
+def register_families_folder(folder_path: str | os.PathLike[str],
not_exists_ok: bool = False) -> None:
"""Register all family class files contained in a directory.
@@ -429,23 +429,23 @@
"""
suffix = '_family.py'
- if not os.path.exists(folder_path):
+ folder = Path(folder_path)
+ if not folder.exists():
if not_exists_ok:
return
raise FileNotFoundError(
f'Family folder {folder_path!r} does not exist')
- if os.path.isdir(folder_path):
- for file_name in os.listdir(folder_path):
- if file_name.endswith(suffix):
- family_name = file_name.removesuffix(suffix)
- family_files[family_name] = os.path.join(folder_path,
- file_name)
+ if folder.is_dir():
+ for file_path in folder.iterdir():
+ if file_path.name.endswith(suffix):
+ family_name = file_path.name.removesuffix(suffix)
+ family_files[family_name] = str(file_path)
return
# probably there is a zip file chain (T278076)
# find the parent zip folder
- path = Path(folder_path)
+ path = folder
if not is_zipfile(path):
for parent in path.parents:
if is_zipfile(path):
@@ -461,15 +461,14 @@
if file_name.endswith(suffix):
file_path = Path(file_name)
family_name = file_path.name.removesuffix(suffix)
- family_files[family_name] = os.path.join(folder_path,
- file_path.name)
+ family_files[family_name] = str(folder / file_path.name)
# Get the names of all known families, and initialize with empty dictionaries.
# 'families/' is a subdirectory of the directory in which config.py is found.
-register_families_folder(os.path.join(os.path.dirname(__file__), 'families'))
+register_families_folder(Path(__file__).parent / 'families')
# 'families/' can also be stored in the base directory
-register_families_folder(os.path.join(base_dir, 'families'),
+register_families_folder(Path(base_dir) / 'families',
not_exists_ok=True)
@@ -876,7 +875,7 @@
# #############################################
-def makepath(path: str, create: bool = True) -> str:
+def makepath(path: str | os.PathLike[str], create: bool = True) -> str:
"""Return a normalized absolute version of the path argument.
If the given path already exists in the filesystem or create is
@@ -891,13 +890,14 @@
:param create: Create the directory if it is True. Otherwise do not
change the filesystem. Default is True.
"""
- dpath = os.path.normpath(os.path.dirname(path))
- if create and not os.path.exists(dpath):
- os.makedirs(dpath)
+ dpath = Path(os.path.normpath(os.path.dirname(path)))
+ if create and not dpath.exists():
+ dpath.mkdir(parents=True)
return os.path.normpath(os.path.abspath(path))
-def datafilepath(*filename: str, create: bool = True) -> str:
+def datafilepath(*filename: str | os.PathLike[str],
+ create: bool = True) -> str:
"""Return an absolute path to a data file in a standard location.
Argument(s) are zero or more directory names, optionally followed by
@@ -909,6 +909,7 @@
:param create: Create the directory if it is True. Otherwise don't
change the filesystem. Default is True.
"""
+ # Preserve trailing separators for makepath directory creation.
return makepath(os.path.join(base_dir, *filename), create=create)
@@ -938,19 +939,19 @@
_exec_globals = copy.deepcopy(_public_globals)
# Always try to get the user files
-_filename = os.path.join(base_dir, user_config_file)
-if os.path.exists(_filename):
- _filestatus = os.stat(_filename)
+_filename = Path(base_dir) / user_config_file
+if _filename.exists():
+ _filestatus = _filename.stat()
_filemode = _filestatus[0]
_fileuid = _filestatus[4]
if not OSWIN32 \
and _fileuid not in [os.getuid(), 0]: # type: ignore[attr-defined]
- warning(f'Skipped {_filename!r}: owned by someone else.')
+ warning(f'Skipped {str(_filename)!r}: owned by someone else.')
elif OSWIN32 or _filemode & 0o02 == 0:
- with open(_filename, 'rb') as f:
- exec(compile(f.read(), _filename, 'exec'), _exec_globals)
+ exec(compile(_filename.read_bytes(), str(_filename), 'exec'),
+ _exec_globals)
else:
- warning(f'Skipped {_filename!r}: writeable by others.')
+ warning(f'Skipped {str(_filename)!r}: writeable by others.')
elif __no_user_config and __no_user_config != '2':
warning(f'{user_config_file} cannot be loaded.')
diff --git a/pywikibot/i18n.py b/pywikibot/i18n.py
index eeda694..7e776d5 100644
--- a/pywikibot/i18n.py
+++ b/pywikibot/i18n.py
@@ -843,12 +843,12 @@
# obtain the directory containing all the json files for this package
package = twtitle.split('-')[0]
mod = __import__(_messages_package_name, fromlist=['__file__'])
- pathname = os.path.join(next(iter(mod.__path__)), package)
+ pathname = Path(next(iter(mod.__path__))) / package
# build a list of languages in that directory
- langs = [filename.removesuffix('.json')
- for filename in sorted(os.listdir(pathname))
- if filename.endswith('.json')]
+ langs = [path.stem
+ for path in sorted(pathname.iterdir(), key=lambda path: path.name)
+ if path.name.endswith('.json')]
# Exclude languages whose translations do not include this message.
return [lang for lang in langs
diff --git a/pywikibot/scripts/generate_user_files.py
b/pywikibot/scripts/generate_user_files.py
index 8a15a58..e39ac28 100755
--- a/pywikibot/scripts/generate_user_files.py
+++ b/pywikibot/scripts/generate_user_files.py
@@ -57,9 +57,9 @@
"""Create a new user directory."""
while True:
new_base = pywikibot.input('New user directory? ')
- new_base = os.path.abspath(new_base)
- if os.path.exists(new_base):
- if os.path.isfile(new_base):
+ new_base = Path(os.path.abspath(new_base))
+ if new_base.exists():
+ if new_base.is_file():
pywikibot.error('there is an existing file with that name.')
continue
# make sure user can read and write this directory
@@ -69,16 +69,16 @@
pywikibot.info('Using existing directory')
else:
try:
- os.mkdir(new_base, pywikibot.config.private_files_permission)
+ new_base.mkdir(mode=pywikibot.config.private_files_permission)
except Exception as e:
pywikibot.error(f'directory creation failed: {e}')
continue
pywikibot.info('Created new directory.')
break
- if new_base == pywikibot.config.get_base_dir(new_base):
+ if str(new_base) == pywikibot.config.get_base_dir(str(new_base)):
# config would find that file
- return new_base
+ return str(new_base)
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
@@ -88,16 +88,17 @@
set environment variables.""", width=76)
pywikibot.info(msg)
if pywikibot.input_yn('Is this OK?', default=False, automatic_quit=False):
- return new_base
+ return str(new_base)
pywikibot.info('Aborting changes.')
return False
def file_exists(filename) -> bool:
"""Return whether the file exists and print a message if it exists."""
- if os.path.exists(filename):
+ path = Path(filename)
+ if path.exists():
pywikibot.info('{1} already exists in the target directory "{0}".'
- .format(*os.path.split(filename)))
+ .format(path.parent, path.name))
return True
return False
@@ -477,8 +478,8 @@
pywikibot.info(f'\nYour default user directory is "{base_dir}"')
while True:
# Show whether file exists
- userfile = file_exists(os.path.join(base_dir, USER_BASENAME))
- passfile = file_exists(os.path.join(base_dir, PASS_BASENAME))
+ userfile = file_exists(Path(base_dir) / USER_BASENAME)
+ passfile = file_exists(Path(base_dir) / PASS_BASENAME)
if force and not config.verbose_output or not (userfile or passfile):
break
if pywikibot.input_yn(
diff --git a/pywikibot/version.py b/pywikibot/version.py
index 2762366..e46f5b9 100644
--- a/pywikibot/version.py
+++ b/pywikibot/version.py
@@ -142,8 +142,7 @@
# some Windows git versions provide git.cmd instead of git.exe
cmd = 'git.cmd'
- with open(os.path.join(_program_dir, '.git/config')) as f:
- tag = f.read()
+ tag = (Path(_program_dir) / '.git' / 'config').read_text()
# Try 'origin' and then 'gerrit' as remote name; bail if can't find either.
remote_pos = tag.find('[remote "origin"]')
if remote_pos == -1:
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1342387?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: I4ae84a1a35ba29abf5b56573670de3b4de23a5aa
Gerrit-Change-Number: 1342387
Gerrit-PatchSet: 2
Gerrit-Owner: Mahveotm <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]