Repository navigation
pathlib's relative_to should behave like os.path.relpath #84538
Description
Activity
Can we improve pathlib.relative_to(other) so that it handles the case of a path not being a direct child of other, like os.path.relpath?
For example:
Path('/some/thing').relative_to('/foo') -> Path('../some/thing')At the moment it just raises an exception.
Reacted by Bogdan Pradatu- added3.9 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
on Apr 22, 2020 The current behaviour is by design. I would not mind adding an option to control it, though.
If you are new to Python development and want to submit a patch or PR, I recommend reading the Developer's Guide:
https://devguide.python.org/Thanks for your answer. Yeah, I'm new, I'm reading the guide, sorry for any
faux pas :)Ok, an option would be great as well, a simple True/False switch? Any
suggestion for the name?
I'll get back with a proper patch this time.On Wed, Apr 22, 2020 at 8:18 PM Antoine Pitrou <report@bugs.python.org>
wrote:Antoine Pitrou <solipsis@pitrou.net> added the comment:
The current behaviour is by design. I would not mind adding an option to
control it, though.If you are new to Python development and want to submit a patch or PR, I
recommend reading the Developer's Guide:
https://devguide.python.org/----------
Python tracker <report@bugs.python.org>
<https://bugs.python.org/issue40358\>
Note that the implementation of relpath is pure and thus assumes it's working with existing, resolved paths (i.e. "the filesystem is not accessed to confirm the existence or nature of path or start"). For example:
>>> os.path.relpath('/some/thing', '/symlink') '../some/thing'If "symlink" targets "/spam/eggs/foo", then the resolved path would be "/spam/eggs/some/thing" instead of "/some/thing". Maybe the relative_to method should default to a
strictmode that raises ValueError on ambiguous cases that depend on the "existence or nature" of the paths. I think the current implementation is strict.Yeah, you're right, there's no access to the filesystem and the result
is generated assuming the paths are already resolved.
strictseems 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.
Thank you for your work on this Domenico. For reviewing the code, would you mind creating a Github pull request for it as described here https://devguide.python.org/pullrequest/
I may have forgotten to use the proper format for the title of each
commit, should I delete the pull request and make a new one or can it
be fixed when (or if) it's pulled?On Thu, Apr 30, 2020 at 2:03 AM Roundup Robot <report@bugs.python.org> wrote:
Change by Roundup Robot <devnull@psf.upfronthosting.co.za>:
----------
nosy: +python-dev
nosy_count: 5.0 -> 6.0
pull_requests: +19128
stage: -> patch review
pull_request: #19807
Python tracker <report@bugs.python.org>
<https://bugs.python.org/issue40358\>
On bpo-44078 (closed as duplicate), Mark Hammond made a similar request.
- added3.11only security fixesonly security fixesand removed3.10 (EOL)end of lifeend of life
on May 14, 2021 Also requested in bpo-42234.
For the record, requested on Discourse as well, with a fairly similar proposal.
os.path.relpath()always callsabspath(), which is "impure" by pathlib standards.If both arguments to
relpath()are relative paths then theabspath()calls can probably be removed. It would save a system call, and allowPurePath.relative_to()to call intoos.path.relpath()safely:def relative_to(self, *other): if not other: raise TypeError("need at least one argument") other = type(self)(*other) if self.is_absolute() != other.is_absolute(): raise ValueError("one path is relative and the other is absolute: " "{!r}, {!r}".format(str(self), str(other))) rel = self._flavour.relpath(self, other) return type(self)(rel)
Reacted by C.A.M. GerlachAn interesting comment in
PurePath.relative_to():# 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
This isn't true for
ntpath.relpath():>>> import ntpath >>> ntpath.relpath('c:\\', 'c:') '.'
Pathlib's behaviour is deliberate but questionable. If we're willing to drop it, then we can call through to
os.path.relpath()when users callrelative_to(..., strict=False).Also,
path.is_relative_to(other)would simplify down toother == path or other in path.parents, which is so simple it barely qualifies for a (misleadingly named) method. It might make sense to deprecate it."C:" is the current directory in drive C, so I think pathlib's answer is the correct one here (of course, I wrote it so I may be biaised :-)).
Thanks Antoine! That makes sense, but it still seems a odd to me that naked Windows drive paths are the only place where absolute and relative paths can be mixed! Extending your logic:
- Should
PureWindowsPath('c:/').relative_to('c:foo')also return'/'? - Should
PurePosixPath('/').relative_to('foo')also return'/'?
Both of these raise
ValueErrorat the moment. Maybe this is a "practicality beats purity" thing?- Should
I would say it is a practically beats purity thing indeed. I don't remember the details of why I chose to allow it at the time.
Reacted by Barney Gale and C.A.M. GerlachCorrection to my earlier post:
C:is a relative path, andrelpath()transforms it to an absolute path (i.e. prepends the working directory) before working out the result. From an actual windows box andC:\Users\me:>>> import ntpath >>> ntpath.relpath('C:\\', start='C:') '..\\..' >>> ntpath.relpath('C:\\', start='C:foo') '..\\..\\..'
Here's
pathlibfor comparison's sake:>>> import pathlib >>> pathlib.PureWindowsPath('C:/').relative_to('C:') PureWindowsPath('/') >>> pathlib.PureWindowsPath('C:/').relative_to('C:foo') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/barney.gale/code/cpython/Lib/pathlib.py", line 673, in relative_to raise ValueError("{!r} is not in the subpath of {!r}" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueError: 'C:\\' is not in the subpath of 'C:foo' OR one path is relative and the other is absolute.
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: