jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1330618?usp=email )

Change subject: Use pathlib for path containment
......................................................................

Use pathlib for path containment

Use Path.is_relative_to when checking configuration and module paths.
This avoids treating similarly prefixed sibling directories as children and
uses platform-aware POSIX and Windows path semantics.

Change-Id: I987f9ced796504559f27e104f61b0f4703ab6477
---
M pywikibot/config.py
M pywikibot/version.py
M tests/__init__.py
A tests/config_tests.py
M tests/version_tests.py
5 files changed, 128 insertions(+), 6 deletions(-)

Approvals:
  jenkins-bot: Verified
  Xqt: Looks good to me, approved




diff --git a/pywikibot/config.py b/pywikibot/config.py
index 4830dd2..f60988d 100644
--- a/pywikibot/config.py
+++ b/pywikibot/config.py
@@ -913,9 +913,16 @@


 def shortpath(path: str) -> str:
-    """Return a file path relative to config.base_dir."""
-    if path.startswith(base_dir):
-        return path[len(base_dir) + len(os.path.sep):]
+    """Return a file path relative to config.base_dir.
+
+    .. version-changed:: 11.8
+       Path components are used to determine whether *path* is inside
+       :data:`base_dir`.
+    """
+    path_obj = Path(path)
+    if path_obj.is_relative_to(base_dir):
+        relative_path = path_obj.relative_to(base_dir)
+        return '' if relative_path == Path('.') else str(relative_path)
     return path


diff --git a/pywikibot/version.py b/pywikibot/version.py
index 486c706..8d76fe8 100644
--- a/pywikibot/version.py
+++ b/pywikibot/version.py
@@ -251,14 +251,18 @@
     :param module: The module instance.
     :type module: module
     :return: The filename if it's a pywikibot module otherwise None.
+
+    .. version-changed:: 11.8
+       Path components are used to determine whether the module is
+       inside the Pywikibot program directory.
     """
     if hasattr(module, '__file__'):
         filename = module.__file__
         if not filename or not os.path.exists(filename):
             return None

-        program_dir = _get_program_dir()
-        if filename.startswith(program_dir):
+        program_dir = Path(_get_program_dir())
+        if Path(filename).is_relative_to(program_dir):
             return filename
     return None

diff --git a/tests/__init__.py b/tests/__init__.py
index 4813220..b38fddb 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -96,6 +96,7 @@
     'bot',
     'category',
     'collections',
+    'config',
     'cosmetic_changes',
     'date',
     'datasite',
diff --git a/tests/config_tests.py b/tests/config_tests.py
new file mode 100755
index 0000000..870be2b
--- /dev/null
+++ b/tests/config_tests.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# (C) Pywikibot team, 2026
+#
+# Distributed under the terms of the MIT license.
+#
+"""Test cases for the :mod:`config` module."""
+from __future__ import annotations
+
+import unittest
+from contextlib import suppress
+from pathlib import Path, PurePosixPath, PureWindowsPath
+from unittest.mock import patch
+
+from pywikibot import config
+from tests.aspects import TestCase
+
+
+class ConfigPathTestCase(TestCase):
+
+    """Test configuration path helpers."""
+
+    net = False
+
+    def test_shortpath_path_containment(self) -> None:
+        """Test shortpath uses path components for containment."""
+        base_dir = Path.cwd() / 'pywikibot'
+        child = base_dir / 'data' / 'file.txt'
+        sibling = base_dir.with_name(base_dir.name + '-extra') / 'file.txt'
+
+        with patch.object(config, 'base_dir', str(base_dir)):
+            self.assertEqual(config.shortpath(str(base_dir)), '')
+            self.assertEqual(config.shortpath(str(child)),
+                             str(Path('data') / 'file.txt'))
+            self.assertEqual(config.shortpath(str(sibling)), str(sibling))
+
+    def test_shortpath_path_flavours(self) -> None:
+        """Test POSIX, Windows drive, and Windows UNC path handling."""
+        cases = (
+            (PurePosixPath,
+             '/srv/pywikibot',
+             '/srv/pywikibot/data/file.txt',
+             '/srv/pywikibot-extra/file.txt',
+             'data/file.txt'),
+            (PureWindowsPath,
+             r'C:\Users\bot\pywikibot',
+             r'C:\Users\bot\pywikibot\data\file.txt',
+             r'C:\Users\bot\pywikibot-extra\file.txt',
+             r'data\file.txt'),
+            (PureWindowsPath,
+             r'\\server\share\pywikibot',
+             r'\\server\share\pywikibot\data\file.txt',
+             r'\\server\share\pywikibot-extra\file.txt',
+             r'data\file.txt'),
+        )
+
+        for path_type, base, child, sibling, relative_child in cases:
+            with self.subTest(path_type=path_type, base=base), \
+                    patch.object(config, 'Path', path_type), \
+                    patch.object(config, 'base_dir', base):
+                self.assertEqual(config.shortpath(base), '')
+                self.assertEqual(config.shortpath(child), relative_child)
+                self.assertEqual(config.shortpath(sibling), sibling)
+
+    def test_shortpath_different_windows_drive(self) -> None:
+        """Test paths on another Windows drive are not shortened."""
+        base = r'C:\Users\bot\pywikibot'
+        path = r'D:\pywikibot\file.txt'
+        with patch.object(config, 'Path', PureWindowsPath), \
+                patch.object(config, 'base_dir', base):
+            self.assertEqual(config.shortpath(path), path)
+
+
+if __name__ == '__main__':
+    with suppress(SystemExit):
+        unittest.main()
diff --git a/tests/version_tests.py b/tests/version_tests.py
index c34aa0c..16dd2ba 100755
--- a/tests/version_tests.py
+++ b/tests/version_tests.py
@@ -8,9 +8,11 @@
 from __future__ import annotations

 import time
+import types
 import unittest
 from contextlib import suppress
-from pathlib import Path
+from pathlib import Path, PurePosixPath, PureWindowsPath
+from unittest.mock import patch

 from pywikibot import version
 from tests.aspects import TestCase
@@ -41,6 +43,38 @@
         self.assertEqual(hsh, '')
         self.assertEqual(dummy, [])

+    def test_module_filename_path_containment(self) -> None:
+        """Test module filenames are contained by path components."""
+        cases = (
+            (PurePosixPath,
+             '/srv/pywikibot',
+             '/srv/pywikibot/module.py',
+             '/srv/pywikibot-extra/module.py'),
+            (PureWindowsPath,
+             r'C:\Users\bot\pywikibot',
+             r'C:\Users\bot\pywikibot\module.py',
+             r'C:\Users\bot\pywikibot-extra\module.py'),
+            (PureWindowsPath,
+             r'\\server\share\pywikibot',
+             r'\\server\share\pywikibot\module.py',
+             r'\\server\share\pywikibot-extra\module.py'),
+        )
+
+        for path_type, program_dir, module_path, sibling_path in cases:
+            module = types.SimpleNamespace(__file__=module_path)
+            with self.subTest(path_type=path_type,
+                              program_dir=program_dir), \
+                    patch.object(version, 'Path', path_type), \
+                    patch.object(version, '_get_program_dir',
+                                 return_value=program_dir), \
+                    patch.object(version.os.path, 'exists',
+                                 return_value=True):
+                self.assertEqual(version.get_module_filename(module),
+                                 module_path)
+
+                module.__file__ = sibling_path
+                self.assertIsNone(version.get_module_filename(module))
+

 class RemoteVersionTestCase(TestCase):


--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1330618?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: I987f9ced796504559f27e104f61b0f4703ab6477
Gerrit-Change-Number: 1330618
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