]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
pathlib ABCs: tighten up `resolve()` and `absolute()` (#126611)
authorBarney Gale <barney.gale@gmail.com>
Sat, 9 Nov 2024 18:47:49 +0000 (18:47 +0000)
committerGitHub <noreply@github.com>
Sat, 9 Nov 2024 18:47:49 +0000 (18:47 +0000)
In `PathBase.resolve()`, raise `UnsupportedOperation` if a non-POSIX path
parser is used (our implementation uses `posixpath._realpath()`, which
produces incorrect results for non-POSIX path flavours.) Also tweak code to
call `self.absolute()` upfront rather than supplying an emulated `getcwd()`
function.

Adjust `PathBase.absolute()` to work somewhat like `resolve()`. If a POSIX
path parser is used, we treat the root directory as the current directory.
This is the simplest useful behaviour for concrete path types without a
current directory cursor.

Lib/pathlib/_abc.py
Lib/test/test_pathlib/test_pathlib.py
Lib/test/test_pathlib/test_pathlib_abc.py

index e9e46e511bddf13d683fb4c876ed35bdccbe3932..2c243d470d4eda914cd5228a43788dc21ecc290d 100644 (file)
@@ -735,7 +735,13 @@ class PathBase(PurePathBase):
 
         Use resolve() to resolve symlinks and remove '..' segments.
         """
-        raise UnsupportedOperation(self._unsupported_msg('absolute()'))
+        if self.is_absolute():
+            return self
+        elif self.parser is not posixpath:
+            raise UnsupportedOperation(self._unsupported_msg('absolute()'))
+        else:
+            # Treat the root directory as the current working directory.
+            return self.with_segments('/', *self._raw_paths)
 
     @classmethod
     def cwd(cls):
@@ -772,10 +778,13 @@ class PathBase(PurePathBase):
         """
         if self._resolving:
             return self
+        elif self.parser is not posixpath:
+            raise UnsupportedOperation(self._unsupported_msg('resolve()'))
 
-        def getcwd():
-            return str(self.with_segments().absolute())
+        def raise_error(*args):
+            raise OSError("Unsupported operation.")
 
+        getcwd = raise_error
         if strict or getattr(self.readlink, '_supported', True):
             def lstat(path_str):
                 path = self.with_segments(path_str)
@@ -790,14 +799,10 @@ class PathBase(PurePathBase):
             # If the user has *not* overridden the `readlink()` method, then
             # symlinks are unsupported and (in non-strict mode) we can improve
             # performance by not calling `path.lstat()`.
-            def skip(path_str):
-                # This exception will be internally consumed by `_realpath()`.
-                raise OSError("Operation skipped.")
-
-            lstat = readlink = skip
+            lstat = readlink = raise_error
 
         return self.with_segments(posixpath._realpath(
-            str(self), strict, self.parser.sep,
+            str(self.absolute()), strict, self.parser.sep,
             getcwd=getcwd, lstat=lstat, readlink=readlink,
             maxlinks=self._max_symlinks))
 
index c7104bfda90f6cb15479ee0421a66f2469c77996..46966b6df2d7b0981e51d8cee1a3544eed91ebe8 100644 (file)
@@ -861,6 +861,28 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
     def test_move_into_empty_name_other_os(self):
         self.test_move_into_empty_name()
 
+    def _check_complex_symlinks(self, link0_target):
+        super()._check_complex_symlinks(link0_target)
+        P = self.cls(self.base)
+        # Resolve relative paths.
+        old_path = os.getcwd()
+        os.chdir(self.base)
+        try:
+            p = self.cls('link0').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), self.base)
+            p = self.cls('link1').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), self.base)
+            p = self.cls('link2').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), self.base)
+            p = self.cls('link3').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), self.base)
+        finally:
+            os.chdir(old_path)
+
     def test_resolve_nonexist_relative_issue38671(self):
         p = self.cls('non', 'exist')
 
index bb2e4187ef95746eb7eb694eee55356611e46486..b69d674e1cf1ed479ecfdb9fa210418f48a5a55c 100644 (file)
@@ -2493,6 +2493,23 @@ class DummyPathTest(DummyPurePathTest):
         bad_link.symlink_to("bad" * 200)
         self.assertEqual(sorted(base.glob('**/*')), [bad_link])
 
+    @needs_posix
+    def test_absolute_posix(self):
+        P = self.cls
+        # The default implementation uses '/' as the current directory
+        self.assertEqual(str(P('').absolute()), '/')
+        self.assertEqual(str(P('a').absolute()), '/a')
+        self.assertEqual(str(P('a/b').absolute()), '/a/b')
+
+        self.assertEqual(str(P('/').absolute()), '/')
+        self.assertEqual(str(P('/a').absolute()), '/a')
+        self.assertEqual(str(P('/a/b').absolute()), '/a/b')
+
+        # '//'-prefixed absolute path (supported by POSIX).
+        self.assertEqual(str(P('//').absolute()), '//')
+        self.assertEqual(str(P('//a').absolute()), '//a')
+        self.assertEqual(str(P('//a/b').absolute()), '//a/b')
+
     @needs_symlinks
     def test_readlink(self):
         P = self.cls(self.base)
@@ -2810,29 +2827,6 @@ class DummyPathTest(DummyPurePathTest):
         self.assertEqual(p, P)
         self.assertEqualNormCase(str(p), self.base)
 
-        # Resolve relative paths.
-        try:
-            self.cls('').absolute()
-        except UnsupportedOperation:
-            return
-        old_path = os.getcwd()
-        os.chdir(self.base)
-        try:
-            p = self.cls('link0').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), self.base)
-            p = self.cls('link1').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), self.base)
-            p = self.cls('link2').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), self.base)
-            p = self.cls('link3').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), self.base)
-        finally:
-            os.chdir(old_path)
-
     @needs_symlinks
     def test_complex_symlinks_absolute(self):
         self._check_complex_symlinks(self.base)