This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: pathlib method relative_to doesnt work with // in paths
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: John15321, arhadthedev, barneygale, eryksun
Priority: normal Keywords: patch

Created on 2022-03-30 11:01 by John15321, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 32193 open arhadthedev, 2022-03-30 14:50
Messages (8)
msg416336 - (view) Author: Jan Bronicki (John15321) Date: 2022-03-30 11:01
The `//` path should be equivalent to `/`, and in some ways, it does behave like that in pathlib. But in the `relative_to` method on a `Path` object, it does not work
This is causing our CI pipeline to fail. In the documentation here you can see `//` being properly processed:

https://docs.python.org/3/library/pathlib.html


```python
In [10]: x=Path("/Library/Video") ; x.relative_to(Path("/"))
Out[10]: PosixPath('Library/Video')

In [11]: x=Path("//Library/Video") ; x.relative_to(Path("/"))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [11], in <cell line: 1>()
----> 1 x=Path("//Library/Video") ; x.relative_to(Path("/"))

File ~/.pyenv/versions/3.8.13/lib/python3.8/pathlib.py:908, in PurePath.relative_to(self, *other)
    906 if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
    907     formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
--> 908     raise ValueError("{!r} does not start with {!r}"
    909                      .format(str(self), str(formatted)))
    910 return self._from_parsed_parts('', root if n == 1 else '',
    911                                abs_parts[n:])

ValueError: '//Library/Video' does not start with '/'
```
msg416342 - (view) Author: Oleg Iarygin (arhadthedev) * Date: 2022-03-30 11:58
I started to investigate and found that a double slash in the beginning cancels any parsing of a path:

    >>> Path("//Library/Video")._parts
    ['\\\\Library\\Video\\']

vs

    >>> Path("/Library/Video")._parts
    ['\\', 'Library', 'Video']

Investigating further.
msg416348 - (view) Author: Oleg Iarygin (arhadthedev) * Date: 2022-03-30 12:24
As I found out, any path starting with two double slashes is treated as an UNC (network) path:

    vvvvvvvvvvvvvvvvvvvv root
    \\machine\mountpoint\directory\etc\...
               directory ^^^^^^^^^^^^^^^^^

So "/Library/Video" and "/" are directories mounted on your machine while "//Library/Video" is a computer named "Library" with a share named "Video".

However, an error message in Python 3.8 (as a file path suggests) is misleading. In 3.11 it was changed to "'\\Library\Video\' is not in the subpath of '\' OR one path is relative and the other is absolute."
msg416351 - (view) Author: Oleg Iarygin (arhadthedev) * Date: 2022-03-30 12:37
Also, the error message cannot be fixed because for 3.8 only security fixes are accepted since May 2021. For 3.9 and later, the message is already corrected.
msg416352 - (view) Author: Jan Bronicki (John15321) Date: 2022-03-30 12:44
But shouldn't it just work with `//` as a `/`? It seems like this is the behavior elsewhere. Sure I get that it cannot be done for 3.8. But the new error message implies that either `//` is not a subpath of `/` which it is, or that one is relative and the other is absolute, which is also false because both are absolutes
msg416355 - (view) Author: Oleg Iarygin (arhadthedev) * Date: 2022-03-30 13:23
> But shouldn't it just work with `//` as a `/`? It seems like this is the behavior elsewhere.

It works elsewhere because empty directory names are impossible so can be dropped. But if `//` is placed in the beginning, it gets a special meaning that totally changes the whole path so its plain replacement would give a totally wrong one.

Roughly speaking, "//Library/Video" is `/Video` on a computer named `Library`.
msg416356 - (view) Author: Jan Bronicki (John15321) Date: 2022-03-30 13:51
Hmm..., I get it, but Im not gonna lie it's pretty confusing given that in other places `//` works as a substitute for `/`. Maybe it should be mentioned in the documentation?
msg416489 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2022-04-01 14:26
> Hmm..., I get it, but Im not gonna lie it's pretty confusing given 
> that in other places `//` works as a substitute for `/`. Maybe it 
> should be mentioned in the documentation?

In Linux, the system resolves "//" as just "/". In other POSIX systems, such as Cygwin or MSYS2 (running in Windows), "//" is a UNC path of the form "//server/share/filepath". I would expect resolve() to handle this. For example:

Linux:

    >>> p = pathlib.Path('//tmp')
    >>> p
    PosixPath('//tmp')
    >>> p.resolve()
    PosixPath('/tmp')

However, resolve() is broken for a UNC path in 3.9 under MSYS2:

    >>> p = pathlib.Path('//localhost/C$/temp')
    >>> p.exists()
    True
    >>> p.resolve()
    PosixPath('/localhost/C$/temp')
    >>> p.resolve().exists()
    False

realpath() is also broken for this case in 3.9 under MSYS2:

    >>> os.path.realpath(p)
    '/localhost/C$/temp'
History
Date User Action Args
2022-04-11 14:59:57adminsetgithub: 91317
2022-04-01 15:38:13barneygalesetnosy: + barneygale
2022-04-01 14:26:55eryksunsetnosy: + eryksun
messages: + msg416489
2022-03-30 14:50:32arhadthedevsetkeywords: + patch
stage: patch review
pull_requests: + pull_request30271
2022-03-30 13:51:00John15321setmessages: + msg416356
2022-03-30 13:23:30arhadthedevsetmessages: + msg416355
2022-03-30 12:44:01John15321setmessages: + msg416352
2022-03-30 12:37:23arhadthedevsetmessages: + msg416351
2022-03-30 12:24:29arhadthedevsetmessages: + msg416348
2022-03-30 11:58:00arhadthedevsetnosy: + arhadthedev
messages: + msg416342
2022-03-30 11:01:52John15321create