1
1
import functools
2
2
import ntpath
3
3
import posixpath
4
- import sys
5
4
from errno import ENOENT , ENOTDIR , EBADF , ELOOP , EINVAL
6
5
from stat import S_ISDIR , S_ISLNK , S_ISREG , S_ISSOCK , S_ISBLK , S_ISCHR , S_ISFIFO
7
6
@@ -82,7 +81,7 @@ def _select_children(parent_paths, dir_only, follow_symlinks, match):
82
81
except OSError :
83
82
continue
84
83
if match (entry .name ):
85
- yield parent_path ._make_child_entry (entry )
84
+ yield parent_path ._make_child_entry (entry , dir_only )
86
85
87
86
88
87
def _select_recursive (parent_paths , dir_only , follow_symlinks ):
@@ -105,7 +104,7 @@ def _select_recursive(parent_paths, dir_only, follow_symlinks):
105
104
for entry in entries :
106
105
try :
107
106
if entry .is_dir (follow_symlinks = follow_symlinks ):
108
- paths .append (path ._make_child_entry (entry ))
107
+ paths .append (path ._make_child_entry (entry , dir_only ))
109
108
continue
110
109
except OSError :
111
110
pass
@@ -147,20 +146,6 @@ class PurePathBase:
147
146
# in the `__init__()` method.
148
147
'_raw_paths' ,
149
148
150
- # The `_drv`, `_root` and `_tail_cached` slots store parsed and
151
- # normalized parts of the path. They are set when any of the `drive`,
152
- # `root` or `_tail` properties are accessed for the first time. The
153
- # three-part division corresponds to the result of
154
- # `os.path.splitroot()`, except that the tail is further split on path
155
- # separators (i.e. it is a list of strings), and that the root and
156
- # tail are normalized.
157
- '_drv' , '_root' , '_tail_cached' ,
158
-
159
- # The `_str` slot stores the string representation of the path,
160
- # computed from the drive, root and tail when `__str__()` is called
161
- # for the first time. It's used to implement `_str_normcase`
162
- '_str' ,
163
-
164
149
# The '_resolving' slot stores a boolean indicating whether the path
165
150
# is being processed by `PathBase.resolve()`. This prevents duplicate
166
151
# work from occurring when `resolve()` calls `stat()` or `readlink()`.
@@ -179,65 +164,16 @@ def with_segments(self, *pathsegments):
179
164
"""
180
165
return type (self )(* pathsegments )
181
166
182
- @classmethod
183
- def _parse_path (cls , path ):
184
- if not path :
185
- return '' , '' , []
186
- sep = cls .pathmod .sep
187
- altsep = cls .pathmod .altsep
188
- if altsep :
189
- path = path .replace (altsep , sep )
190
- drv , root , rel = cls .pathmod .splitroot (path )
191
- if not root and drv .startswith (sep ) and not drv .endswith (sep ):
192
- drv_parts = drv .split (sep )
193
- if len (drv_parts ) == 4 and drv_parts [2 ] not in '?.' :
194
- # e.g. //server/share
195
- root = sep
196
- elif len (drv_parts ) == 6 :
197
- # e.g. //?/unc/server/share
198
- root = sep
199
- parsed = [sys .intern (str (x )) for x in rel .split (sep ) if x and x != '.' ]
200
- return drv , root , parsed
201
-
202
- def _load_parts (self ):
203
- paths = self ._raw_paths
204
- if len (paths ) == 0 :
205
- path = ''
206
- elif len (paths ) == 1 :
207
- path = paths [0 ]
208
- else :
209
- path = self .pathmod .join (* paths )
210
- drv , root , tail = self ._parse_path (path )
211
- self ._drv = drv
212
- self ._root = root
213
- self ._tail_cached = tail
214
-
215
- def _from_parsed_parts (self , drv , root , tail ):
216
- path_str = self ._format_parsed_parts (drv , root , tail )
217
- path = self .with_segments (path_str )
218
- path ._str = path_str or '.'
219
- path ._drv = drv
220
- path ._root = root
221
- path ._tail_cached = tail
222
- return path
223
-
224
- @classmethod
225
- def _format_parsed_parts (cls , drv , root , tail ):
226
- if drv or root :
227
- return drv + root + cls .pathmod .sep .join (tail )
228
- elif tail and cls .pathmod .splitdrive (tail [0 ])[0 ]:
229
- tail = ['.' ] + tail
230
- return cls .pathmod .sep .join (tail )
231
-
232
167
def __str__ (self ):
233
168
"""Return the string representation of the path, suitable for
234
169
passing to system calls."""
235
- try :
236
- return self ._str
237
- except AttributeError :
238
- self ._str = self ._format_parsed_parts (self .drive , self .root ,
239
- self ._tail ) or '.'
240
- return self ._str
170
+ paths = self ._raw_paths
171
+ if len (paths ) == 1 :
172
+ return paths [0 ]
173
+ elif paths :
174
+ return self .pathmod .join (* paths )
175
+ else :
176
+ return ''
241
177
242
178
def as_posix (self ):
243
179
"""Return the string representation of the path with forward (/)
@@ -247,42 +183,23 @@ def as_posix(self):
247
183
@property
248
184
def drive (self ):
249
185
"""The drive prefix (letter or UNC path), if any."""
250
- try :
251
- return self ._drv
252
- except AttributeError :
253
- self ._load_parts ()
254
- return self ._drv
186
+ return self .pathmod .splitdrive (str (self ))[0 ]
255
187
256
188
@property
257
189
def root (self ):
258
190
"""The root of the path, if any."""
259
- try :
260
- return self ._root
261
- except AttributeError :
262
- self ._load_parts ()
263
- return self ._root
264
-
265
- @property
266
- def _tail (self ):
267
- try :
268
- return self ._tail_cached
269
- except AttributeError :
270
- self ._load_parts ()
271
- return self ._tail_cached
191
+ return self .pathmod .splitroot (str (self ))[1 ]
272
192
273
193
@property
274
194
def anchor (self ):
275
195
"""The concatenation of the drive and root, or ''."""
276
- anchor = self . drive + self .root
277
- return anchor
196
+ drive , root , _ = self .pathmod . splitroot ( str ( self ))
197
+ return drive + root
278
198
279
199
@property
280
200
def name (self ):
281
201
"""The final path component, if any."""
282
- path_str = str (self )
283
- if not path_str or path_str == '.' :
284
- return ''
285
- return self .pathmod .basename (path_str )
202
+ return self .pathmod .basename (str (self ))
286
203
287
204
@property
288
205
def suffix (self ):
@@ -323,13 +240,10 @@ def stem(self):
323
240
324
241
def with_name (self , name ):
325
242
"""Return a new path with the file name changed."""
326
- m = self .pathmod
327
- if not name or m . sep in name or ( m . altsep and m . altsep in name ) or name == '.' :
243
+ dirname = self .pathmod . dirname
244
+ if dirname ( name ):
328
245
raise ValueError (f"Invalid name { name !r} " )
329
- parent , old_name = m .split (str (self ))
330
- if not old_name or old_name == '.' :
331
- raise ValueError (f"{ self !r} has an empty name" )
332
- return self .with_segments (parent , name )
246
+ return self .with_segments (dirname (str (self )), name )
333
247
334
248
def with_stem (self , stem ):
335
249
"""Return a new path with the stem changed."""
@@ -480,7 +394,7 @@ def is_absolute(self):
480
394
def is_reserved (self ):
481
395
"""Return True if the path contains one of the special names reserved
482
396
by the system, if any."""
483
- if self .pathmod is posixpath or not self ._tail :
397
+ if self .pathmod is posixpath or not self .name :
484
398
return False
485
399
486
400
# NOTE: the rules for reserved names seem somewhat complicated
@@ -490,7 +404,7 @@ def is_reserved(self):
490
404
if self .drive .startswith ('\\ \\ ' ):
491
405
# UNC paths are never reserved.
492
406
return False
493
- name = self ._tail [ - 1 ] .partition ('.' )[0 ].partition (':' )[0 ].rstrip (' ' )
407
+ name = self .name .partition ('.' )[0 ].partition (':' )[0 ].rstrip (' ' )
494
408
return name .upper () in _WIN_RESERVED_NAMES
495
409
496
410
def match (self , path_pattern , * , case_sensitive = None ):
@@ -503,9 +417,9 @@ def match(self, path_pattern, *, case_sensitive=None):
503
417
case_sensitive = _is_case_sensitive (self .pathmod )
504
418
sep = path_pattern .pathmod .sep
505
419
pattern_str = str (path_pattern )
506
- if path_pattern .drive or path_pattern . root :
420
+ if path_pattern .anchor :
507
421
pass
508
- elif path_pattern ._tail :
422
+ elif path_pattern .parts :
509
423
pattern_str = f'**{ sep } { pattern_str } '
510
424
else :
511
425
raise ValueError ("empty pattern" )
@@ -780,8 +694,10 @@ def _scandir(self):
780
694
from contextlib import nullcontext
781
695
return nullcontext (self .iterdir ())
782
696
783
- def _make_child_entry (self , entry ):
697
+ def _make_child_entry (self , entry , is_dir = False ):
784
698
# Transform an entry yielded from _scandir() into a path object.
699
+ if is_dir :
700
+ return entry .joinpath ('' )
785
701
return entry
786
702
787
703
def _make_child_relpath (self , name ):
@@ -792,13 +708,13 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None):
792
708
kind, including directories) matching the given relative pattern.
793
709
"""
794
710
path_pattern = self .with_segments (pattern )
795
- if path_pattern .drive or path_pattern . root :
711
+ if path_pattern .anchor :
796
712
raise NotImplementedError ("Non-relative patterns are unsupported" )
797
- elif not path_pattern ._tail :
713
+ elif not path_pattern .parts :
798
714
raise ValueError ("Unacceptable pattern: {!r}" .format (pattern ))
799
715
800
- pattern_parts = path_pattern ._tail . copy ( )
801
- if pattern [ - 1 ] in ( self .pathmod .sep , self . pathmod . altsep ):
716
+ pattern_parts = list ( path_pattern .parts )
717
+ if not self .pathmod .basename ( pattern ):
802
718
# GH-65238: pathlib doesn't preserve trailing slash. Add it back.
803
719
pattern_parts .append ('' )
804
720
@@ -816,7 +732,7 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None):
816
732
filter_paths = follow_symlinks is not None and '..' not in pattern_parts
817
733
deduplicate_paths = False
818
734
sep = self .pathmod .sep
819
- paths = iter ([self ] if self .is_dir () else [])
735
+ paths = iter ([self . joinpath ( '' ) ] if self .is_dir () else [])
820
736
part_idx = 0
821
737
while part_idx < len (pattern_parts ):
822
738
part = pattern_parts [part_idx ]
0 commit comments