Domenico Ragusa <domenicorag...@gmail.com> added the comment:
Yeah, you're right, there's no access to the filesystem and the result
is generated assuming the paths are already resolved.
`strict` seems to be an appropriate name for the option, thanks.
I've looked into the test suite, it helped a lot especially with
Windows paths, they were more complicated than I though.
I've duplicated the tests to verify that it still function as before
and I've added some tests for values that would raise an exception. It
works.
I'm not overly fond of the way I check for unrelated paths, but I
couldn't think of something more elegant.
Any input is appreciated.
----------
Added file: https://bugs.python.org/file49095/relative_to.patch
_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue40358>
_______________________________________
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index f98d69e..eb25761 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -895,10 +895,10 @@ class PurePath(object):
return self._from_parsed_parts(self._drv, self._root,
self._parts[:-1] + [name])
- def relative_to(self, *other):
+ def relative_to(self, *other, strict=True):
"""Return the relative path to another path identified by the passed
arguments. If the operation is not possible (because this is not
- a subpath of the other path), raise ValueError.
+ related to the other path), raise ValueError.
"""
# For the purpose of this method, drive and root are considered
# separate parts, i.e.:
@@ -918,14 +918,31 @@ class PurePath(object):
to_abs_parts = [to_drv, to_root] + to_parts[1:]
else:
to_abs_parts = to_parts
+
n = len(to_abs_parts)
cf = self._flavour.casefold_parts
- if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+ common = 0
+ for p, tp in zip(cf(abs_parts), cf(to_abs_parts)):
+ if p != tp:
+ break
+ common += 1
+
+ if strict:
+ failure = (root or drv) if n == 0 else common != n
+ error_message = "{!r} does not start with {!r}"
+ up_parts = []
+ else:
+ failure = root != to_root
+ if drv or to_drv:
+ failure = cf([drv]) != cf([to_drv]) or (failure and n > 1)
+ error_message = "{!r} is not related to {!r}"
+ up_parts = (n-common)*['..']
+
+ if failure:
formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
- raise ValueError("{!r} does not start with {!r}"
- .format(str(self), str(formatted)))
- return self._from_parsed_parts('', root if n == 1 else '',
- abs_parts[n:])
+ raise ValueError(error_message.format(str(self), str(formatted)))
+ return self._from_parsed_parts('', root if common == 1 else '',
+ up_parts+abs_parts[common:])
def is_relative_to(self, *other):
"""Return True if the path is relative to another path or False.
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 1589282..a6d8fe4 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -613,13 +613,30 @@ class _BasePurePathTest(object):
self.assertEqual(p.relative_to('a/'), P('b'))
self.assertEqual(p.relative_to(P('a/b')), P())
self.assertEqual(p.relative_to('a/b'), P())
+ self.assertEqual(p.relative_to(P(), strict=False), P('a/b'))
+ self.assertEqual(p.relative_to('', strict=False), P('a/b'))
+ self.assertEqual(p.relative_to(P('a'), strict=False), P('b'))
+ self.assertEqual(p.relative_to('a', strict=False), P('b'))
+ self.assertEqual(p.relative_to('a/', strict=False), P('b'))
+ self.assertEqual(p.relative_to(P('a/b'), strict=False), P())
+ self.assertEqual(p.relative_to('a/b', strict=False), P())
+ self.assertEqual(p.relative_to(P('a/c'), strict=False), P('../b'))
+ self.assertEqual(p.relative_to('a/c', strict=False), P('../b'))
+ self.assertEqual(p.relative_to(P('a/b/c'), strict=False), P('..'))
+ self.assertEqual(p.relative_to('a/b/c', strict=False), P('..'))
+ self.assertEqual(p.relative_to(P('c'), strict=False), P('../a/b'))
+ self.assertEqual(p.relative_to('c', strict=False), P('../a/b'))
# With several args.
self.assertEqual(p.relative_to('a', 'b'), P())
+ self.assertEqual(p.relative_to('a', 'b', strict=False), P())
# Unrelated paths.
self.assertRaises(ValueError, p.relative_to, P('c'))
self.assertRaises(ValueError, p.relative_to, P('a/b/c'))
self.assertRaises(ValueError, p.relative_to, P('a/c'))
self.assertRaises(ValueError, p.relative_to, P('/a'))
+ self.assertRaises(ValueError, p.relative_to, P('/'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('/a'), strict=False)
+
p = P('/a/b')
self.assertEqual(p.relative_to(P('/')), P('a/b'))
self.assertEqual(p.relative_to('/'), P('a/b'))
@@ -628,6 +645,19 @@ class _BasePurePathTest(object):
self.assertEqual(p.relative_to('/a/'), P('b'))
self.assertEqual(p.relative_to(P('/a/b')), P())
self.assertEqual(p.relative_to('/a/b'), P())
+ self.assertEqual(p.relative_to(P('/'), strict=False), P('a/b'))
+ self.assertEqual(p.relative_to('/', strict=False), P('a/b'))
+ self.assertEqual(p.relative_to(P('/a'), strict=False), P('b'))
+ self.assertEqual(p.relative_to('/a', strict=False), P('b'))
+ self.assertEqual(p.relative_to('/a/', strict=False), P('b'))
+ self.assertEqual(p.relative_to(P('/a/b'), strict=False), P())
+ self.assertEqual(p.relative_to('/a/b', strict=False), P())
+ self.assertEqual(p.relative_to(P('/a/c'), strict=False), P('../b'))
+ self.assertEqual(p.relative_to('/a/c', strict=False), P('../b'))
+ self.assertEqual(p.relative_to(P('/a/b/c'), strict=False), P('..'))
+ self.assertEqual(p.relative_to('/a/b/c', strict=False), P('..'))
+ self.assertEqual(p.relative_to(P('/c'), strict=False), P('../a/b'))
+ self.assertEqual(p.relative_to('/c', strict=False), P('../a/b'))
# Unrelated paths.
self.assertRaises(ValueError, p.relative_to, P('/c'))
self.assertRaises(ValueError, p.relative_to, P('/a/b/c'))
@@ -635,6 +665,8 @@ class _BasePurePathTest(object):
self.assertRaises(ValueError, p.relative_to, P())
self.assertRaises(ValueError, p.relative_to, '')
self.assertRaises(ValueError, p.relative_to, P('a'))
+ self.assertRaises(ValueError, p.relative_to, P(''), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('a'), strict=False)
def test_is_relative_to_common(self):
P = self.cls
@@ -1079,6 +1111,17 @@ class PureWindowsPathTest(_BasePurePathTest,
unittest.TestCase):
self.assertEqual(p.relative_to('c:foO/'), P('Bar'))
self.assertEqual(p.relative_to(P('c:foO/baR')), P())
self.assertEqual(p.relative_to('c:foO/baR'), P())
+ self.assertEqual(p.relative_to(P('c:'), strict=False), P('Foo/Bar'))
+ self.assertEqual(p.relative_to('c:', strict=False), P('Foo/Bar'))
+ self.assertEqual(p.relative_to(P('c:foO'), strict=False), P('Bar'))
+ self.assertEqual(p.relative_to('c:foO', strict=False), P('Bar'))
+ self.assertEqual(p.relative_to('c:foO/', strict=False), P('Bar'))
+ self.assertEqual(p.relative_to(P('c:foO/baR'), strict=False), P())
+ self.assertEqual(p.relative_to('c:foO/baR', strict=False), P())
+ self.assertEqual(p.relative_to(P('C:Foo/Bar/Baz'), strict=False),
P('..'))
+ self.assertEqual(p.relative_to(P('C:Foo/Baz'), strict=False),
P('../Bar'))
+ self.assertEqual(p.relative_to(P('C:Baz/Bar'), strict=False),
P('../../Foo/Bar'))
+
# Unrelated paths.
self.assertRaises(ValueError, p.relative_to, P())
self.assertRaises(ValueError, p.relative_to, '')
@@ -1089,6 +1132,13 @@ class PureWindowsPathTest(_BasePurePathTest,
unittest.TestCase):
self.assertRaises(ValueError, p.relative_to, P('C:/Foo'))
self.assertRaises(ValueError, p.relative_to, P('C:Foo/Bar/Baz'))
self.assertRaises(ValueError, p.relative_to, P('C:Foo/Baz'))
+ self.assertRaises(ValueError, p.relative_to, P(), strict=False)
+ self.assertRaises(ValueError, p.relative_to, '', strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('d:'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('/'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('Foo'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('/Foo'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), strict=False)
p = P('C:/Foo/Bar')
self.assertEqual(p.relative_to(P('c:')), P('/Foo/Bar'))
self.assertEqual(p.relative_to('c:'), P('/Foo/Bar'))
@@ -1101,6 +1151,20 @@ class PureWindowsPathTest(_BasePurePathTest,
unittest.TestCase):
self.assertEqual(p.relative_to('c:/foO/'), P('Bar'))
self.assertEqual(p.relative_to(P('c:/foO/baR')), P())
self.assertEqual(p.relative_to('c:/foO/baR'), P())
+ self.assertEqual(p.relative_to(P('c:'), strict=False), P('/Foo/Bar'))
+ self.assertEqual(p.relative_to('c:', strict=False), P('/Foo/Bar'))
+ self.assertEqual(str(p.relative_to(P('c:'), strict=False)),
'\\Foo\\Bar')
+ self.assertEqual(str(p.relative_to('c:', strict=False)), '\\Foo\\Bar')
+ self.assertEqual(p.relative_to(P('c:/'), strict=False), P('Foo/Bar'))
+ self.assertEqual(p.relative_to('c:/', strict=False), P('Foo/Bar'))
+ self.assertEqual(p.relative_to(P('c:/foO'), strict=False), P('Bar'))
+ self.assertEqual(p.relative_to('c:/foO', strict=False), P('Bar'))
+ self.assertEqual(p.relative_to('c:/foO/', strict=False), P('Bar'))
+ self.assertEqual(p.relative_to(P('c:/foO/baR'), strict=False), P())
+ self.assertEqual(p.relative_to('c:/foO/baR', strict=False), P())
+ self.assertEqual(p.relative_to('C:/Baz', strict=False),
P('../Foo/Bar'))
+ self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', strict=False),
P('..'))
+ self.assertEqual(p.relative_to('C:/Foo/Baz', strict=False),
P('../Bar'))
# Unrelated paths.
self.assertRaises(ValueError, p.relative_to, P('C:/Baz'))
self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz'))
@@ -1111,6 +1175,13 @@ class PureWindowsPathTest(_BasePurePathTest,
unittest.TestCase):
self.assertRaises(ValueError, p.relative_to, P('/'))
self.assertRaises(ValueError, p.relative_to, P('/Foo'))
self.assertRaises(ValueError, p.relative_to, P('//C/Foo'))
+ self.assertRaises(ValueError, p.relative_to, P('C:Foo'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('d:'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('d:/'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('/'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('/Foo'), strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('//C/Foo'),
strict=False)
+
# UNC paths.
p = P('//Server/Share/Foo/Bar')
self.assertEqual(p.relative_to(P('//sErver/sHare')), P('Foo/Bar'))
@@ -1121,11 +1192,25 @@ class PureWindowsPathTest(_BasePurePathTest,
unittest.TestCase):
self.assertEqual(p.relative_to('//sErver/sHare/Foo/'), P('Bar'))
self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar')), P())
self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar'), P())
+ self.assertEqual(p.relative_to(P('//sErver/sHare'), strict=False),
P('Foo/Bar'))
+ self.assertEqual(p.relative_to('//sErver/sHare', strict=False),
P('Foo/Bar'))
+ self.assertEqual(p.relative_to('//sErver/sHare/', strict=False),
P('Foo/Bar'))
+ self.assertEqual(p.relative_to(P('//sErver/sHare/Foo'), strict=False),
P('Bar'))
+ self.assertEqual(p.relative_to('//sErver/sHare/Foo', strict=False),
P('Bar'))
+ self.assertEqual(p.relative_to('//sErver/sHare/Foo/', strict=False),
P('Bar'))
+ self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar'),
strict=False), P())
+ self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar',
strict=False), P())
+ self.assertEqual(p.relative_to(P('//sErver/sHare/bar'), strict=False),
P('../Foo/Bar'))
+ self.assertEqual(p.relative_to('//sErver/sHare/bar', strict=False),
P('../Foo/Bar'))
# Unrelated paths.
self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'))
self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'))
self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'))
self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'))
+ self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'),
strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'),
strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'),
strict=False)
+ self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'),
strict=False)
def test_is_relative_to(self):
P = self.cls
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com