Skip to content

Pathlib join pure windows path and pure posix path changes it behaviour on 3.13 #125069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pohmelie opened this issue Oct 7, 2024 · 17 comments
Closed
Labels
3.13 bugs and security fixes topic-pathlib type-bug An unexpected behavior, bug, or error

Comments

@pohmelie
Copy link

pohmelie commented Oct 7, 2024

Bug report

Bug description:

3.12.7

Python 3.12.7 (main, Oct  8 2024, 00:39:14) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pathlib; pathlib.PureWindowsPath("C:\\A") / pathlib.PurePosixPath("/foo/C:\\B").relative_to("/")
PureWindowsPath('C:/B')

3.13.0rc3

Python 3.13.0rc3 (main, Oct  7 2024, 23:18:57) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pathlib; pathlib.PureWindowsPath("C:\\A") / pathlib.PurePosixPath("/foo/C:\\B").relative_to("/")
PureWindowsPath('C:/A/foo/C:/B')

Not sure if it is a bug, or a fixed behaviour

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

Linux

Linked PRs

@pohmelie pohmelie added the type-bug An unexpected behavior, bug, or error label Oct 7, 2024
@AA-Turner AA-Turner added the 3.13 bugs and security fixes label Oct 7, 2024
@AA-Turner
Copy link
Member

cc @barneygale, though the reproducer does look a bit off, with multiple drive roots.

@pohmelie can you reproduce this using only os.path?

A

@pohmelie
Copy link
Author

pohmelie commented Oct 7, 2024

@AA-Turner
If you provide a code with os.path module, then I can test it. But, AFAIK, os.path have no ability to manage windows and posix paths.

@terryjreedy
Copy link
Member

Checking 3.13 What's New or the linked log file might reveal if intentional change.

@ImFeH2
Copy link
Contributor

ImFeH2 commented Oct 8, 2024

It happens after this change.
#111431

@hacscred
Copy link

hacscred commented Oct 8, 2024

Classic that an optimization changes semantics. I'd rip out that optimization or make sure that the new behavior matches the specification.

@ZeroIntensity
Copy link
Member

ZeroIntensity commented Oct 8, 2024

cc @barneygale, apparently that PR introduced a breaking change.

Edit: oh wait, Adam already pinged, sorry!

@barneygale
Copy link
Contributor

Thanks for reporting this! There's a few things going on here. I think it comes down to pathlib's special handling of PurePath(PurePath(...)) and our efforts to delay joining of *pathsegments. I believe the 3.13 behaviour is correct, and that we should fix 3.12. PR coming up...

@barneygale
Copy link
Contributor

Another repro, this one works in 3.13 and 3.14:

>>> a = PureWindowsPath('a')
>>> b1 = PurePosixPath('b', 'c:\\d')
>>> b2 = PurePosixPath('b/c:\\d')
>>> b1 == b2
True
>>> a / b1
PureWindowsPath('c:/d')
>>> a / b2
PureWindowsPath('a/b/c:/d')

barneygale added a commit to barneygale/cpython that referenced this issue Oct 8, 2024
…..))`

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.
barneygale added a commit to barneygale/cpython that referenced this issue Oct 11, 2024
@barneygale
Copy link
Contributor

PR available, if anyone has a mo to review: #125156

barneygale added a commit to barneygale/cpython that referenced this issue Oct 13, 2024
barneygale added a commit that referenced this issue Oct 13, 2024
…125156)

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.

Co-authored-by: Bénédikt Tran <[email protected]>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Oct 13, 2024
…..))` (pythonGH-125156)

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.

(cherry picked from commit cb8e599)

Co-authored-by: Barney Gale <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
barneygale added a commit to barneygale/cpython that referenced this issue Oct 13, 2024
…xPath(...))` (pythonGH-125156)

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.

Co-authored-by: Bénédikt Tran <[email protected]>.
(cherry picked from commit cb8e599)

Co-authored-by: Barney Gale <[email protected]>
barneygale added a commit that referenced this issue Oct 13, 2024
…...))` (GH-125156) (#125409)

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.

(cherry picked from commit cb8e599)

Co-authored-by: Barney Gale <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
barneygale added a commit that referenced this issue Oct 13, 2024
…...))` (GH-125156) (#125410)

`PurePath.__init__()` incorrectly uses the `_raw_paths` of a given
`PurePath` object with a different flavour, even though the procedure to
join path segments can differ between flavours.

This change makes the `_raw_paths`-enabled deferred joining apply _only_
when the path flavours match.

(cherry picked from commit cb8e599)

Co-authored-by: Bénédikt Tran <[email protected]>
@barneygale
Copy link
Contributor

barneygale commented Oct 13, 2024

I've pushed a fix to 3.12, 3.13 and 3.14 (main).

In the original repro case, the 3.12 behaviour was buggy and the 3.13 behaviour was correct. The optimisation in #111431 (3.13+) happened to fix the issue for paths generated by relative_to(). The change I've applied here should fix the problem at its source.

Thanks for reporting :)

@pohmelie
Copy link
Author

@barneygale
So, what should I do in my library code? Should I dispatch behaviour by python version? Will the fix lands on 3.12.8 and buggy behaviour still presents in 3.12.0 to 3.12.7 inclusive? 🤔 I want equal behaviour of my function on all supported python versions from 3.8 to 3.13.

@barneygale
Copy link
Contributor

@barneygale So, what should I do in my library code? Should I dispatch behaviour by python version? Will the fix lands on 3.12.8 and buggy behaviour still presents in 3.12.0 to 3.12.7 inclusive? 🤔 I want equal behaviour of my function on all supported python versions from 3.8 to 3.13.

Untested, but I think this will work in all version:

diff --git a/src/aioftp/server.py b/src/aioftp/server.py
index 1830d78..d31f40a 100644
--- a/src/aioftp/server.py
+++ b/src/aioftp/server.py
@@ -1031,7 +1031,7 @@ class Server:
             else:
                 resolved_virtual_path /= part
         base_path = connection.user.base_path
-        real_path = base_path / resolved_virtual_path.relative_to("/")
+        real_path = base_path / str(resolved_virtual_path.relative_to("/"))
         # replace with `is_relative_to` check after 3.9+ requirements lands
         try:
             real_path.relative_to(base_path)

@hacscred
Copy link

(Pathlib code is atrocious, btw. )

(I think for something as big as Python, getting rid of informal specifications would be a good idea, but I doubt the people in the project could ever accept it.

The specification of many pathlib functions is completely underspecified.)

@barneygale
Copy link
Contributor

Please use https://discuss.python.org/ for general discussion. If you're looking to convince other people to do work on your behalf (e.g. improving the pathlib codebase) then I suggest you strike a more constructive note. If you're planning to put in your own issues and PRs I'd be happy to review.

@pohmelie
Copy link
Author

@barneygale yep, looks like this str(...) trick works, thank you!

pohmelie added a commit to aio-libs/aioftp that referenced this issue Oct 14, 2024
@hacscred
Copy link

Please use https://discuss.python.org/ for general discussion. If you're looking to convince other people to do work on your behalf (e.g. improving the pathlib codebase) then I suggest you strike a more constructive note. If you're planning to put in your own issues and PRs I'd be happy to review.

I don't particularly care that much about Python. I looked at the code for this issue and I just wanted to leave my opinion on the Internet, which could be used by someone as an argument to fix pathlib.

I was just being nice in sharing my (highly valuable) opinion.

I don't particularly care if anyone does something with it. I certainly am not forcing anyone to do anything. Not sure where you got that idea.

I am also not interested in discussion, because that would suggest I care about the opinion of others, which I don't, since I already am an expert.

So, ignore my advice all you want, but that doesn't change the reality of the quality of that code. The code generator is even worse, for those interested.

I will say it again: I am not asking for your opinion. I am giving you mine.

@barneygale
Copy link
Contributor

Wow what valuable feedback, thanks buddy! :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.13 bugs and security fixes topic-pathlib type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

7 participants