mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-99029: Fix handling of PureWindowsPath('C:\<blah>').relative_to('C:')
(GH-99031)
`relative_to()` now treats naked drive paths as relative. This brings its behaviour in line with other parts of pathlib, and with `ntpath.relpath()`, and so allows us to factor out the pathlib-specific implementation.
This commit is contained in:
parent
7d2dcc53d0
commit
ae234fbc5c
3 changed files with 20 additions and 52 deletions
|
@ -632,57 +632,27 @@ class PurePath(object):
|
||||||
The *walk_up* parameter controls whether `..` may be used to resolve
|
The *walk_up* parameter controls whether `..` may be used to resolve
|
||||||
the path.
|
the path.
|
||||||
"""
|
"""
|
||||||
# For the purpose of this method, drive and root are considered
|
|
||||||
# separate parts, i.e.:
|
|
||||||
# Path('c:/').relative_to('c:') gives Path('/')
|
|
||||||
# Path('c:/').relative_to('/') raise ValueError
|
|
||||||
if not other:
|
if not other:
|
||||||
raise TypeError("need at least one argument")
|
raise TypeError("need at least one argument")
|
||||||
parts = self._parts
|
path_cls = type(self)
|
||||||
drv = self._drv
|
other = path_cls(*other)
|
||||||
root = self._root
|
for step, path in enumerate([other] + list(other.parents)):
|
||||||
if root:
|
if self.is_relative_to(path):
|
||||||
abs_parts = [drv, root] + parts[1:]
|
|
||||||
else:
|
|
||||||
abs_parts = parts
|
|
||||||
other_drv, other_root, other_parts = self._parse_args(other)
|
|
||||||
if other_root:
|
|
||||||
other_abs_parts = [other_drv, other_root] + other_parts[1:]
|
|
||||||
else:
|
|
||||||
other_abs_parts = other_parts
|
|
||||||
num_parts = len(other_abs_parts)
|
|
||||||
casefold = self._flavour.casefold_parts
|
|
||||||
num_common_parts = 0
|
|
||||||
for part, other_part in zip(casefold(abs_parts), casefold(other_abs_parts)):
|
|
||||||
if part != other_part:
|
|
||||||
break
|
break
|
||||||
num_common_parts += 1
|
|
||||||
if walk_up:
|
|
||||||
failure = root != other_root
|
|
||||||
if drv or other_drv:
|
|
||||||
failure = casefold([drv]) != casefold([other_drv]) or (failure and num_parts > 1)
|
|
||||||
error_message = "{!r} is not on the same drive as {!r}"
|
|
||||||
up_parts = (num_parts-num_common_parts)*['..']
|
|
||||||
else:
|
else:
|
||||||
failure = (root or drv) if num_parts == 0 else num_common_parts != num_parts
|
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
|
||||||
error_message = "{!r} is not in the subpath of {!r}"
|
if step and not walk_up:
|
||||||
up_parts = []
|
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
|
||||||
error_message += " OR one path is relative and the other is absolute."
|
parts = ('..',) * step + self.parts[len(path.parts):]
|
||||||
if failure:
|
return path_cls(*parts)
|
||||||
formatted = self._format_parsed_parts(other_drv, other_root, other_parts)
|
|
||||||
raise ValueError(error_message.format(str(self), str(formatted)))
|
|
||||||
path_parts = up_parts + abs_parts[num_common_parts:]
|
|
||||||
new_root = root if num_common_parts == 1 else ''
|
|
||||||
return self._from_parsed_parts('', new_root, path_parts)
|
|
||||||
|
|
||||||
def is_relative_to(self, *other):
|
def is_relative_to(self, *other):
|
||||||
"""Return True if the path is relative to another path or False.
|
"""Return True if the path is relative to another path or False.
|
||||||
"""
|
"""
|
||||||
try:
|
if not other:
|
||||||
self.relative_to(*other)
|
raise TypeError("need at least one argument")
|
||||||
return True
|
other = type(self)(*other)
|
||||||
except ValueError:
|
return other == self or other in self.parents
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parts(self):
|
def parts(self):
|
||||||
|
|
|
@ -1183,10 +1183,6 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
|
||||||
self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True)
|
self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True)
|
||||||
self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True)
|
self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True)
|
||||||
p = P('C:/Foo/Bar')
|
p = P('C:/Foo/Bar')
|
||||||
self.assertEqual(p.relative_to(P('c:')), P('/Foo/Bar'))
|
|
||||||
self.assertEqual(p.relative_to('c:'), P('/Foo/Bar'))
|
|
||||||
self.assertEqual(str(p.relative_to(P('c:'))), '\\Foo\\Bar')
|
|
||||||
self.assertEqual(str(p.relative_to('c:')), '\\Foo\\Bar')
|
|
||||||
self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar'))
|
self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar'))
|
||||||
self.assertEqual(p.relative_to('c:/'), P('Foo/Bar'))
|
self.assertEqual(p.relative_to('c:/'), P('Foo/Bar'))
|
||||||
self.assertEqual(p.relative_to(P('c:/foO')), P('Bar'))
|
self.assertEqual(p.relative_to(P('c:/foO')), P('Bar'))
|
||||||
|
@ -1194,10 +1190,6 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
|
||||||
self.assertEqual(p.relative_to('c:/foO/'), P('Bar'))
|
self.assertEqual(p.relative_to('c:/foO/'), P('Bar'))
|
||||||
self.assertEqual(p.relative_to(P('c:/foO/baR')), P())
|
self.assertEqual(p.relative_to(P('c:/foO/baR')), P())
|
||||||
self.assertEqual(p.relative_to('c:/foO/baR'), P())
|
self.assertEqual(p.relative_to('c:/foO/baR'), P())
|
||||||
self.assertEqual(p.relative_to(P('c:'), walk_up=True), P('/Foo/Bar'))
|
|
||||||
self.assertEqual(p.relative_to('c:', walk_up=True), P('/Foo/Bar'))
|
|
||||||
self.assertEqual(str(p.relative_to(P('c:'), walk_up=True)), '\\Foo\\Bar')
|
|
||||||
self.assertEqual(str(p.relative_to('c:', walk_up=True)), '\\Foo\\Bar')
|
|
||||||
self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar'))
|
self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar'))
|
||||||
self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar'))
|
self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar'))
|
||||||
self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar'))
|
self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar'))
|
||||||
|
@ -1209,6 +1201,8 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
|
||||||
self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..'))
|
self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..'))
|
||||||
self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar'))
|
self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar'))
|
||||||
# Unrelated paths.
|
# Unrelated paths.
|
||||||
|
self.assertRaises(ValueError, p.relative_to, 'c:')
|
||||||
|
self.assertRaises(ValueError, p.relative_to, P('c:'))
|
||||||
self.assertRaises(ValueError, p.relative_to, P('C:/Baz'))
|
self.assertRaises(ValueError, p.relative_to, P('C:/Baz'))
|
||||||
self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz'))
|
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('C:/Foo/Baz'))
|
||||||
|
@ -1218,6 +1212,8 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
|
||||||
self.assertRaises(ValueError, p.relative_to, P('/'))
|
self.assertRaises(ValueError, p.relative_to, P('/'))
|
||||||
self.assertRaises(ValueError, p.relative_to, P('/Foo'))
|
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'))
|
||||||
|
self.assertRaises(ValueError, p.relative_to, 'c:', walk_up=True)
|
||||||
|
self.assertRaises(ValueError, p.relative_to, P('c:'), walk_up=True)
|
||||||
self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True)
|
self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True)
|
||||||
self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True)
|
self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True)
|
||||||
self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True)
|
self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True)
|
||||||
|
@ -1275,13 +1271,13 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
|
||||||
self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz')))
|
self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz')))
|
||||||
self.assertFalse(p.is_relative_to(P('C:Foo/Baz')))
|
self.assertFalse(p.is_relative_to(P('C:Foo/Baz')))
|
||||||
p = P('C:/Foo/Bar')
|
p = P('C:/Foo/Bar')
|
||||||
self.assertTrue(p.is_relative_to('c:'))
|
|
||||||
self.assertTrue(p.is_relative_to(P('c:/')))
|
self.assertTrue(p.is_relative_to(P('c:/')))
|
||||||
self.assertTrue(p.is_relative_to(P('c:/foO')))
|
self.assertTrue(p.is_relative_to(P('c:/foO')))
|
||||||
self.assertTrue(p.is_relative_to('c:/foO/'))
|
self.assertTrue(p.is_relative_to('c:/foO/'))
|
||||||
self.assertTrue(p.is_relative_to(P('c:/foO/baR')))
|
self.assertTrue(p.is_relative_to(P('c:/foO/baR')))
|
||||||
self.assertTrue(p.is_relative_to('c:/foO/baR'))
|
self.assertTrue(p.is_relative_to('c:/foO/baR'))
|
||||||
# Unrelated paths.
|
# Unrelated paths.
|
||||||
|
self.assertFalse(p.is_relative_to('c:'))
|
||||||
self.assertFalse(p.is_relative_to(P('C:/Baz')))
|
self.assertFalse(p.is_relative_to(P('C:/Baz')))
|
||||||
self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz')))
|
self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz')))
|
||||||
self.assertFalse(p.is_relative_to(P('C:/Foo/Baz')))
|
self.assertFalse(p.is_relative_to(P('C:/Foo/Baz')))
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:meth:`pathlib.PurePath.relative_to()` now treats naked Windows drive paths
|
||||||
|
as relative. This brings its behaviour in line with other parts of pathlib.
|
Loading…
Add table
Add a link
Reference in a new issue