Skip to content

Commit 752e183

Browse files
authored
GH-114575: Rename PurePath.pathmod to PurePath.parser (#116513)
And rename the private base class from `PathModuleBase` to `ParserBase`.
1 parent bfc57d4 commit 752e183

File tree

6 files changed

+123
-119
lines changed

6 files changed

+123
-119
lines changed

Doc/library/pathlib.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,10 @@ Methods and properties
303303

304304
Pure paths provide the following methods and properties:
305305

306-
.. attribute:: PurePath.pathmod
306+
.. attribute:: PurePath.parser
307307

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

311311
.. versionadded:: 3.13
312312

Doc/whatsnew/3.13.rst

+4
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,10 @@ pathlib
524524
shell-style wildcards, including the recursive wildcard "``**``".
525525
(Contributed by Barney Gale in :gh:`73435`.)
526526

527+
* Add :attr:`pathlib.PurePath.parser` class attribute that stores the
528+
implementation of :mod:`os.path` used for low-level path parsing and
529+
joining: either ``posixpath`` or ``ntpath``.
530+
527531
* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`,
528532
:meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`,
529533
:meth:`~pathlib.Path.is_dir`, :meth:`~pathlib.Path.owner`,

Lib/pathlib/__init__.py

+27-27
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class PurePath(_abc.PurePathBase):
110110
# path. It's set when `__hash__()` is called for the first time.
111111
'_hash',
112112
)
113-
pathmod = os.path
113+
parser = os.path
114114

115115
def __new__(cls, *args, **kwargs):
116116
"""Construct a PurePath from one or several strings and or existing
@@ -126,7 +126,7 @@ def __init__(self, *args):
126126
paths = []
127127
for arg in args:
128128
if isinstance(arg, PurePath):
129-
if arg.pathmod is ntpath and self.pathmod is posixpath:
129+
if arg.parser is ntpath and self.parser is posixpath:
130130
# GH-103631: Convert separators for backwards compatibility.
131131
paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
132132
else:
@@ -187,7 +187,7 @@ def _str_normcase(self):
187187
try:
188188
return self._str_normcase_cached
189189
except AttributeError:
190-
if _abc._is_case_sensitive(self.pathmod):
190+
if _abc._is_case_sensitive(self.parser):
191191
self._str_normcase_cached = str(self)
192192
else:
193193
self._str_normcase_cached = str(self).lower()
@@ -203,34 +203,34 @@ def __hash__(self):
203203
def __eq__(self, other):
204204
if not isinstance(other, PurePath):
205205
return NotImplemented
206-
return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
206+
return self._str_normcase == other._str_normcase and self.parser is other.parser
207207

208208
@property
209209
def _parts_normcase(self):
210210
# Cached parts with normalized case, for comparisons.
211211
try:
212212
return self._parts_normcase_cached
213213
except AttributeError:
214-
self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
214+
self._parts_normcase_cached = self._str_normcase.split(self.parser.sep)
215215
return self._parts_normcase_cached
216216

217217
def __lt__(self, other):
218-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
218+
if not isinstance(other, PurePath) or self.parser is not other.parser:
219219
return NotImplemented
220220
return self._parts_normcase < other._parts_normcase
221221

222222
def __le__(self, other):
223-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
223+
if not isinstance(other, PurePath) or self.parser is not other.parser:
224224
return NotImplemented
225225
return self._parts_normcase <= other._parts_normcase
226226

227227
def __gt__(self, other):
228-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
228+
if not isinstance(other, PurePath) or self.parser is not other.parser:
229229
return NotImplemented
230230
return self._parts_normcase > other._parts_normcase
231231

232232
def __ge__(self, other):
233-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
233+
if not isinstance(other, PurePath) or self.parser is not other.parser:
234234
return NotImplemented
235235
return self._parts_normcase >= other._parts_normcase
236236

@@ -247,10 +247,10 @@ def __str__(self):
247247
@classmethod
248248
def _format_parsed_parts(cls, drv, root, tail):
249249
if drv or root:
250-
return drv + root + cls.pathmod.sep.join(tail)
251-
elif tail and cls.pathmod.splitdrive(tail[0])[0]:
250+
return drv + root + cls.parser.sep.join(tail)
251+
elif tail and cls.parser.splitdrive(tail[0])[0]:
252252
tail = ['.'] + tail
253-
return cls.pathmod.sep.join(tail)
253+
return cls.parser.sep.join(tail)
254254

255255
def _from_parsed_parts(self, drv, root, tail):
256256
path_str = self._format_parsed_parts(drv, root, tail)
@@ -265,11 +265,11 @@ def _from_parsed_parts(self, drv, root, tail):
265265
def _parse_path(cls, path):
266266
if not path:
267267
return '', '', []
268-
sep = cls.pathmod.sep
269-
altsep = cls.pathmod.altsep
268+
sep = cls.parser.sep
269+
altsep = cls.parser.altsep
270270
if altsep:
271271
path = path.replace(altsep, sep)
272-
drv, root, rel = cls.pathmod.splitroot(path)
272+
drv, root, rel = cls.parser.splitroot(path)
273273
if not root and drv.startswith(sep) and not drv.endswith(sep):
274274
drv_parts = drv.split(sep)
275275
if len(drv_parts) == 4 and drv_parts[2] not in '?.':
@@ -290,7 +290,7 @@ def _raw_path(self):
290290
elif len(paths) == 1:
291291
path = paths[0]
292292
else:
293-
path = self.pathmod.join(*paths)
293+
path = self.parser.join(*paths)
294294
return path
295295

296296
@property
@@ -360,8 +360,8 @@ def name(self):
360360

361361
def with_name(self, name):
362362
"""Return a new path with the file name changed."""
363-
m = self.pathmod
364-
if not name or m.sep in name or (m.altsep and m.altsep in name) or name == '.':
363+
p = self.parser
364+
if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.':
365365
raise ValueError(f"Invalid name {name!r}")
366366
tail = self._tail.copy()
367367
if not tail:
@@ -413,13 +413,13 @@ def is_relative_to(self, other, /, *_deprecated):
413413
def is_absolute(self):
414414
"""True if the path is absolute (has both a root and, if applicable,
415415
a drive)."""
416-
if self.pathmod is posixpath:
416+
if self.parser is posixpath:
417417
# Optimization: work with raw paths on POSIX.
418418
for path in self._raw_paths:
419419
if path.startswith('/'):
420420
return True
421421
return False
422-
return self.pathmod.isabs(self)
422+
return self.parser.isabs(self)
423423

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

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

493493

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

503503

@@ -607,7 +607,7 @@ def _make_child_relpath(self, name):
607607
path_str = str(self)
608608
tail = self._tail
609609
if tail:
610-
path_str = f'{path_str}{self.pathmod.sep}{name}'
610+
path_str = f'{path_str}{self.parser.sep}{name}'
611611
elif path_str != '.':
612612
path_str = f'{path_str}{name}'
613613
else:
@@ -675,7 +675,7 @@ def absolute(self):
675675
drive, root, rel = os.path.splitroot(cwd)
676676
if not rel:
677677
return self._from_parsed_parts(drive, root, self._tail)
678-
tail = rel.split(self.pathmod.sep)
678+
tail = rel.split(self.parser.sep)
679679
tail.extend(self._tail)
680680
return self._from_parsed_parts(drive, root, tail)
681681

Lib/pathlib/_abc.py

+25-25
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def _ignore_error(exception):
3737

3838

3939
@functools.cache
40-
def _is_case_sensitive(pathmod):
41-
return pathmod.normcase('Aa') == 'Aa'
40+
def _is_case_sensitive(parser):
41+
return parser.normcase('Aa') == 'Aa'
4242

4343
#
4444
# Globbing helpers
@@ -156,12 +156,12 @@ class UnsupportedOperation(NotImplementedError):
156156
pass
157157

158158

159-
class PathModuleBase:
160-
"""Base class for path modules, which do low-level path manipulation.
159+
class ParserBase:
160+
"""Base class for path parsers, which do low-level path manipulation.
161161
162-
Path modules provide a subset of the os.path API, specifically those
162+
Path parsers provide a subset of the os.path API, specifically those
163163
functions needed to provide PurePathBase functionality. Each PurePathBase
164-
subclass references its path module via a 'pathmod' class attribute.
164+
subclass references its path parser via a 'parser' class attribute.
165165
166166
Every method in this base class raises an UnsupportedOperation exception.
167167
"""
@@ -221,10 +221,10 @@ class PurePathBase:
221221
# work from occurring when `resolve()` calls `stat()` or `readlink()`.
222222
'_resolving',
223223
)
224-
pathmod = PathModuleBase()
224+
parser = ParserBase()
225225

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

250250
@property
251251
def drive(self):
252252
"""The drive prefix (letter or UNC path), if any."""
253-
return self.pathmod.splitdrive(self.anchor)[0]
253+
return self.parser.splitdrive(self.anchor)[0]
254254

255255
@property
256256
def root(self):
257257
"""The root of the path, if any."""
258-
return self.pathmod.splitdrive(self.anchor)[1]
258+
return self.parser.splitdrive(self.anchor)[1]
259259

260260
@property
261261
def anchor(self):
@@ -265,7 +265,7 @@ def anchor(self):
265265
@property
266266
def name(self):
267267
"""The final path component, if any."""
268-
return self.pathmod.split(self._raw_path)[1]
268+
return self.parser.split(self._raw_path)[1]
269269

270270
@property
271271
def suffix(self):
@@ -306,7 +306,7 @@ def stem(self):
306306

307307
def with_name(self, name):
308308
"""Return a new path with the file name changed."""
309-
split = self.pathmod.split
309+
split = self.parser.split
310310
if split(name)[0]:
311311
raise ValueError(f"Invalid name {name!r}")
312312
return self.with_segments(split(self._raw_path)[0], name)
@@ -419,7 +419,7 @@ def _stack(self):
419419
uppermost parent of the path (equivalent to path.parents[-1]), and
420420
*parts* is a reversed list of parts following the anchor.
421421
"""
422-
split = self.pathmod.split
422+
split = self.parser.split
423423
path = self._raw_path
424424
parent, name = split(path)
425425
names = []
@@ -433,7 +433,7 @@ def _stack(self):
433433
def parent(self):
434434
"""The logical parent of the path."""
435435
path = self._raw_path
436-
parent = self.pathmod.split(path)[0]
436+
parent = self.parser.split(path)[0]
437437
if path != parent:
438438
parent = self.with_segments(parent)
439439
parent._resolving = self._resolving
@@ -443,7 +443,7 @@ def parent(self):
443443
@property
444444
def parents(self):
445445
"""A sequence of this path's logical parents."""
446-
split = self.pathmod.split
446+
split = self.parser.split
447447
path = self._raw_path
448448
parent = split(path)[0]
449449
parents = []
@@ -456,7 +456,7 @@ def parents(self):
456456
def is_absolute(self):
457457
"""True if the path is absolute (has both a root and, if applicable,
458458
a drive)."""
459-
return self.pathmod.isabs(self._raw_path)
459+
return self.parser.isabs(self._raw_path)
460460

461461
@property
462462
def _pattern_stack(self):
@@ -481,8 +481,8 @@ def match(self, path_pattern, *, case_sensitive=None):
481481
if not isinstance(path_pattern, PurePathBase):
482482
path_pattern = self.with_segments(path_pattern)
483483
if case_sensitive is None:
484-
case_sensitive = _is_case_sensitive(self.pathmod)
485-
sep = path_pattern.pathmod.sep
484+
case_sensitive = _is_case_sensitive(self.parser)
485+
sep = path_pattern.parser.sep
486486
path_parts = self.parts[::-1]
487487
pattern_parts = path_pattern.parts[::-1]
488488
if not pattern_parts:
@@ -505,8 +505,8 @@ def full_match(self, pattern, *, case_sensitive=None):
505505
if not isinstance(pattern, PurePathBase):
506506
pattern = self.with_segments(pattern)
507507
if case_sensitive is None:
508-
case_sensitive = _is_case_sensitive(self.pathmod)
509-
match = _compile_pattern(pattern._pattern_str, pattern.pathmod.sep, case_sensitive)
508+
case_sensitive = _is_case_sensitive(self.parser)
509+
match = _compile_pattern(pattern._pattern_str, pattern.parser.sep, case_sensitive)
510510
return match(self._pattern_str) is not None
511511

512512

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

802802
stack = pattern._pattern_stack
803803
specials = ('', '.', '..')
804804
deduplicate_paths = False
805-
sep = self.pathmod.sep
805+
sep = self.parser.sep
806806
paths = iter([self] if self.is_dir() else [])
807807
while stack:
808808
part = stack.pop()
@@ -973,7 +973,7 @@ def resolve(self, strict=False):
973973
continue
974974
path_tail.append(part)
975975
if querying and part != '..':
976-
path = self.with_segments(path_root + self.pathmod.sep.join(path_tail))
976+
path = self.with_segments(path_root + self.parser.sep.join(path_tail))
977977
path._resolving = True
978978
try:
979979
st = path.stat(follow_symlinks=False)
@@ -1002,7 +1002,7 @@ def resolve(self, strict=False):
10021002
raise
10031003
else:
10041004
querying = False
1005-
return self.with_segments(path_root + self.pathmod.sep.join(path_tail))
1005+
return self.with_segments(path_root + self.parser.sep.join(path_tail))
10061006

10071007
def symlink_to(self, target, target_is_directory=False):
10081008
"""

0 commit comments

Comments
 (0)