https://github.com/python/cpython/commit/266328552e922fd9030cd699e10a25f03a67c8ba
commit: 266328552e922fd9030cd699e10a25f03a67c8ba
branch: main
author: Barney Gale <[email protected]>
committer: barneygale <[email protected]>
date: 2024-11-09T18:47:49Z
summary:

pathlib ABCs: tighten up `resolve()` and `absolute()` (#126611)

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.

files:
M Lib/pathlib/_abc.py
M Lib/test/test_pathlib/test_pathlib.py
M Lib/test/test_pathlib/test_pathlib_abc.py

diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index e9e46e511bddf1..2c243d470d4eda 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -735,7 +735,13 @@ def absolute(self):
 
         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 @@ def resolve(self, strict=False):
         """
         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 @@ def readlink(path_str):
             # 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))
 
diff --git a/Lib/test/test_pathlib/test_pathlib.py 
b/Lib/test/test_pathlib/test_pathlib.py
index c7104bfda90f6c..46966b6df2d7b0 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -861,6 +861,28 @@ def test_move_into_other_os(self):
     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')
 
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py 
b/Lib/test/test_pathlib/test_pathlib_abc.py
index bb2e4187ef9574..b69d674e1cf1ed 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -2493,6 +2493,23 @@ def test_glob_long_symlink(self):
         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 @@ def _check_complex_symlinks(self, link0_target):
         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)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to