0
0
mirror of https://github.com/python/cpython.git synced 2024-11-21 21:09:37 +01:00

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.
This commit is contained in:
Barney Gale 2024-11-09 18:47:49 +00:00 committed by GitHub
parent 0f47a3199c
commit 266328552e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 32 deletions

View File

@ -735,7 +735,13 @@ class PathBase(PurePathBase):
Use resolve() to resolve symlinks and remove '..' segments. 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 @classmethod
def cwd(cls): def cwd(cls):
@ -772,10 +778,13 @@ class PathBase(PurePathBase):
""" """
if self._resolving: if self._resolving:
return self return self
elif self.parser is not posixpath:
raise UnsupportedOperation(self._unsupported_msg('resolve()'))
def getcwd(): def raise_error(*args):
return str(self.with_segments().absolute()) raise OSError("Unsupported operation.")
getcwd = raise_error
if strict or getattr(self.readlink, '_supported', True): if strict or getattr(self.readlink, '_supported', True):
def lstat(path_str): def lstat(path_str):
path = self.with_segments(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 # If the user has *not* overridden the `readlink()` method, then
# symlinks are unsupported and (in non-strict mode) we can improve # symlinks are unsupported and (in non-strict mode) we can improve
# performance by not calling `path.lstat()`. # performance by not calling `path.lstat()`.
def skip(path_str): lstat = readlink = raise_error
# This exception will be internally consumed by `_realpath()`.
raise OSError("Operation skipped.")
lstat = readlink = skip
return self.with_segments(posixpath._realpath( 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, getcwd=getcwd, lstat=lstat, readlink=readlink,
maxlinks=self._max_symlinks)) maxlinks=self._max_symlinks))

View File

@ -861,6 +861,28 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
def test_move_into_empty_name_other_os(self): def test_move_into_empty_name_other_os(self):
self.test_move_into_empty_name() 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): def test_resolve_nonexist_relative_issue38671(self):
p = self.cls('non', 'exist') p = self.cls('non', 'exist')

View File

@ -2493,6 +2493,23 @@ class DummyPathTest(DummyPurePathTest):
bad_link.symlink_to("bad" * 200) bad_link.symlink_to("bad" * 200)
self.assertEqual(sorted(base.glob('**/*')), [bad_link]) 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 @needs_symlinks
def test_readlink(self): def test_readlink(self):
P = self.cls(self.base) P = self.cls(self.base)
@ -2810,29 +2827,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertEqual(p, P) self.assertEqual(p, P)
self.assertEqualNormCase(str(p), self.base) 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 @needs_symlinks
def test_complex_symlinks_absolute(self): def test_complex_symlinks_absolute(self):
self._check_complex_symlinks(self.base) self._check_complex_symlinks(self.base)