Skip to content

GH-114575: Rename PurePath.pathmod to PurePath.parser #116513

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

Merged
merged 2 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,10 @@ Methods and properties

Pure paths provide the following methods and properties:

.. attribute:: PurePath.pathmod
.. attribute:: PurePath.parser

The implementation of the :mod:`os.path` module used for low-level path
operations: either :mod:`posixpath` or :mod:`ntpath`.
parsing and joining: either :mod:`posixpath` or :mod:`ntpath`.

.. versionadded:: 3.13

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ pathlib
shell-style wildcards, including the recursive wildcard "``**``".
(Contributed by Barney Gale in :gh:`73435`.)

* Add :attr:`pathlib.PurePath.parser` class attribute that stores the
implementation of :mod:`os.path` used for low-level path parsing and
joining: either ``posixpath`` or ``ntpath``.

* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`,
:meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`,
:meth:`~pathlib.Path.is_dir`, :meth:`~pathlib.Path.owner`,
Expand Down
54 changes: 27 additions & 27 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class PurePath(_abc.PurePathBase):
# path. It's set when `__hash__()` is called for the first time.
'_hash',
)
pathmod = os.path
parser = os.path

def __new__(cls, *args, **kwargs):
"""Construct a PurePath from one or several strings and or existing
Expand All @@ -126,7 +126,7 @@ def __init__(self, *args):
paths = []
for arg in args:
if isinstance(arg, PurePath):
if arg.pathmod is ntpath and self.pathmod is posixpath:
if arg.parser is ntpath and self.parser is posixpath:
# GH-103631: Convert separators for backwards compatibility.
paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
else:
Expand Down Expand Up @@ -187,7 +187,7 @@ def _str_normcase(self):
try:
return self._str_normcase_cached
except AttributeError:
if _abc._is_case_sensitive(self.pathmod):
if _abc._is_case_sensitive(self.parser):
self._str_normcase_cached = str(self)
else:
self._str_normcase_cached = str(self).lower()
Expand All @@ -203,34 +203,34 @@ def __hash__(self):
def __eq__(self, other):
if not isinstance(other, PurePath):
return NotImplemented
return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
return self._str_normcase == other._str_normcase and self.parser is other.parser

@property
def _parts_normcase(self):
# Cached parts with normalized case, for comparisons.
try:
return self._parts_normcase_cached
except AttributeError:
self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
self._parts_normcase_cached = self._str_normcase.split(self.parser.sep)
return self._parts_normcase_cached

def __lt__(self, other):
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
if not isinstance(other, PurePath) or self.parser is not other.parser:
return NotImplemented
return self._parts_normcase < other._parts_normcase

def __le__(self, other):
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
if not isinstance(other, PurePath) or self.parser is not other.parser:
return NotImplemented
return self._parts_normcase <= other._parts_normcase

def __gt__(self, other):
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
if not isinstance(other, PurePath) or self.parser is not other.parser:
return NotImplemented
return self._parts_normcase > other._parts_normcase

def __ge__(self, other):
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
if not isinstance(other, PurePath) or self.parser is not other.parser:
return NotImplemented
return self._parts_normcase >= other._parts_normcase

Expand All @@ -247,10 +247,10 @@ def __str__(self):
@classmethod
def _format_parsed_parts(cls, drv, root, tail):
if drv or root:
return drv + root + cls.pathmod.sep.join(tail)
elif tail and cls.pathmod.splitdrive(tail[0])[0]:
return drv + root + cls.parser.sep.join(tail)
elif tail and cls.parser.splitdrive(tail[0])[0]:
tail = ['.'] + tail
return cls.pathmod.sep.join(tail)
return cls.parser.sep.join(tail)

def _from_parsed_parts(self, drv, root, tail):
path_str = self._format_parsed_parts(drv, root, tail)
Expand All @@ -265,11 +265,11 @@ def _from_parsed_parts(self, drv, root, tail):
def _parse_path(cls, path):
if not path:
return '', '', []
sep = cls.pathmod.sep
altsep = cls.pathmod.altsep
sep = cls.parser.sep
altsep = cls.parser.altsep
if altsep:
path = path.replace(altsep, sep)
drv, root, rel = cls.pathmod.splitroot(path)
drv, root, rel = cls.parser.splitroot(path)
if not root and drv.startswith(sep) and not drv.endswith(sep):
drv_parts = drv.split(sep)
if len(drv_parts) == 4 and drv_parts[2] not in '?.':
Expand All @@ -290,7 +290,7 @@ def _raw_path(self):
elif len(paths) == 1:
path = paths[0]
else:
path = self.pathmod.join(*paths)
path = self.parser.join(*paths)
return path

@property
Expand Down Expand Up @@ -360,8 +360,8 @@ def name(self):

def with_name(self, name):
"""Return a new path with the file name changed."""
m = self.pathmod
if not name or m.sep in name or (m.altsep and m.altsep in name) or name == '.':
p = self.parser
if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.':
raise ValueError(f"Invalid name {name!r}")
tail = self._tail.copy()
if not tail:
Expand Down Expand Up @@ -413,13 +413,13 @@ def is_relative_to(self, other, /, *_deprecated):
def is_absolute(self):
"""True if the path is absolute (has both a root and, if applicable,
a drive)."""
if self.pathmod is posixpath:
if self.parser is posixpath:
# Optimization: work with raw paths on POSIX.
for path in self._raw_paths:
if path.startswith('/'):
return True
return False
return self.pathmod.isabs(self)
return self.parser.isabs(self)

def is_reserved(self):
"""Return True if the path contains one of the special names reserved
Expand All @@ -428,8 +428,8 @@ def is_reserved(self):
"for removal in Python 3.15. Use os.path.isreserved() to "
"detect reserved paths on Windows.")
warnings.warn(msg, DeprecationWarning, stacklevel=2)
if self.pathmod is ntpath:
return self.pathmod.isreserved(self)
if self.parser is ntpath:
return self.parser.isreserved(self)
return False

def as_uri(self):
Expand Down Expand Up @@ -462,7 +462,7 @@ def _pattern_stack(self):
raise NotImplementedError("Non-relative patterns are unsupported")
elif not parts:
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
elif pattern[-1] in (self.pathmod.sep, self.pathmod.altsep):
elif pattern[-1] in (self.parser.sep, self.parser.altsep):
# GH-65238: pathlib doesn't preserve trailing slash. Add it back.
parts.append('')
parts.reverse()
Expand All @@ -487,7 +487,7 @@ class PurePosixPath(PurePath):
On a POSIX system, instantiating a PurePath should return this object.
However, you can also instantiate it directly on any system.
"""
pathmod = posixpath
parser = posixpath
__slots__ = ()


Expand All @@ -497,7 +497,7 @@ class PureWindowsPath(PurePath):
On a Windows system, instantiating a PurePath should return this object.
However, you can also instantiate it directly on any system.
"""
pathmod = ntpath
parser = ntpath
__slots__ = ()


Expand Down Expand Up @@ -607,7 +607,7 @@ def _make_child_relpath(self, name):
path_str = str(self)
tail = self._tail
if tail:
path_str = f'{path_str}{self.pathmod.sep}{name}'
path_str = f'{path_str}{self.parser.sep}{name}'
elif path_str != '.':
path_str = f'{path_str}{name}'
else:
Expand Down Expand Up @@ -675,7 +675,7 @@ def absolute(self):
drive, root, rel = os.path.splitroot(cwd)
if not rel:
return self._from_parsed_parts(drive, root, self._tail)
tail = rel.split(self.pathmod.sep)
tail = rel.split(self.parser.sep)
tail.extend(self._tail)
return self._from_parsed_parts(drive, root, tail)

Expand Down
50 changes: 25 additions & 25 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def _ignore_error(exception):


@functools.cache
def _is_case_sensitive(pathmod):
return pathmod.normcase('Aa') == 'Aa'
def _is_case_sensitive(parser):
return parser.normcase('Aa') == 'Aa'

#
# Globbing helpers
Expand Down Expand Up @@ -156,12 +156,12 @@ class UnsupportedOperation(NotImplementedError):
pass


class PathModuleBase:
"""Base class for path modules, which do low-level path manipulation.
class ParserBase:
"""Base class for path parsers, which do low-level path manipulation.

Path modules provide a subset of the os.path API, specifically those
Path parsers provide a subset of the os.path API, specifically those
functions needed to provide PurePathBase functionality. Each PurePathBase
subclass references its path module via a 'pathmod' class attribute.
subclass references its path parser via a 'parser' class attribute.

Every method in this base class raises an UnsupportedOperation exception.
"""
Expand Down Expand Up @@ -221,10 +221,10 @@ class PurePathBase:
# work from occurring when `resolve()` calls `stat()` or `readlink()`.
'_resolving',
)
pathmod = PathModuleBase()
parser = ParserBase()

def __init__(self, path, *paths):
self._raw_path = self.pathmod.join(path, *paths) if paths else path
self._raw_path = self.parser.join(path, *paths) if paths else path
if not isinstance(self._raw_path, str):
raise TypeError(
f"path should be a str, not {type(self._raw_path).__name__!r}")
Expand All @@ -245,17 +245,17 @@ def __str__(self):
def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
return str(self).replace(self.pathmod.sep, '/')
return str(self).replace(self.parser.sep, '/')

@property
def drive(self):
"""The drive prefix (letter or UNC path), if any."""
return self.pathmod.splitdrive(self.anchor)[0]
return self.parser.splitdrive(self.anchor)[0]

@property
def root(self):
"""The root of the path, if any."""
return self.pathmod.splitdrive(self.anchor)[1]
return self.parser.splitdrive(self.anchor)[1]

@property
def anchor(self):
Expand All @@ -265,7 +265,7 @@ def anchor(self):
@property
def name(self):
"""The final path component, if any."""
return self.pathmod.split(self._raw_path)[1]
return self.parser.split(self._raw_path)[1]

@property
def suffix(self):
Expand Down Expand Up @@ -306,7 +306,7 @@ def stem(self):

def with_name(self, name):
"""Return a new path with the file name changed."""
split = self.pathmod.split
split = self.parser.split
if split(name)[0]:
raise ValueError(f"Invalid name {name!r}")
return self.with_segments(split(self._raw_path)[0], name)
Expand Down Expand Up @@ -419,7 +419,7 @@ def _stack(self):
uppermost parent of the path (equivalent to path.parents[-1]), and
*parts* is a reversed list of parts following the anchor.
"""
split = self.pathmod.split
split = self.parser.split
path = self._raw_path
parent, name = split(path)
names = []
Expand All @@ -433,7 +433,7 @@ def _stack(self):
def parent(self):
"""The logical parent of the path."""
path = self._raw_path
parent = self.pathmod.split(path)[0]
parent = self.parser.split(path)[0]
if path != parent:
parent = self.with_segments(parent)
parent._resolving = self._resolving
Expand All @@ -443,7 +443,7 @@ def parent(self):
@property
def parents(self):
"""A sequence of this path's logical parents."""
split = self.pathmod.split
split = self.parser.split
path = self._raw_path
parent = split(path)[0]
parents = []
Expand All @@ -456,7 +456,7 @@ def parents(self):
def is_absolute(self):
"""True if the path is absolute (has both a root and, if applicable,
a drive)."""
return self.pathmod.isabs(self._raw_path)
return self.parser.isabs(self._raw_path)

@property
def _pattern_stack(self):
Expand All @@ -481,8 +481,8 @@ def match(self, path_pattern, *, case_sensitive=None):
if not isinstance(path_pattern, PurePathBase):
path_pattern = self.with_segments(path_pattern)
if case_sensitive is None:
case_sensitive = _is_case_sensitive(self.pathmod)
sep = path_pattern.pathmod.sep
case_sensitive = _is_case_sensitive(self.parser)
sep = path_pattern.parser.sep
path_parts = self.parts[::-1]
pattern_parts = path_pattern.parts[::-1]
if not pattern_parts:
Expand All @@ -505,8 +505,8 @@ def full_match(self, pattern, *, case_sensitive=None):
if not isinstance(pattern, PurePathBase):
pattern = self.with_segments(pattern)
if case_sensitive is None:
case_sensitive = _is_case_sensitive(self.pathmod)
match = _compile_pattern(pattern._pattern_str, pattern.pathmod.sep, case_sensitive)
case_sensitive = _is_case_sensitive(self.parser)
match = _compile_pattern(pattern._pattern_str, pattern.parser.sep, case_sensitive)
return match(self._pattern_str) is not None


Expand Down Expand Up @@ -797,12 +797,12 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=True):
pattern = self.with_segments(pattern)
if case_sensitive is None:
# TODO: evaluate case-sensitivity of each directory in _select_children().
case_sensitive = _is_case_sensitive(self.pathmod)
case_sensitive = _is_case_sensitive(self.parser)

stack = pattern._pattern_stack
specials = ('', '.', '..')
deduplicate_paths = False
sep = self.pathmod.sep
sep = self.parser.sep
paths = iter([self] if self.is_dir() else [])
while stack:
part = stack.pop()
Expand Down Expand Up @@ -973,7 +973,7 @@ def resolve(self, strict=False):
continue
path_tail.append(part)
if querying and part != '..':
path = self.with_segments(path_root + self.pathmod.sep.join(path_tail))
path = self.with_segments(path_root + self.parser.sep.join(path_tail))
path._resolving = True
try:
st = path.stat(follow_symlinks=False)
Expand Down Expand Up @@ -1002,7 +1002,7 @@ def resolve(self, strict=False):
raise
else:
querying = False
return self.with_segments(path_root + self.pathmod.sep.join(path_tail))
return self.with_segments(path_root + self.parser.sep.join(path_tail))

def symlink_to(self, target, target_is_directory=False):
"""
Expand Down
Loading