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]

Reply via email to