diff -r 96a68e369d13 Lib/pathlib.py --- a/Lib/pathlib.py Mon Dec 09 20:33:33 2013 -0700 +++ b/Lib/pathlib.py Tue Dec 10 16:52:08 2013 +0800 @@ -175,7 +175,7 @@ def casefold_parts(self, parts): return [p.lower() for p in parts] - def resolve(self, path): + def resolve(self, path, strict=True): s = str(path) if not s: return os.getcwd() @@ -252,7 +252,7 @@ def casefold_parts(self, parts): return parts - def resolve(self, path): + def resolve(self, path, strict=True): sep = self.sep def split(p): return [x for x in p.split(sep) if x] @@ -279,7 +279,10 @@ target = accessor.readlink(cur) except OSError as e: if e.errno != EINVAL: - raise + if strict: + raise + else: + return cur # Not a symlink resolved = cur else: @@ -1010,7 +1013,7 @@ obj._init(template=self) return obj - def resolve(self): + def resolve(self, strict=True): """ Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under @@ -1018,7 +1021,7 @@ """ 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 -r 96a68e369d13 Lib/test/test_pathlib.py --- a/Lib/test/test_pathlib.py Mon Dec 09 20:33:33 2013 -0700 +++ b/Lib/test/test_pathlib.py Tue Dec 10 16:52:08 2013 +0800 @@ -1280,8 +1280,8 @@ self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) self.assertEqual(set(p.glob("../xyzzy")), set()) - def _check_resolve_relative(self, p, expected): - q = p.resolve() + def _check_resolve_relative(self, p, expected, strict=True): + q = p.resolve(strict) self.assertEqual(q, expected) def _check_resolve_absolute(self, p, expected): @@ -1295,6 +1295,15 @@ with self.assertRaises(OSError) as cm: p.resolve() 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) @@ -1304,6 +1313,11 @@ 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') + self._check_resolve_relative(p, P(BASE, 'foo'), False) # Now create absolute symlinks d = tempfile.mkdtemp(suffix='-dirD') self.addCleanup(shutil.rmtree, d) @@ -1311,6 +1325,11 @@ 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') + self._check_resolve_relative(p, P(BASE, 'foo'), False) @with_symlinks def test_resolve_dot(self): @@ -1321,6 +1340,10 @@ self.dirlink(os.path.join('1', '1'), join('2')) q = p / '2' self.assertEqual(q.resolve(), p) + r = q / '3' / '4' + self.assertRaises(FileNotFoundError, r.resolve) + # Non-strict + self.assertEqual(r.resolve(strict=False), p / '3') def test_with(self): p = self.cls(BASE) @@ -1640,10 +1663,10 @@ 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) @@ -1676,8 +1699,6 @@ @with_symlinks def test_resolve_loop(self): - # Loop detection for broken symlinks under POSIX - P = self.cls # Loops with relative symlinks os.symlink('linkX/inside', join('linkX')) self._check_symlink_loop(BASE, 'linkX') @@ -1685,6 +1706,8 @@ 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') @@ -1692,6 +1715,8 @@ 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