cpython: 03bbee2b0d28 (original) (raw)
--- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -919,7 +919,7 @@ call fails (for example because the path to an existing file or directory, it will be unconditionally replaced. -.. method:: Path.resolve() +.. method:: Path.resolve(strict=False) Make the path absolute, resolving any symlinks. A new path object is returned:: @@ -936,10 +936,14 @@ call fails (for example because the path >>> p.resolve() PosixPath('/home/antoine/pathlib/setup.py')
- If the path doesn't exist, :exc:
FileNotFoundError
is raised. If an - infinite loop is encountered along the resolution path,
- :exc:
RuntimeError
is raised.
- If the path doesn't exist and strict is
True
, :exc:FileNotFoundError
- is raised. If strict is
False
, the path is resolved as far as possible - and any remainder is appended without checking whether it exists. If an
- infinite loop is encountered along the resolution path, :exc:
RuntimeError
- is raised.
- .. versionadded:: 3.6
The *strict* argument.[](#l1.26)
.. method:: Path.rglob(pattern)
--- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -178,12 +178,26 @@ class _WindowsFlavour(_Flavour): def casefold_parts(self, parts): return [p.lower() for p in parts]
- def resolve(self, path, strict=False): s = str(path) if not s: return os.getcwd()
previous_s = None[](#l2.12) if _getfinalpathname is not None:[](#l2.13)
return self._ext_to_normal(_getfinalpathname(s))[](#l2.14)
if strict:[](#l2.15)
return self._ext_to_normal(_getfinalpathname(s))[](#l2.16)
else:[](#l2.17)
while True:[](#l2.18)
try:[](#l2.19)
s = self._ext_to_normal(_getfinalpathname(s))[](#l2.20)
except FileNotFoundError:[](#l2.21)
previous_s = s[](#l2.22)
s = os.path.abspath(os.path.join(s, os.pardir))[](#l2.23)
else:[](#l2.24)
if previous_s is None:[](#l2.25)
return s[](#l2.26)
else:[](#l2.27)
return s + os.path.sep + os.path.basename(previous_s)[](#l2.28) # Means fallback on absolute[](#l2.29) return None[](#l2.30)
@@ -285,7 +299,7 @@ class _PosixFlavour(_Flavour): def casefold_parts(self, parts): return parts
@@ -315,7 +329,10 @@ class _PosixFlavour(_Flavour): target = accessor.readlink(newpath) except OSError as e: if e.errno != EINVAL:
raise[](#l2.45)
if strict:[](#l2.46)
raise[](#l2.47)
else:[](#l2.48)
return newpath[](#l2.49) # Not a symlink[](#l2.50) path = newpath[](#l2.51) else:[](#l2.52)
@@ -1092,7 +1109,7 @@ class Path(PurePath): obj._init(template=self) return obj
- def resolve(self, strict=False): """ Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under
@@ -1100,7 +1117,7 @@ class Path(PurePath): """ if self._closed: self._raise_closed()
s = self._flavour.resolve(self)[](#l2.66)
s = self._flavour.resolve(self, strict=strict)[](#l2.67) if s is None:[](#l2.68) # No symlink resolution => for consistency, raise an error if[](#l2.69) # the path doesn't exist or is forbidden[](#l2.70)
--- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1486,8 +1486,8 @@ class _BasePathTest(object): self.assertEqual(set(p.glob("../xyzzy")), set())
- def _check_resolve(self, p, expected, strict=True):
q = p.resolve(strict)[](#l3.10) self.assertEqual(q, expected)[](#l3.11)
# this can be used to check both relative and absolute resolutions @@ -1498,8 +1498,17 @@ class _BasePathTest(object): P = self.cls p = P(BASE, 'foo') with self.assertRaises(OSError) as cm:
p.resolve()[](#l3.18)
p.resolve(strict=True)[](#l3.19) self.assertEqual(cm.exception.errno, errno.ENOENT)[](#l3.20)
# Non-strict[](#l3.21)
self.assertEqual(str(p.resolve(strict=False)),[](#l3.22)
os.path.join(BASE, 'foo'))[](#l3.23)
p = P(BASE, 'foo', 'in', 'spam')[](#l3.24)
self.assertEqual(str(p.resolve(strict=False)),[](#l3.25)
os.path.join(BASE, 'foo'))[](#l3.26)
p = P(BASE, '..', 'foo', 'in', 'spam')[](#l3.27)
self.assertEqual(str(p.resolve(strict=False)),[](#l3.28)
os.path.abspath(os.path.join('foo')))[](#l3.29) # These are all relative symlinks[](#l3.30) p = P(BASE, 'dirB', 'fileB')[](#l3.31) self._check_resolve_relative(p, p)[](#l3.32)
@@ -1509,6 +1518,18 @@ class _BasePathTest(object): self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) p = P(BASE, 'dirB', 'linkD', 'fileB') self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
# Non-strict[](#l3.37)
p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')[](#l3.38)
self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo'), False)[](#l3.39)
p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')[](#l3.40)
if os.name == 'nt':[](#l3.41)
# In Windows, if linkY points to dirB, 'dirA\linkY\..'[](#l3.42)
# resolves to 'dirA' without resolving linkY first.[](#l3.43)
self._check_resolve_relative(p, P(BASE, 'dirA', 'foo'), False)[](#l3.44)
else:[](#l3.45)
# In Posix, if linkY points to dirB, 'dirA/linkY/..'[](#l3.46)
# resolves to 'dirB/..' first before resolving to parent of dirB.[](#l3.47)
self._check_resolve_relative(p, P(BASE, 'foo'), False)[](#l3.48) # Now create absolute symlinks[](#l3.49) d = tempfile.mkdtemp(suffix='-dirD')[](#l3.50) self.addCleanup(support.rmtree, d)[](#l3.51)
@@ -1516,6 +1537,18 @@ class _BasePathTest(object): os.symlink(join('dirB'), os.path.join(d, 'linkY')) p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB'))
# Non-strict[](#l3.56)
p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')[](#l3.57)
self._check_resolve_relative(p, P(BASE, 'dirB', 'foo'), False)[](#l3.58)
p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')[](#l3.59)
if os.name == 'nt':[](#l3.60)
# In Windows, if linkY points to dirB, 'dirA\linkY\..'[](#l3.61)
# resolves to 'dirA' without resolving linkY first.[](#l3.62)
self._check_resolve_relative(p, P(d, 'foo'), False)[](#l3.63)
else:[](#l3.64)
# In Posix, if linkY points to dirB, 'dirA/linkY/..'[](#l3.65)
# resolves to 'dirB/..' first before resolving to parent of dirB.[](#l3.66)
self._check_resolve_relative(p, P(BASE, 'foo'), False)[](#l3.67)
@with_symlinks def test_resolve_dot(self): @@ -1525,7 +1558,11 @@ class _BasePathTest(object): self.dirlink(os.path.join('0', '0'), join('1')) self.dirlink(os.path.join('1', '1'), join('2')) q = p / '2'
self.assertEqual(q.resolve(), p)[](#l3.75)
self.assertEqual(q.resolve(strict=True), p)[](#l3.76)
r = q / '3' / '4'[](#l3.77)
self.assertRaises(FileNotFoundError, r.resolve, strict=True)[](#l3.78)
# Non-strict[](#l3.79)
self.assertEqual(r.resolve(strict=False), p / '3')[](#l3.80)
def test_with(self): p = self.cls(BASE) @@ -1972,10 +2009,10 @@ class PathTest(_BasePathTest, unittest.T class PosixPathTest(_BasePathTest, unittest.TestCase): cls = pathlib.PosixPath
- def _check_symlink_loop(self, *args, strict=True): path = self.cls(*args) with self.assertRaises(RuntimeError):
print(path.resolve())[](#l3.92)
print(path.resolve(strict))[](#l3.93)
def test_open_mode(self): old_mask = os.umask(0) @@ -2008,7 +2045,6 @@ class PosixPathTest(_BasePathTest, unitt @with_symlinks def test_resolve_loop(self):
# Loop detection for broken symlinks under POSIX[](#l3.101) # Loops with relative symlinks[](#l3.102) os.symlink('linkX/inside', join('linkX'))[](#l3.103) self._check_symlink_loop(BASE, 'linkX')[](#l3.104)
@@ -2016,6 +2052,8 @@ class PosixPathTest(_BasePathTest, unitt self._check_symlink_loop(BASE, 'linkY') os.symlink('linkZ/../linkZ', join('linkZ')) self._check_symlink_loop(BASE, 'linkZ')
# Non-strict[](#l3.109)
self._check_symlink_loop(BASE, 'linkZ', 'foo', strict=False)[](#l3.110) # Loops with absolute symlinks[](#l3.111) os.symlink(join('linkU/inside'), join('linkU'))[](#l3.112) self._check_symlink_loop(BASE, 'linkU')[](#l3.113)
@@ -2023,6 +2061,8 @@ class PosixPathTest(_BasePathTest, unitt self._check_symlink_loop(BASE, 'linkV') os.symlink(join('linkW/../linkW'), join('linkW')) self._check_symlink_loop(BASE, 'linkW')
# Non-strict[](#l3.118)
self._check_symlink_loop(BASE, 'linkW', 'foo', strict=False)[](#l3.119)
def test_glob(self): P = self.cls
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,9 @@ Core and Builtins Library ------- +- Issue #19717: Makes Path.resolve() succeed on paths that do not exist.