Skip to content

Commit

Permalink
GH-114575: Rename PurePath.pathmod to PurePath.parser (#116513)
Browse files Browse the repository at this point in the history
And rename the private base class from `PathModuleBase` to `ParserBase`.
  • Loading branch information
barneygale authored Mar 31, 2024
1 parent bfc57d4 commit 752e183
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 119 deletions.
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 @@ -524,6 +524,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

0 comments on commit 752e183

Please sign in to comment.