summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@microsoft.com>2016-11-09 20:58:31 (GMT)
committerSteve Dower <steve.dower@microsoft.com>2016-11-09 20:58:31 (GMT)
commitbb132fc34ef2aeaf4cbe9a37b3f70833e088c6a0 (patch)
tree219c1d9f5f7786c17bfd1bc83cad69a2e8caa1f0 /Lib
parent02f252f3af496f297dc8fc1f35a0634a686612a2 (diff)
parent98eb360531e5e3045b21b34afd32474cf8741c9b (diff)
downloadcpython-bb132fc34ef2aeaf4cbe9a37b3f70833e088c6a0.zip
cpython-bb132fc34ef2aeaf4cbe9a37b3f70833e088c6a0.tar.gz
cpython-bb132fc34ef2aeaf4cbe9a37b3f70833e088c6a0.tar.bz2
Issue #19717: Makes Path.resolve() succeed on paths that do not exist (patch by Vajrasky Kok)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/pathlib.py29
-rw-r--r--Lib/test/test_pathlib.py54
2 files changed, 70 insertions, 13 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 1b5ab38..6965393 100644
--- 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):
+ def resolve(self, path, strict=False):
s = str(path)
if not s:
return os.getcwd()
+ previous_s = None
if _getfinalpathname is not None:
- return self._ext_to_normal(_getfinalpathname(s))
+ if strict:
+ return self._ext_to_normal(_getfinalpathname(s))
+ else:
+ while True:
+ try:
+ s = self._ext_to_normal(_getfinalpathname(s))
+ except FileNotFoundError:
+ previous_s = s
+ s = os.path.abspath(os.path.join(s, os.pardir))
+ else:
+ if previous_s is None:
+ return s
+ else:
+ return s + os.path.sep + os.path.basename(previous_s)
# Means fallback on absolute
return None
@@ -285,7 +299,7 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts):
return parts
- def resolve(self, path):
+ def resolve(self, path, strict=False):
sep = self.sep
accessor = path._accessor
seen = {}
@@ -315,7 +329,10 @@ class _PosixFlavour(_Flavour):
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL:
- raise
+ if strict:
+ raise
+ else:
+ return newpath
# Not a symlink
path = newpath
else:
@@ -1092,7 +1109,7 @@ class Path(PurePath):
obj._init(template=self)
return obj
- def resolve(self):
+ 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)
+ s = self._flavour.resolve(self, strict=strict)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 2f2ba3c..f98c1fe 100644
--- 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):
- q = p.resolve()
+ def _check_resolve(self, p, expected, strict=True):
+ q = p.resolve(strict)
self.assertEqual(q, expected)
# 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()
+ p.resolve(strict=True)
self.assertEqual(cm.exception.errno, errno.ENOENT)
+ # Non-strict
+ self.assertEqual(str(p.resolve(strict=False)),
+ os.path.join(BASE, 'foo'))
+ p = P(BASE, 'foo', 'in', 'spam')
+ self.assertEqual(str(p.resolve(strict=False)),
+ os.path.join(BASE, 'foo'))
+ p = P(BASE, '..', 'foo', 'in', 'spam')
+ self.assertEqual(str(p.resolve(strict=False)),
+ os.path.abspath(os.path.join('foo')))
# These are all relative symlinks
p = P(BASE, 'dirB', 'fileB')
self._check_resolve_relative(p, p)
@@ -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
+ p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')
+ self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo'), False)
+ p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
+ if os.name == 'nt':
+ # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+ # resolves to 'dirA' without resolving linkY first.
+ self._check_resolve_relative(p, P(BASE, 'dirA', 'foo'), False)
+ else:
+ # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+ # resolves to 'dirB/..' first before resolving to parent of dirB.
+ self._check_resolve_relative(p, P(BASE, 'foo'), False)
# Now create absolute symlinks
d = tempfile.mkdtemp(suffix='-dirD')
self.addCleanup(support.rmtree, d)
@@ -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
+ p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')
+ self._check_resolve_relative(p, P(BASE, 'dirB', 'foo'), False)
+ p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
+ if os.name == 'nt':
+ # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+ # resolves to 'dirA' without resolving linkY first.
+ self._check_resolve_relative(p, P(d, 'foo'), False)
+ else:
+ # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+ # resolves to 'dirB/..' first before resolving to parent of dirB.
+ self._check_resolve_relative(p, P(BASE, 'foo'), False)
@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)
+ self.assertEqual(q.resolve(strict=True), p)
+ r = q / '3' / '4'
+ self.assertRaises(FileNotFoundError, r.resolve, strict=True)
+ # Non-strict
+ self.assertEqual(r.resolve(strict=False), p / '3')
def test_with(self):
p = self.cls(BASE)
@@ -1972,10 +2009,10 @@ class PathTest(_BasePathTest, unittest.TestCase):
class PosixPathTest(_BasePathTest, unittest.TestCase):
cls = pathlib.PosixPath
- def _check_symlink_loop(self, *args):
+ def _check_symlink_loop(self, *args, strict=True):
path = self.cls(*args)
with self.assertRaises(RuntimeError):
- print(path.resolve())
+ print(path.resolve(strict))
def test_open_mode(self):
old_mask = os.umask(0)
@@ -2008,7 +2045,6 @@ class PosixPathTest(_BasePathTest, unittest.TestCase):
@with_symlinks
def test_resolve_loop(self):
- # Loop detection for broken symlinks under POSIX
# Loops with relative symlinks
os.symlink('linkX/inside', join('linkX'))
self._check_symlink_loop(BASE, 'linkX')
@@ -2016,6 +2052,8 @@ class PosixPathTest(_BasePathTest, unittest.TestCase):
self._check_symlink_loop(BASE, 'linkY')
os.symlink('linkZ/../linkZ', join('linkZ'))
self._check_symlink_loop(BASE, 'linkZ')
+ # Non-strict
+ self._check_symlink_loop(BASE, 'linkZ', 'foo', strict=False)
# Loops with absolute symlinks
os.symlink(join('linkU/inside'), join('linkU'))
self._check_symlink_loop(BASE, 'linkU')
@@ -2023,6 +2061,8 @@ class PosixPathTest(_BasePathTest, unittest.TestCase):
self._check_symlink_loop(BASE, 'linkV')
os.symlink(join('linkW/../linkW'), join('linkW'))
self._check_symlink_loop(BASE, 'linkW')
+ # Non-strict
+ self._check_symlink_loop(BASE, 'linkW', 'foo', strict=False)
def test_glob(self):
P = self.cls