Skip to content

Commit

Permalink
pythonGH-110109: pathlib ABCs: do not vary path syntax by host OS.
Browse files Browse the repository at this point in the history
Change the value of `pathlib._abc.PurePathBase.pathmod` from `os.path` to
`posixpath`.

User subclasses of `PurePathBase` and `PathBase` previously used the host
OS's path syntax, e.g. backslashes as separators on Windows. This is wrong
in most use cases, and likely to catch developers out unless they test on
both Windows and non-Windows machines.

In this patch we change the default to POSIX syntax, regardless of OS. This
is somewhat arguable (why not make all aspects of syntax abstract and
individually configurable?) but an improvement all the same.

This change has no effect on `PurePath`, `Path`, nor their subclasses. Only
private APIs are affected.
  • Loading branch information
barneygale committed Dec 16, 2023
1 parent d91e43e commit a03b584
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 36 deletions.
1 change: 1 addition & 0 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class PurePath(_abc.PurePathBase):
# path. It's set when `__hash__()` is called for the first time.
'_hash',
)
pathmod = os.path

def __new__(cls, *args, **kwargs):
"""Construct a PurePath from one or several strings and or existing
Expand Down
3 changes: 1 addition & 2 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import functools
import io
import ntpath
import os
import posixpath
import sys
import warnings
Expand Down Expand Up @@ -204,7 +203,7 @@ class PurePathBase:
# work from occurring when `resolve()` calls `stat()` or `readlink()`.
'_resolving',
)
pathmod = os.path
pathmod = posixpath

def __init__(self, *paths):
self._raw_paths = paths
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import os
import sys
import errno
import ntpath
import pathlib
import pickle
import posixpath
import socket
import stat
import tempfile
Expand Down Expand Up @@ -44,6 +46,47 @@
class PurePathTest(test_pathlib_abc.DummyPurePathTest):
cls = pathlib.PurePath

def test_concrete_class(self):
if self.cls is pathlib.PurePath:
expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath
else:
expected = self.cls
p = self.cls('a')
self.assertIs(type(p), expected)

def test_concrete_pathmod(self):
if self.cls is pathlib.PurePosixPath:
expected = posixpath
elif self.cls is pathlib.PureWindowsPath:
expected = ntpath
else:
expected = os.path
p = self.cls('a')
self.assertIs(p.pathmod, expected)

def test_different_pathmods_unequal(self):
p = self.cls('a')
if p.pathmod is posixpath:
q = pathlib.PureWindowsPath('a')
else:
q = pathlib.PurePosixPath('a')
self.assertNotEqual(p, q)

def test_different_pathmods_unordered(self):
p = self.cls('a')
if p.pathmod is posixpath:
q = pathlib.PureWindowsPath('a')
else:
q = pathlib.PurePosixPath('a')
with self.assertRaises(TypeError):
p < q
with self.assertRaises(TypeError):
p <= q
with self.assertRaises(TypeError):
p > q
with self.assertRaises(TypeError):
p >= q

def test_constructor_nested(self):
P = self.cls
P(FakePath("a/b/c"))
Expand Down
40 changes: 6 additions & 34 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def test_magic_methods(self):
self.assertIs(P.__gt__, object.__gt__)
self.assertIs(P.__ge__, object.__ge__)

def test_pathmod(self):
self.assertIs(self.cls.pathmod, posixpath)


class DummyPurePath(pathlib._abc.PurePathBase):
def __eq__(self, other):
Expand Down Expand Up @@ -93,37 +96,6 @@ def test_constructor_common(self):
P('a/b/c')
P('/a/b/c')

def test_concrete_class(self):
if self.cls is pathlib.PurePath:
expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath
else:
expected = self.cls
p = self.cls('a')
self.assertIs(type(p), expected)

def test_different_pathmods_unequal(self):
p = self.cls('a')
if p.pathmod is posixpath:
q = pathlib.PureWindowsPath('a')
else:
q = pathlib.PurePosixPath('a')
self.assertNotEqual(p, q)

def test_different_pathmods_unordered(self):
p = self.cls('a')
if p.pathmod is posixpath:
q = pathlib.PureWindowsPath('a')
else:
q = pathlib.PurePosixPath('a')
with self.assertRaises(TypeError):
p < q
with self.assertRaises(TypeError):
p <= q
with self.assertRaises(TypeError):
p > q
with self.assertRaises(TypeError):
p >= q

def _check_str_subclass(self, *args):
# Issue #21127: it should be possible to construct a PurePath object
# from a str subclass instance, and it then gets converted to
Expand Down Expand Up @@ -1420,7 +1392,7 @@ def test_resolve_common(self):
self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in',
'spam'), False)
p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
if os.name == 'nt' and isinstance(p, pathlib.Path):
if self.cls.pathmod is not posixpath:
# In Windows, if linkY points to dirB, 'dirA\linkY\..'
# resolves to 'dirA' without resolving linkY first.
self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in',
Expand All @@ -1440,7 +1412,7 @@ def test_resolve_common(self):
self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'),
False)
p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
if os.name == 'nt' and isinstance(p, pathlib.Path):
if self.cls.pathmod is not posixpath:
# In Windows, if linkY points to dirB, 'dirA\linkY\..'
# resolves to 'dirA' without resolving linkY first.
self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False)
Expand Down Expand Up @@ -1473,7 +1445,7 @@ def _check_symlink_loop(self, *args):
def test_resolve_loop(self):
if not self.can_symlink:
self.skipTest("symlinks required")
if os.name == 'nt' and issubclass(self.cls, pathlib.Path):
if self.cls.pathmod is not posixpath:
self.skipTest("symlink loops work differently with concrete Windows paths")
# Loops with relative symlinks.
self.cls(BASE, 'linkX').symlink_to('linkX/inside')
Expand Down

0 comments on commit a03b584

Please sign in to comment.