Skip to content

Commit 2663285

Browse files
authored
pathlib ABCs: tighten up resolve() and absolute() (python#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.
1 parent 0f47a31 commit 2663285

File tree

3 files changed

+53
-32
lines changed

3 files changed

+53
-32
lines changed

Lib/pathlib/_abc.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,13 @@ def absolute(self):
735735
736736
Use resolve() to resolve symlinks and remove '..' segments.
737737
"""
738-
raise UnsupportedOperation(self._unsupported_msg('absolute()'))
738+
if self.is_absolute():
739+
return self
740+
elif self.parser is not posixpath:
741+
raise UnsupportedOperation(self._unsupported_msg('absolute()'))
742+
else:
743+
# Treat the root directory as the current working directory.
744+
return self.with_segments('/', *self._raw_paths)
739745

740746
@classmethod
741747
def cwd(cls):
@@ -772,10 +778,13 @@ def resolve(self, strict=False):
772778
"""
773779
if self._resolving:
774780
return self
781+
elif self.parser is not posixpath:
782+
raise UnsupportedOperation(self._unsupported_msg('resolve()'))
775783

776-
def getcwd():
777-
return str(self.with_segments().absolute())
784+
def raise_error(*args):
785+
raise OSError("Unsupported operation.")
778786

787+
getcwd = raise_error
779788
if strict or getattr(self.readlink, '_supported', True):
780789
def lstat(path_str):
781790
path = self.with_segments(path_str)
@@ -790,14 +799,10 @@ def readlink(path_str):
790799
# If the user has *not* overridden the `readlink()` method, then
791800
# symlinks are unsupported and (in non-strict mode) we can improve
792801
# performance by not calling `path.lstat()`.
793-
def skip(path_str):
794-
# This exception will be internally consumed by `_realpath()`.
795-
raise OSError("Operation skipped.")
796-
797-
lstat = readlink = skip
802+
lstat = readlink = raise_error
798803

799804
return self.with_segments(posixpath._realpath(
800-
str(self), strict, self.parser.sep,
805+
str(self.absolute()), strict, self.parser.sep,
801806
getcwd=getcwd, lstat=lstat, readlink=readlink,
802807
maxlinks=self._max_symlinks))
803808

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,28 @@ def test_move_into_other_os(self):
861861
def test_move_into_empty_name_other_os(self):
862862
self.test_move_into_empty_name()
863863

864+
def _check_complex_symlinks(self, link0_target):
865+
super()._check_complex_symlinks(link0_target)
866+
P = self.cls(self.base)
867+
# Resolve relative paths.
868+
old_path = os.getcwd()
869+
os.chdir(self.base)
870+
try:
871+
p = self.cls('link0').resolve()
872+
self.assertEqual(p, P)
873+
self.assertEqualNormCase(str(p), self.base)
874+
p = self.cls('link1').resolve()
875+
self.assertEqual(p, P)
876+
self.assertEqualNormCase(str(p), self.base)
877+
p = self.cls('link2').resolve()
878+
self.assertEqual(p, P)
879+
self.assertEqualNormCase(str(p), self.base)
880+
p = self.cls('link3').resolve()
881+
self.assertEqual(p, P)
882+
self.assertEqualNormCase(str(p), self.base)
883+
finally:
884+
os.chdir(old_path)
885+
864886
def test_resolve_nonexist_relative_issue38671(self):
865887
p = self.cls('non', 'exist')
866888

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,6 +2493,23 @@ def test_glob_long_symlink(self):
24932493
bad_link.symlink_to("bad" * 200)
24942494
self.assertEqual(sorted(base.glob('**/*')), [bad_link])
24952495

2496+
@needs_posix
2497+
def test_absolute_posix(self):
2498+
P = self.cls
2499+
# The default implementation uses '/' as the current directory
2500+
self.assertEqual(str(P('').absolute()), '/')
2501+
self.assertEqual(str(P('a').absolute()), '/a')
2502+
self.assertEqual(str(P('a/b').absolute()), '/a/b')
2503+
2504+
self.assertEqual(str(P('/').absolute()), '/')
2505+
self.assertEqual(str(P('/a').absolute()), '/a')
2506+
self.assertEqual(str(P('/a/b').absolute()), '/a/b')
2507+
2508+
# '//'-prefixed absolute path (supported by POSIX).
2509+
self.assertEqual(str(P('//').absolute()), '//')
2510+
self.assertEqual(str(P('//a').absolute()), '//a')
2511+
self.assertEqual(str(P('//a/b').absolute()), '//a/b')
2512+
24962513
@needs_symlinks
24972514
def test_readlink(self):
24982515
P = self.cls(self.base)
@@ -2810,29 +2827,6 @@ def _check_complex_symlinks(self, link0_target):
28102827
self.assertEqual(p, P)
28112828
self.assertEqualNormCase(str(p), self.base)
28122829

2813-
# Resolve relative paths.
2814-
try:
2815-
self.cls('').absolute()
2816-
except UnsupportedOperation:
2817-
return
2818-
old_path = os.getcwd()
2819-
os.chdir(self.base)
2820-
try:
2821-
p = self.cls('link0').resolve()
2822-
self.assertEqual(p, P)
2823-
self.assertEqualNormCase(str(p), self.base)
2824-
p = self.cls('link1').resolve()
2825-
self.assertEqual(p, P)
2826-
self.assertEqualNormCase(str(p), self.base)
2827-
p = self.cls('link2').resolve()
2828-
self.assertEqual(p, P)
2829-
self.assertEqualNormCase(str(p), self.base)
2830-
p = self.cls('link3').resolve()
2831-
self.assertEqual(p, P)
2832-
self.assertEqualNormCase(str(p), self.base)
2833-
finally:
2834-
os.chdir(old_path)
2835-
28362830
@needs_symlinks
28372831
def test_complex_symlinks_absolute(self):
28382832
self._check_complex_symlinks(self.base)

0 commit comments

Comments
 (0)