Tachyon /Users/simon/.local/share/uv/python/cpython-3.15.0-macos-aarch64-none/lib/python3.15/pathlib/__init__.py
885
Self Samples
969
Cumulative
30
Lines Hit
3.33%
% of Total
332
Max Self
341
Max Total
Intensity:
Cold Hot
Self Time
Total Time
Show All
Hot Only
Heat
Specialization
Line
Self
Total
Code
1
"""Object-oriented filesystem paths.
2
3
This module provides classes to represent abstract paths and concrete
4
paths with operations that have semantics appropriate for different
5
operating systems.
6
"""
7
8
import io
9
import ntpath
10
import operator
11
import os
12
import posixpath
13
import sys
14
from errno import *
15
from itertools import chain
16
from stat import (
17
S_IMODE, S_ISDIR, S_ISREG, S_ISLNK, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO,
18
)
19
from _collections_abc import Sequence
20
lazy import shutil
21
lazy from glob import _StringGlobber, _no_recurse_symlinks
22
23
try:
24
import pwd
25
except ImportError:
26
pwd = None
27
try:
28
import grp
29
except ImportError:
30
grp = None
31
32
from pathlib._os import (
33
vfsopen, vfspath,
34
ensure_different_files, ensure_distinct_paths,
35
copyfile2, copyfileobj,
36
)
37
38
39
__all__ = [
40
"UnsupportedOperation",
41
"PurePath", "PurePosixPath", "PureWindowsPath",
42
"Path", "PosixPath", "WindowsPath",
43
]
44
45
46
class UnsupportedOperation(NotImplementedError):
47
"""An exception that is raised when an unsupported operation is attempted.
48
"""
49
pass
50
51
52
class _PathParents(Sequence):
53
"""This object provides sequence-like access to the logical ancestors
54
of a path. Don't try to construct it yourself."""
55
__slots__ = ('_path', '_drv', '_root', '_tail')
56
57
def __init__(self, path):
58
self._path = path
59
self._drv = path.drive
60
self._root = path.root
61
self._tail = path._tail
62
63
def __len__(self):
64
return len(self._tail)
65
66
def __getitem__(self, idx):
67
if isinstance(idx, slice):
68
return tuple(self[i] for i in range(*idx.indices(len(self))))
69
70
if idx >= len(self) or idx < -len(self):
71
raise IndexError(idx)
72
if idx < 0:
73
idx += len(self)
74
return self._path._from_parsed_parts(self._drv, self._root,
75
self._tail[:-idx - 1])
76
77
def __repr__(self):
78
return "<{}.parents>".format(type(self._path).__name__)
79
80
81
class PurePath:
82
"""Base class for manipulating paths without I/O.
83
84
PurePath represents a filesystem path and offers operations which
85
don't imply any actual filesystem I/O. Depending on your system,
86
instantiating a PurePath will return either a PurePosixPath or a
87
PureWindowsPath object. You can also instantiate either of these classes
88
directly, regardless of your system.
89
"""
90
91
__slots__ = (
92
# The `_raw_paths` slot stores unjoined string paths. This is set in
93
# the `__init__()` method.
94
'_raw_paths',
95
96
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
97
# normalized parts of the path. They are set when any of the `drive`,
98
# `root` or `_tail` properties are accessed for the first time. The
99
# three-part division corresponds to the result of
100
# `os.path.splitroot()`, except that the tail is further split on path
101
# separators (i.e. it is a list of strings), and that the root and
102
# tail are normalized.
103
'_drv', '_root', '_tail_cached',
104
105
# The `_str` slot stores the string representation of the path,
106
# computed from the drive, root and tail when `__str__()` is called
107
# for the first time. It's used to implement `_str_normcase`
108
'_str',
109
110
# The `_str_normcase_cached` slot stores the string path with
111
# normalized case. It is set when the `_str_normcase` property is
112
# accessed for the first time. It's used to implement `__eq__()`
113
# `__hash__()`, and `_parts_normcase`
114
'_str_normcase_cached',
115
116
# The `_parts_normcase_cached` slot stores the case-normalized
117
# string path after splitting on path separators. It's set when the
118
# `_parts_normcase` property is accessed for the first time. It's used
119
# to implement comparison methods like `__lt__()`.
120
'_parts_normcase_cached',
121
122
# The `_hash` slot stores the hash of the case-normalized string
123
# path. It's set when `__hash__()` is called for the first time.
124
'_hash',
125
)
126
parser = os.path
127
128
def __new__(cls, *args, **kwargs):
129
"""Construct a PurePath from one or several strings and or existing
130
PurePath objects. The strings and path objects are combined so as
131
to yield a canonicalized path, which is incorporated into the
132
new PurePath object.
133
"""
134
if cls is PurePath:
135
cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
136
return object.__new__(cls)
137
138
def __init__(self, *args):
139
paths = []
140
for arg in args:
141
if isinstance(arg, PurePath):
142
if arg.parser is not self.parser:
143
# GH-103631: Convert separators for backwards compatibility.
144
paths.append(arg.as_posix())
145
else:
146
paths.extend(arg._raw_paths)
147
else:
148
try:
149
path = os.fspath(arg)
150
except TypeError:
151
path = arg
152
if not isinstance(path, str):
153
raise TypeError(
154
"argument should be a str or an os.PathLike "
155
"object where __fspath__ returns a str, "
156
f"not {type(path).__name__!r}")
157
paths.append(path)
158
self._raw_paths = paths
159
160
def with_segments(self, *pathsegments):
161
"""Construct a new path object from any number of path-like objects.
162
Subclasses may override this method to customize how new path objects
163
are created from methods like `iterdir()`.
164
"""
165
3
3
return type(self)(*pathsegments)
166
167
def joinpath(self, *pathsegments):
168
"""Combine this path with one or several arguments, and return a
169
new path representing either a subpath (if all arguments are relative
170
paths) or a totally different path (if one of the arguments is
171
anchored).
172
"""
173
return self.with_segments(self, *pathsegments)
174
175
def __truediv__(self, key):
176
try:
177
return self.with_segments(self, key)
178
except TypeError:
179
return NotImplemented
180
181
def __rtruediv__(self, key):
182
try:
183
return self.with_segments(key, self)
184
except TypeError:
185
return NotImplemented
186
187
def __reduce__(self):
188
return self.__class__, tuple(self._raw_paths)
189
190
def __repr__(self):
191
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
192
193
def __bytes__(self):
194
"""Return the bytes representation of the path. This is only
195
recommended to use under Unix."""
196
return os.fsencode(self)
197
198
@property
199
def _str_normcase(self):
200
# String with normalized case, for hashing and equality checks
201
try:
202
1
1
return self._str_normcase_cached
203
except AttributeError:
204
if self.parser is posixpath:
205
self._str_normcase_cached = str(self)
206
else:
207
self._str_normcase_cached = str(self).lower()
208
return self._str_normcase_cached
209
210
def __hash__(self):
211
try:
212
return self._hash
213
except AttributeError:
214
self._hash = hash(self._str_normcase)
215
return self._hash
216
217
def __eq__(self, other):
218
if not isinstance(other, PurePath):
219
return NotImplemented
220
return self._str_normcase == other._str_normcase and self.parser is other.parser
221
222
@property
223
def _parts_normcase(self):
224
# Cached parts with normalized case, for comparisons.
225
try:
226
return self._parts_normcase_cached
227
except AttributeError:
228
self._parts_normcase_cached = self._str_normcase.split(self.parser.sep)
229
return self._parts_normcase_cached
230
231
def __lt__(self, other):
232
if not isinstance(other, PurePath) or self.parser is not other.parser:
233
return NotImplemented
234
return self._parts_normcase < other._parts_normcase
235
236
def __le__(self, other):
237
if not isinstance(other, PurePath) or self.parser is not other.parser:
238
return NotImplemented
239
return self._parts_normcase <= other._parts_normcase
240
241
def __gt__(self, other):
242
if not isinstance(other, PurePath) or self.parser is not other.parser:
243
return NotImplemented
244
return self._parts_normcase > other._parts_normcase
245
246
def __ge__(self, other):
247
if not isinstance(other, PurePath) or self.parser is not other.parser:
248
return NotImplemented
249
return self._parts_normcase >= other._parts_normcase
250
251
def __str__(self):
252
"""Return the string representation of the path, suitable for
253
passing to system calls."""
254
try:
255
return self._str
256
except AttributeError:
257
3
3
self._str = self._format_parsed_parts(self.drive, self.root,
258
self._tail) or '.'
259
return self._str
260
261
__fspath__ = __str__
262
__vfspath__ = __str__
263
264
@classmethod
265
def _format_parsed_parts(cls, drv, root, tail):
266
if drv or root:
267
return drv + root + cls.parser.sep.join(tail)
268
elif tail and cls.parser.splitdrive(tail[0])[0]:
269
tail = ['.'] + tail
270
return cls.parser.sep.join(tail)
271
272
def _from_parsed_parts(self, drv, root, tail):
273
2
2
path = self._from_parsed_string(self._format_parsed_parts(drv, root, tail))
274
path._drv = drv
275
path._root = root
276
path._tail_cached = tail
277
return path
278
279
def _from_parsed_string(self, path_str):
280
path = self.with_segments(path_str)
281
path._str = path_str or '.'
282
return path
283
284
@classmethod
285
def _parse_path(cls, path):
286
if not path:
287
return '', '', []
288
sep = cls.parser.sep
289
altsep = cls.parser.altsep
290
if altsep:
291
path = path.replace(altsep, sep)
292
drv, root, rel = cls.parser.splitroot(path)
293
if not root and drv.startswith(sep) and not drv.endswith(sep):
294
drv_parts = drv.split(sep)
295
if len(drv_parts) == 4 and drv_parts[2] not in '?.':
296
# e.g. //server/share
297
root = sep
298
elif len(drv_parts) == 6:
299
# e.g. //?/unc/server/share
300
root = sep
301
1
1
return drv, root, [x for x in rel.split(sep) if x and x != '.']
302
303
@classmethod
304
def _parse_pattern(cls, pattern):
305
"""Parse a glob pattern to a list of parts. This is much like
306
_parse_path, except:
307
308
- Rather than normalizing and returning the drive and root, we raise
309
NotImplementedError if either are present.
310
- If the path has no real parts, we raise ValueError.
311
- If the path ends in a slash, then a final empty part is added.
312
"""
313
drv, root, rel = cls.parser.splitroot(pattern)
314
if root or drv:
315
raise NotImplementedError("Non-relative patterns are unsupported")
316
sep = cls.parser.sep
317
altsep = cls.parser.altsep
318
if altsep:
319
rel = rel.replace(altsep, sep)
320
parts = [x for x in rel.split(sep) if x and x != '.']
321
if not parts:
322
raise ValueError(f"Unacceptable pattern: {str(pattern)!r}")
323
elif rel.endswith(sep):
324
# GH-65238: preserve trailing slash in glob patterns.
325
parts.append('')
326
return parts
327
328
def as_posix(self):
329
"""Return the string representation of the path with forward (/)
330
slashes."""
331
18
18
return str(self).replace(self.parser.sep, '/')
332
333
@property
334
def _raw_path(self):
335
paths = self._raw_paths
336
if len(paths) == 1:
337
return paths[0]
338
elif paths:
339
# Join path segments from the initializer.
340
return self.parser.join(*paths)
341
else:
342
return ''
343
344
@property
345
def drive(self):
346
"""The drive prefix (letter or UNC path), if any."""
347
try:
348
return self._drv
349
except AttributeError:
350
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
351
return self._drv
352
353
@property
354
def root(self):
355
"""The root of the path, if any."""
356
try:
357
return self._root
358
except AttributeError:
359
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
360
return self._root
361
362
@property
363
def _tail(self):
364
try:
365
3
3
return self._tail_cached
366
except AttributeError:
367
6
7
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
368
return self._tail_cached
369
370
@property
371
def anchor(self):
372
"""The concatenation of the drive and root, or ''."""
373
return self.drive + self.root
374
375
@property
376
def parts(self):
377
"""An object providing sequence-like access to the
378
components in the filesystem path."""
379
1
1
if self.drive or self.root:
380
return (self.drive + self.root,) + tuple(self._tail)
381
else:
382
return tuple(self._tail)
383
384
@property
385
def parent(self):
386
"""The logical parent of the path."""
387
drv = self.drive
388
root = self.root
389
tail = self._tail
390
if not tail:
391
return self
392
return self._from_parsed_parts(drv, root, tail[:-1])
393
394
@property
395
def parents(self):
396
"""A sequence of this path's logical parents."""
397
# The value of this property should not be cached on the path object,
398
# as doing so would introduce a reference cycle.
399
2
2
return _PathParents(self)
400
401
@property
402
def name(self):
403
"""The final path component, if any."""
404
33
37
tail = self._tail
405
if not tail:
406
return ''
407
return tail[-1]
408
409
def with_name(self, name):
410
"""Return a new path with the file name changed."""
411
p = self.parser
412
if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.':
413
raise ValueError(f"Invalid name {name!r}")
414
tail = self._tail.copy()
415
if not tail:
416
raise ValueError(f"{self!r} has an empty name")
417
tail[-1] = name
418
return self._from_parsed_parts(self.drive, self.root, tail)
419
420
def with_stem(self, stem):
421
"""Return a new path with the stem changed."""
422
suffix = self.suffix
423
if not suffix:
424
return self.with_name(stem)
425
elif not stem:
426
# If the suffix is non-empty, we can't make the stem empty.
427
raise ValueError(f"{self!r} has a non-empty suffix")
428
else:
429
return self.with_name(stem + suffix)
430
431
def with_suffix(self, suffix):
432
"""Return a new path with the file suffix changed. If the path
433
has no suffix, add given suffix. If the given suffix is an empty
434
string, remove the suffix from the path.
435
"""
436
stem = self.stem
437
if not stem:
438
# If the stem is empty, we can't make the suffix non-empty.
439
raise ValueError(f"{self!r} has an empty name")
440
elif suffix and not suffix.startswith('.'):
441
raise ValueError(f"Invalid suffix {suffix!r}")
442
else:
443
return self.with_name(stem + suffix)
444
445
@property
446
def stem(self):
447
"""The final path component, minus its last suffix."""
448
name = self.name
449
i = name.rfind('.')
450
if i != -1:
451
stem = name[:i]
452
# Stem must contain at least one non-dot character.
453
if stem.lstrip('.'):
454
return stem
455
return name
456
457
@property
458
def suffix(self):
459
"""
460
The final component's last suffix, if any.
461
462
This includes the leading period. For example: '.txt'
463
"""
464
name = self.name.lstrip('.')
465
i = name.rfind('.')
466
if i != -1:
467
return name[i:]
468
return ''
469
470
@property
471
def suffixes(self):
472
"""
473
A list of the final component's suffixes, if any.
474
475
These include the leading periods. For example: ['.tar', '.gz']
476
"""
477
return ['.' + ext for ext in self.name.lstrip('.').split('.')[1:]]
478
479
def relative_to(self, other, *, walk_up=False):
480
"""Return the relative path to another path identified by the passed
481
arguments. If the operation is not possible (because this is not
482
related to the other path), raise ValueError.
483
484
The *walk_up* parameter controls whether `..` may be used to resolve
485
the path.
486
"""
487
1
1
if not hasattr(other, 'with_segments'):
488
other = self.with_segments(other)
489
parts = []
490
5
5
for path in chain([other], other.parents):
491
332
341
if path == self or path in self.parents:
492
19
19
break
493
elif not walk_up:
494
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
495
elif path.name == '..':
496
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
497
else:
498
parts.append('..')
499
else:
500
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
501
19
19
parts.extend(self._tail[len(path._tail):])
502
88
90
return self._from_parsed_parts('', '', parts)
503
504
def is_relative_to(self, other):
505
"""Return True if the path is relative to another path or False.
506
"""
507
if not hasattr(other, 'with_segments'):
508
other = self.with_segments(other)
509
2
2
return other == self or other in self.parents
510
511
def is_absolute(self):
512
"""True if the path is absolute (has both a root and, if applicable,
513
a drive)."""
514
if self.parser is posixpath:
515
# Optimization: work with raw paths on POSIX.
516
for path in self._raw_paths:
517
if path.startswith('/'):
518
return True
519
return False
520
return self.parser.isabs(self)
521
522
def as_uri(self):
523
"""Return the path as a URI."""
524
import warnings
525
msg = ("pathlib.PurePath.as_uri() is deprecated and scheduled "
526
"for removal in Python 3.19. Use pathlib.Path.as_uri().")
527
warnings._deprecated("pathlib.PurePath.as_uri", msg, remove=(3, 19))
528
if not self.is_absolute():
529
raise ValueError("relative path can't be expressed as a file URI")
530
531
drive = self.drive
532
if len(drive) == 2 and drive[1] == ':':
533
# It's a path on a local drive => 'file:///c:/a/b'
534
prefix = 'file:///' + drive
535
path = self.as_posix()[2:]
536
elif drive:
537
# It's a path on a network drive => 'file://host/share/a/b'
538
prefix = 'file:'
539
path = self.as_posix()
540
else:
541
# It's a posix path => 'file:///etc/hosts'
542
prefix = 'file://'
543
path = str(self)
544
from urllib.parse import quote_from_bytes
545
return prefix + quote_from_bytes(os.fsencode(path))
546
547
def full_match(self, pattern, *, case_sensitive=None):
548
"""
549
Return True if this path matches the given glob-style pattern. The
550
pattern is matched against the entire path.
551
"""
552
if not hasattr(pattern, 'with_segments'):
553
pattern = self.with_segments(pattern)
554
if case_sensitive is None:
555
case_sensitive = self.parser is posixpath
556
557
# The string representation of an empty path is a single dot ('.'). Empty
558
# paths shouldn't match wildcards, so we change it to the empty string.
559
path = str(self) if self.parts else ''
560
pattern = str(pattern) if pattern.parts else ''
561
globber = _StringGlobber(self.parser.sep, case_sensitive, recursive=True)
562
return globber.compile(pattern)(path) is not None
563
564
def match(self, path_pattern, *, case_sensitive=None):
565
"""
566
Return True if this path matches the given pattern. If the pattern is
567
relative, matching is done from the right; otherwise, the entire path
568
is matched. The recursive wildcard '**' is *not* supported by this
569
method.
570
"""
571
if not hasattr(path_pattern, 'with_segments'):
572
path_pattern = self.with_segments(path_pattern)
573
if case_sensitive is None:
574
case_sensitive = self.parser is posixpath
575
path_parts = self.parts[::-1]
576
pattern_parts = path_pattern.parts[::-1]
577
if not pattern_parts:
578
raise ValueError("empty pattern")
579
if len(path_parts) < len(pattern_parts):
580
return False
581
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
582
return False
583
globber = _StringGlobber(self.parser.sep, case_sensitive)
584
for path_part, pattern_part in zip(path_parts, pattern_parts):
585
match = globber.compile(pattern_part)
586
if match(path_part) is None:
587
return False
588
return True
589
590
# Subclassing os.PathLike makes isinstance() checks slower,
591
# which in turn makes Path construction slower. Register instead!
592
os.PathLike.register(PurePath)
593
594
595
class PurePosixPath(PurePath):
596
"""PurePath subclass for non-Windows systems.
597
598
On a POSIX system, instantiating a PurePath should return this object.
599
However, you can also instantiate it directly on any system.
600
"""
601
parser = posixpath
602
__slots__ = ()
603
604
605
class PureWindowsPath(PurePath):
606
"""PurePath subclass for Windows systems.
607
608
On a Windows system, instantiating a PurePath should return this object.
609
However, you can also instantiate it directly on any system.
610
"""
611
parser = ntpath
612
__slots__ = ()
613
614
615
_STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
616
617
618
class _Info:
619
"""Implementation of pathlib.types.PathInfo that provides status
620
information by querying a wrapped os.stat_result object. Don't try to
621
construct it yourself."""
622
__slots__ = ('_path', '_entry', '_stat_result', '_lstat_result')
623
624
def __init__(self, path, entry=None):
625
self._path = path
626
self._entry = entry
627
self._stat_result = None
628
self._lstat_result = None
629
630
def __repr__(self):
631
path_type = "WindowsPath" if os.name == "nt" else "PosixPath"
632
return f"<{path_type}.info>"
633
634
def _stat(self, *, follow_symlinks=True):
635
"""Return the status as an os.stat_result."""
636
if self._entry:
637
return self._entry.stat(follow_symlinks=follow_symlinks)
638
if follow_symlinks:
639
if not self._stat_result:
640
try:
641
self._stat_result = os.stat(self._path)
642
except (OSError, ValueError):
643
self._stat_result = _STAT_RESULT_ERROR
644
raise
645
return self._stat_result
646
else:
647
if not self._lstat_result:
648
try:
649
self._lstat_result = os.lstat(self._path)
650
except (OSError, ValueError):
651
self._lstat_result = _STAT_RESULT_ERROR
652
raise
653
return self._lstat_result
654
655
def exists(self, *, follow_symlinks=True):
656
"""Whether this path exists."""
657
if self._entry:
658
if not follow_symlinks:
659
return True
660
if follow_symlinks:
661
if self._stat_result is _STAT_RESULT_ERROR:
662
return False
663
else:
664
if self._lstat_result is _STAT_RESULT_ERROR:
665
return False
666
try:
667
self._stat(follow_symlinks=follow_symlinks)
668
except (OSError, ValueError):
669
return False
670
return True
671
672
def is_dir(self, *, follow_symlinks=True):
673
"""Whether this path is a directory."""
674
if self._entry:
675
try:
676
return self._entry.is_dir(follow_symlinks=follow_symlinks)
677
except OSError:
678
return False
679
if follow_symlinks:
680
if self._stat_result is _STAT_RESULT_ERROR:
681
return False
682
else:
683
if self._lstat_result is _STAT_RESULT_ERROR:
684
return False
685
try:
686
st = self._stat(follow_symlinks=follow_symlinks)
687
except (OSError, ValueError):
688
return False
689
return S_ISDIR(st.st_mode)
690
691
def is_file(self, *, follow_symlinks=True):
692
"""Whether this path is a regular file."""
693
if self._entry:
694
try:
695
return self._entry.is_file(follow_symlinks=follow_symlinks)
696
except OSError:
697
return False
698
if follow_symlinks:
699
if self._stat_result is _STAT_RESULT_ERROR:
700
return False
701
else:
702
if self._lstat_result is _STAT_RESULT_ERROR:
703
return False
704
try:
705
st = self._stat(follow_symlinks=follow_symlinks)
706
except (OSError, ValueError):
707
return False
708
return S_ISREG(st.st_mode)
709
710
def is_symlink(self):
711
"""Whether this path is a symbolic link."""
712
if self._entry:
713
try:
714
return self._entry.is_symlink()
715
except OSError:
716
return False
717
if self._lstat_result is _STAT_RESULT_ERROR:
718
return False
719
try:
720
st = self._stat(follow_symlinks=False)
721
except (OSError, ValueError):
722
return False
723
return S_ISLNK(st.st_mode)
724
725
def _posix_permissions(self, *, follow_symlinks=True):
726
"""Return the POSIX file permissions."""
727
return S_IMODE(self._stat(follow_symlinks=follow_symlinks).st_mode)
728
729
def _file_id(self, *, follow_symlinks=True):
730
"""Returns the identifier of the file."""
731
st = self._stat(follow_symlinks=follow_symlinks)
732
return st.st_dev, st.st_ino
733
734
def _access_time_ns(self, *, follow_symlinks=True):
735
"""Return the access time in nanoseconds."""
736
return self._stat(follow_symlinks=follow_symlinks).st_atime_ns
737
738
def _mod_time_ns(self, *, follow_symlinks=True):
739
"""Return the modify time in nanoseconds."""
740
return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns
741
742
if hasattr(os.stat_result, 'st_flags'):
743
def _bsd_flags(self, *, follow_symlinks=True):
744
"""Return the flags."""
745
return self._stat(follow_symlinks=follow_symlinks).st_flags
746
747
if hasattr(os, 'listxattr'):
748
def _xattrs(self, *, follow_symlinks=True):
749
"""Return the xattrs as a list of (attr, value) pairs, or an empty
750
list if extended attributes aren't supported."""
751
try:
752
return [
753
(attr, os.getxattr(self._path, attr, follow_symlinks=follow_symlinks))
754
for attr in os.listxattr(self._path, follow_symlinks=follow_symlinks)]
755
except OSError as err:
756
if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES):
757
raise
758
return []
759
760
761
def _copy_info(info, target, follow_symlinks=True):
762
"""Copy metadata from the given PathInfo to the given local path."""
763
copy_times_ns = (
764
hasattr(info, '_access_time_ns') and
765
hasattr(info, '_mod_time_ns') and
766
(follow_symlinks or os.utime in os.supports_follow_symlinks))
767
if copy_times_ns:
768
t0 = info._access_time_ns(follow_symlinks=follow_symlinks)
769
t1 = info._mod_time_ns(follow_symlinks=follow_symlinks)
770
os.utime(target, ns=(t0, t1), follow_symlinks=follow_symlinks)
771
772
# We must copy extended attributes before the file is (potentially)
773
# chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
774
copy_xattrs = (
775
hasattr(info, '_xattrs') and
776
hasattr(os, 'setxattr') and
777
(follow_symlinks or os.setxattr in os.supports_follow_symlinks))
778
if copy_xattrs:
779
xattrs = info._xattrs(follow_symlinks=follow_symlinks)
780
for attr, value in xattrs:
781
try:
782
os.setxattr(target, attr, value, follow_symlinks=follow_symlinks)
783
except OSError as e:
784
if e.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES):
785
raise
786
787
copy_posix_permissions = (
788
hasattr(info, '_posix_permissions') and
789
(follow_symlinks or os.chmod in os.supports_follow_symlinks))
790
if copy_posix_permissions:
791
posix_permissions = info._posix_permissions(follow_symlinks=follow_symlinks)
792
try:
793
os.chmod(target, posix_permissions, follow_symlinks=follow_symlinks)
794
except NotImplementedError:
795
# if we got a NotImplementedError, it's because
796
# * follow_symlinks=False,
797
# * lchown() is unavailable, and
798
# * either
799
# * fchownat() is unavailable or
800
# * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
801
# (it returned ENOSUP.)
802
# therefore we're out of options--we simply cannot chown the
803
# symlink. give up, suppress the error.
804
# (which is what shutil always did in this circumstance.)
805
pass
806
807
copy_bsd_flags = (
808
hasattr(info, '_bsd_flags') and
809
hasattr(os, 'chflags') and
810
(follow_symlinks or os.chflags in os.supports_follow_symlinks))
811
if copy_bsd_flags:
812
bsd_flags = info._bsd_flags(follow_symlinks=follow_symlinks)
813
try:
814
os.chflags(target, bsd_flags, follow_symlinks=follow_symlinks)
815
except OSError as why:
816
if why.errno not in (EOPNOTSUPP, ENOTSUP):
817
raise
818
819
820
class Path(PurePath):
821
"""PurePath subclass that can make system calls.
822
823
Path represents a filesystem path but unlike PurePath, also offers
824
methods to do system calls on path objects. Depending on your system,
825
instantiating a Path will return either a PosixPath or a WindowsPath
826
object. You can also instantiate a PosixPath or WindowsPath directly,
827
but cannot instantiate a WindowsPath on a POSIX system or vice versa.
828
"""
829
__slots__ = ('_info',)
830
831
def __new__(cls, *args, **kwargs):
832
if cls is Path:
833
cls = WindowsPath if os.name == 'nt' else PosixPath
834
return object.__new__(cls)
835
836
@property
837
def info(self):
838
"""
839
A PathInfo object that exposes the file type and other file attributes
840
of this path.
841
"""
842
try:
843
return self._info
844
except AttributeError:
845
self._info = _Info(str(self))
846
return self._info
847
848
def stat(self, *, follow_symlinks=True):
849
"""
850
Return the result of the stat() system call on this path, like
851
os.stat() does.
852
"""
853
return os.stat(self, follow_symlinks=follow_symlinks)
854
855
def lstat(self):
856
"""
857
Like stat(), except if the path points to a symlink, the symlink's
858
status information is returned, rather than its target's.
859
"""
860
193
194
return os.lstat(self)
861
862
def exists(self, *, follow_symlinks=True):
863
"""
864
Whether this path exists.
865
866
This method normally follows symlinks; to check whether a symlink exists,
867
add the argument follow_symlinks=False.
868
"""
869
if follow_symlinks:
870
1
return os.path.exists(self)
871
return os.path.lexists(self)
872
873
def is_dir(self, *, follow_symlinks=True):
874
"""
875
Whether this path is a directory.
876
"""
877
if follow_symlinks:
878
return os.path.isdir(self)
879
try:
880
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
881
except (OSError, ValueError):
882
return False
883
884
def is_file(self, *, follow_symlinks=True):
885
"""
886
Whether this path is a regular file (also True for symlinks pointing
887
to regular files).
888
"""
889
if follow_symlinks:
890
return os.path.isfile(self)
891
try:
892
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
893
except (OSError, ValueError):
894
return False
895
896
def is_mount(self):
897
"""
898
Check if this path is a mount point
899
"""
900
return os.path.ismount(self)
901
902
def is_symlink(self):
903
"""
904
Whether this path is a symbolic link.
905
"""
906
4
19
return os.path.islink(self)
907
908
def is_junction(self):
909
"""
910
Whether this path is a junction.
911
"""
912
return os.path.isjunction(self)
913
914
def is_block_device(self):
915
"""
916
Whether this path is a block device.
917
"""
918
try:
919
return S_ISBLK(self.stat().st_mode)
920
except (OSError, ValueError):
921
return False
922
923
def is_char_device(self):
924
"""
925
Whether this path is a character device.
926
"""
927
try:
928
return S_ISCHR(self.stat().st_mode)
929
except (OSError, ValueError):
930
return False
931
932
def is_fifo(self):
933
"""
934
Whether this path is a FIFO.
935
"""
936
try:
937
return S_ISFIFO(self.stat().st_mode)
938
except (OSError, ValueError):
939
return False
940
941
def is_socket(self):
942
"""
943
Whether this path is a socket.
944
"""
945
try:
946
return S_ISSOCK(self.stat().st_mode)
947
except (OSError, ValueError):
948
return False
949
950
def samefile(self, other_path):
951
"""Return whether other_path is the same or not as this file
952
(as returned by os.path.samefile()).
953
"""
954
st = self.stat()
955
try:
956
other_st = other_path.stat()
957
except AttributeError:
958
other_st = self.with_segments(other_path).stat()
959
return (st.st_ino == other_st.st_ino and
960
st.st_dev == other_st.st_dev)
961
962
def open(self, mode='r', buffering=-1, encoding=None,
963
errors=None, newline=None):
964
"""
965
Open the file pointed to by this path and return a file object, as
966
the built-in open() function does.
967
"""
968
if "b" not in mode:
969
encoding = io.text_encoding(encoding)
970
27
27
return io.open(self, mode, buffering, encoding, errors, newline)
971
972
def read_bytes(self):
973
"""
974
Open the file in bytes mode, read it, and close the file.
975
"""
976
6
8
with self.open(mode='rb', buffering=0) as f:
977
5
5
return f.read()
978
979
def read_text(self, encoding=None, errors=None, newline=None):
980
"""
981
Open the file in text mode, read it, and close the file.
982
"""
983
# Call io.text_encoding() here to ensure any warning is raised at an
984
# appropriate stack level.
985
encoding = io.text_encoding(encoding)
986
with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f:
987
return f.read()
988
989
def write_bytes(self, data):
990
"""
991
Open the file in bytes mode, write to it, and close the file.
992
"""
993
# type-check for the buffer interface before truncating the file
994
view = memoryview(data)
995
with self.open(mode='wb') as f:
996
return f.write(view)
997
998
def write_text(self, data, encoding=None, errors=None, newline=None):
999
"""
1000
Open the file in text mode, write to it, and close the file.
1001
"""
1002
# Call io.text_encoding() here to ensure any warning is raised at an
1003
# appropriate stack level.
1004
encoding = io.text_encoding(encoding)
1005
if not isinstance(data, str):
1006
raise TypeError('data must be str, not %s' %
1007
data.__class__.__name__)
1008
with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
1009
return f.write(data)
1010
1011
_remove_leading_dot = operator.itemgetter(slice(2, None))
1012
_remove_trailing_slash = operator.itemgetter(slice(-1))
1013
1014
def _filter_trailing_slash(self, paths):
1015
sep = self.parser.sep
1016
anchor_len = len(self.anchor)
1017
for path_str in paths:
1018
if len(path_str) > anchor_len and path_str[-1] == sep:
1019
path_str = path_str[:-1]
1020
yield path_str
1021
1022
def _from_dir_entry(self, dir_entry, path_str):
1023
path = self.with_segments(path_str)
1024
path._str = path_str
1025
path._info = _Info(dir_entry.path, dir_entry)
1026
return path
1027
1028
def iterdir(self):
1029
"""Yield path objects of the directory contents.
1030
1031
The children are yielded in arbitrary order, and the
1032
special entries '.' and '..' are not included.
1033
"""
1034
root_dir = str(self)
1035
16
16
with os.scandir(root_dir) as scandir_it:
1036
75
75
entries = list(scandir_it)
1037
if root_dir == '.':
1038
return (self._from_dir_entry(e, e.name) for e in entries)
1039
else:
1040
1
1
return (self._from_dir_entry(e, e.path) for e in entries)
1041
1042
def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
1043
"""Iterate over this subtree and yield all existing files (of any
1044
kind, including directories) matching the given relative pattern.
1045
"""
1046
sys.audit("pathlib.Path.glob", self, pattern)
1047
if case_sensitive is None:
1048
case_sensitive = self.parser is posixpath
1049
case_pedantic = False
1050
else:
1051
# The user has expressed a case sensitivity choice, but we don't
1052
# know the case sensitivity of the underlying filesystem, so we
1053
# must use scandir() for everything, including non-wildcard parts.
1054
case_pedantic = True
1055
parts = self._parse_pattern(pattern)
1056
recursive = True if recurse_symlinks else _no_recurse_symlinks
1057
globber = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
1058
select = globber.selector(parts[::-1])
1059
root = str(self)
1060
paths = select(self.parser.join(root, ''))
1061
1062
# Normalize results
1063
if root == '.':
1064
paths = map(self._remove_leading_dot, paths)
1065
if parts[-1] == '':
1066
paths = map(self._remove_trailing_slash, paths)
1067
elif parts[-1] == '**':
1068
paths = self._filter_trailing_slash(paths)
1069
paths = map(self._from_parsed_string, paths)
1070
return paths
1071
1072
def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
1073
"""Recursively yield all existing files (of any kind, including
1074
directories) matching the given relative pattern, anywhere in
1075
this subtree.
1076
"""
1077
sys.audit("pathlib.Path.rglob", self, pattern)
1078
pattern = self.parser.join('**', pattern)
1079
return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks)
1080
1081
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
1082
"""Walk the directory tree from this directory, similar to os.walk()."""
1083
sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks)
1084
root_dir = str(self)
1085
if not follow_symlinks:
1086
follow_symlinks = os._walk_symlinks_as_files
1087
results = os.walk(root_dir, top_down, on_error, follow_symlinks)
1088
for path_str, dirnames, filenames in results:
1089
if root_dir == '.':
1090
path_str = path_str[2:]
1091
yield self._from_parsed_string(path_str), dirnames, filenames
1092
1093
def absolute(self):
1094
"""Return an absolute version of this path
1095
No normalization or symlink resolution is performed.
1096
1097
Use resolve() to resolve symlinks and remove '..' segments.
1098
"""
1099
if self.is_absolute():
1100
return self
1101
if self.root:
1102
drive = os.path.splitroot(os.getcwd())[0]
1103
return self._from_parsed_parts(drive, self.root, self._tail)
1104
if self.drive:
1105
# There is a CWD on each drive-letter drive.
1106
cwd = os.path.abspath(self.drive)
1107
else:
1108
cwd = os.getcwd()
1109
if not self._tail:
1110
# Fast path for "empty" paths, e.g. Path("."), Path("") or Path().
1111
# We pass only one argument to with_segments() to avoid the cost
1112
# of joining, and we exploit the fact that getcwd() returns a
1113
# fully-normalized string by storing it in _str. This is used to
1114
# implement Path.cwd().
1115
return self._from_parsed_string(cwd)
1116
drive, root, rel = os.path.splitroot(cwd)
1117
if not rel:
1118
return self._from_parsed_parts(drive, root, self._tail)
1119
tail = rel.split(self.parser.sep)
1120
tail.extend(self._tail)
1121
return self._from_parsed_parts(drive, root, tail)
1122
1123
@classmethod
1124
def cwd(cls):
1125
"""Return a new path pointing to the current working directory."""
1126
cwd = os.getcwd()
1127
path = cls(cwd)
1128
path._str = cwd # getcwd() returns a normalized path
1129
return path
1130
1131
def resolve(self, strict=False):
1132
"""
1133
Make the path absolute, resolving all symlinks on the way and also
1134
normalizing it.
1135
"""
1136
1137
2
51
return self.with_segments(os.path.realpath(self, strict=strict))
1138
1139
if pwd:
1140
def owner(self, *, follow_symlinks=True):
1141
"""
1142
Return the login name of the file owner.
1143
"""
1144
uid = self.stat(follow_symlinks=follow_symlinks).st_uid
1145
return pwd.getpwuid(uid).pw_name
1146
else:
1147
def owner(self, *, follow_symlinks=True):
1148
"""
1149
Return the login name of the file owner.
1150
"""
1151
f = f"{type(self).__name__}.owner()"
1152
raise UnsupportedOperation(f"{f} is unsupported on this system")
1153
1154
if grp:
1155
def group(self, *, follow_symlinks=True):
1156
"""
1157
Return the group name of the file gid.
1158
"""
1159
gid = self.stat(follow_symlinks=follow_symlinks).st_gid
1160
return grp.getgrgid(gid).gr_name
1161
else:
1162
def group(self, *, follow_symlinks=True):
1163
"""
1164
Return the group name of the file gid.
1165
"""
1166
f = f"{type(self).__name__}.group()"
1167
raise UnsupportedOperation(f"{f} is unsupported on this system")
1168
1169
if hasattr(os, "readlink"):
1170
def readlink(self):
1171
"""
1172
Return the path to which the symbolic link points.
1173
"""
1174
return self.with_segments(os.readlink(self))
1175
else:
1176
def readlink(self):
1177
"""
1178
Return the path to which the symbolic link points.
1179
"""
1180
f = f"{type(self).__name__}.readlink()"
1181
raise UnsupportedOperation(f"{f} is unsupported on this system")
1182
1183
def touch(self, mode=0o666, exist_ok=True):
1184
"""
1185
Create this file with the given access mode, if it doesn't exist.
1186
"""
1187
1188
if exist_ok:
1189
# First try to bump modification time
1190
# Implementation note: GNU touch uses the UTIME_NOW option of
1191
# the utimensat() / futimens() functions.
1192
try:
1193
os.utime(self, None)
1194
except OSError:
1195
# Avoid exception chaining
1196
pass
1197
else:
1198
return
1199
flags = os.O_CREAT | os.O_WRONLY
1200
if not exist_ok:
1201
flags |= os.O_EXCL
1202
fd = os.open(self, flags, mode)
1203
os.close(fd)
1204
1205
def mkdir(self, mode=0o777, parents=False, exist_ok=False, *, parent_mode=None):
1206
"""
1207
Create a new directory at this given path.
1208
"""
1209
try:
1210
os.mkdir(self, mode)
1211
except FileNotFoundError:
1212
if not parents or self.parent == self:
1213
raise
1214
if parent_mode is not None:
1215
self.parent.mkdir(mode=parent_mode, parents=True, exist_ok=True,
1216
parent_mode=parent_mode)
1217
else:
1218
self.parent.mkdir(parents=True, exist_ok=True)
1219
self.mkdir(mode, parents=False, exist_ok=exist_ok)
1220
except OSError:
1221
# Cannot rely on checking for EEXIST, since the operating system
1222
# could give priority to other errors like EACCES or EROFS
1223
1
1
if not exist_ok or not self.is_dir():
1224
raise
1225
1226
def chmod(self, mode, *, follow_symlinks=True):
1227
"""
1228
Change the permissions of the path, like os.chmod().
1229
"""
1230
os.chmod(self, mode, follow_symlinks=follow_symlinks)
1231
1232
def lchmod(self, mode):
1233
"""
1234
Like chmod(), except if the path points to a symlink, the symlink's
1235
permissions are changed, rather than its target's.
1236
"""
1237
self.chmod(mode, follow_symlinks=False)
1238
1239
def unlink(self, missing_ok=False):
1240
"""
1241
Remove this file or link.
1242
If the path is a directory, use rmdir() instead.
1243
"""
1244
try:
1245
16
16
os.unlink(self)
1246
except FileNotFoundError:
1247
if not missing_ok:
1248
raise
1249
1250
def rmdir(self):
1251
"""
1252
Remove this directory. The directory must be empty.
1253
"""
1254
os.rmdir(self)
1255
1256
def _delete(self):
1257
"""
1258
Delete this file or directory (including all sub-directories).
1259
"""
1260
if self.is_symlink() or self.is_junction():
1261
self.unlink()
1262
elif self.is_dir():
1263
shutil.rmtree(self)
1264
else:
1265
self.unlink()
1266
1267
def rename(self, target):
1268
"""
1269
Rename this path to the target path.
1270
1271
The target path may be absolute or relative. Relative paths are
1272
interpreted relative to the current working directory, *not* the
1273
directory of the Path object.
1274
1275
Returns the new Path instance pointing to the target path.
1276
"""
1277
os.rename(self, target)
1278
if not hasattr(target, 'with_segments'):
1279
target = self.with_segments(target)
1280
return target
1281
1282
def replace(self, target):
1283
"""
1284
Rename this path to the target path, overwriting if that path exists.
1285
1286
The target path may be absolute or relative. Relative paths are
1287
interpreted relative to the current working directory, *not* the
1288
directory of the Path object.
1289
1290
Returns the new Path instance pointing to the target path.
1291
"""
1292
os.replace(self, target)
1293
if not hasattr(target, 'with_segments'):
1294
target = self.with_segments(target)
1295
return target
1296
1297
def copy(self, target, **kwargs):
1298
"""
1299
Recursively copy this file or directory tree to the given destination.
1300
"""
1301
if not hasattr(target, 'with_segments'):
1302
target = self.with_segments(target)
1303
ensure_distinct_paths(self, target)
1304
target._copy_from(self, **kwargs)
1305
return target.joinpath() # Empty join to ensure fresh metadata.
1306
1307
def copy_into(self, target_dir, **kwargs):
1308
"""
1309
Copy this file or directory tree into the given existing directory.
1310
"""
1311
name = self.name
1312
if not name:
1313
raise ValueError(f"{self!r} has an empty name")
1314
elif hasattr(target_dir, 'with_segments'):
1315
target = target_dir / name
1316
else:
1317
target = self.with_segments(target_dir, name)
1318
return self.copy(target, **kwargs)
1319
1320
def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):
1321
"""
1322
Recursively copy the given path to this path.
1323
"""
1324
if not follow_symlinks and source.info.is_symlink():
1325
self._copy_from_symlink(source, preserve_metadata)
1326
elif source.info.is_dir():
1327
children = source.iterdir()
1328
os.mkdir(self)
1329
for child in children:
1330
self.joinpath(child.name)._copy_from(
1331
child, follow_symlinks, preserve_metadata)
1332
if preserve_metadata:
1333
_copy_info(source.info, self)
1334
else:
1335
self._copy_from_file(source, preserve_metadata)
1336
1337
def _copy_from_file(self, source, preserve_metadata=False):
1338
ensure_different_files(source, self)
1339
with vfsopen(source, 'rb') as source_f:
1340
with open(self, 'wb') as target_f:
1341
copyfileobj(source_f, target_f)
1342
if preserve_metadata:
1343
_copy_info(source.info, self)
1344
1345
if copyfile2:
1346
# Use fast OS routine for local file copying where available.
1347
_copy_from_file_fallback = _copy_from_file
1348
def _copy_from_file(self, source, preserve_metadata=False):
1349
try:
1350
source = os.fspath(source)
1351
except TypeError:
1352
pass
1353
else:
1354
copyfile2(source, str(self))
1355
return
1356
self._copy_from_file_fallback(source, preserve_metadata)
1357
1358
if os.name == 'nt':
1359
# If a directory-symlink is copied *before* its target, then
1360
# os.symlink() incorrectly creates a file-symlink on Windows. Avoid
1361
# this by passing *target_is_dir* to os.symlink() on Windows.
1362
def _copy_from_symlink(self, source, preserve_metadata=False):
1363
os.symlink(vfspath(source.readlink()), self, source.info.is_dir())
1364
if preserve_metadata:
1365
_copy_info(source.info, self, follow_symlinks=False)
1366
else:
1367
def _copy_from_symlink(self, source, preserve_metadata=False):
1368
os.symlink(vfspath(source.readlink()), self)
1369
if preserve_metadata:
1370
_copy_info(source.info, self, follow_symlinks=False)
1371
1372
def move(self, target):
1373
"""
1374
Recursively move this file or directory tree to the given destination.
1375
"""
1376
# Use os.replace() if the target is os.PathLike and on the same FS.
1377
try:
1378
target = self.with_segments(target)
1379
except TypeError:
1380
pass
1381
else:
1382
ensure_different_files(self, target)
1383
try:
1384
os.replace(self, target)
1385
except OSError as err:
1386
if err.errno != EXDEV:
1387
raise
1388
else:
1389
return target.joinpath() # Empty join to ensure fresh metadata.
1390
# Fall back to copy+delete.
1391
target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
1392
self._delete()
1393
return target
1394
1395
def move_into(self, target_dir):
1396
"""
1397
Move this file or directory tree into the given existing directory.
1398
"""
1399
name = self.name
1400
if not name:
1401
raise ValueError(f"{self!r} has an empty name")
1402
elif hasattr(target_dir, 'with_segments'):
1403
target = target_dir / name
1404
else:
1405
target = self.with_segments(target_dir, name)
1406
return self.move(target)
1407
1408
if hasattr(os, "symlink"):
1409
def symlink_to(self, target, target_is_directory=False):
1410
"""
1411
Make this path a symlink pointing to the target path.
1412
Note the order of arguments (link, target) is the reverse of os.symlink.
1413
"""
1414
os.symlink(target, self, target_is_directory)
1415
else:
1416
def symlink_to(self, target, target_is_directory=False):
1417
"""
1418
Make this path a symlink pointing to the target path.
1419
Note the order of arguments (link, target) is the reverse of os.symlink.
1420
"""
1421
f = f"{type(self).__name__}.symlink_to()"
1422
raise UnsupportedOperation(f"{f} is unsupported on this system")
1423
1424
if hasattr(os, "link"):
1425
def hardlink_to(self, target):
1426
"""
1427
Make this path a hard link pointing to the same file as *target*.
1428
1429
Note the order of arguments (self, target) is the reverse of os.link's.
1430
"""
1431
os.link(target, self)
1432
else:
1433
def hardlink_to(self, target):
1434
"""
1435
Make this path a hard link pointing to the same file as *target*.
1436
1437
Note the order of arguments (self, target) is the reverse of os.link's.
1438
"""
1439
f = f"{type(self).__name__}.hardlink_to()"
1440
raise UnsupportedOperation(f"{f} is unsupported on this system")
1441
1442
def expanduser(self):
1443
""" Return a new path with expanded ~ and ~user constructs
1444
(as returned by os.path.expanduser)
1445
"""
1446
if (not (self.drive or self.root) and
1447
self._tail and self._tail[0][:1] == '~'):
1448
homedir = os.path.expanduser(self._tail[0])
1449
if homedir[:1] == "~":
1450
raise RuntimeError("Could not determine home directory.")
1451
drv, root, tail = self._parse_path(homedir)
1452
return self._from_parsed_parts(drv, root, tail + self._tail[1:])
1453
1454
return self
1455
1456
@classmethod
1457
def home(cls):
1458
"""Return a new path pointing to expanduser('~').
1459
"""
1460
homedir = os.path.expanduser("~")
1461
if homedir == "~":
1462
raise RuntimeError("Could not determine home directory.")
1463
return cls(homedir)
1464
1465
def as_uri(self):
1466
"""Return the path as a URI."""
1467
if not self.is_absolute():
1468
raise ValueError("relative paths can't be expressed as file URIs")
1469
from urllib.request import pathname2url
1470
return pathname2url(str(self), add_scheme=True)
1471
1472
@classmethod
1473
def from_uri(cls, uri):
1474
"""Return a new path from the given 'file' URI."""
1475
from urllib.error import URLError
1476
from urllib.request import url2pathname
1477
try:
1478
path = cls(url2pathname(uri, require_scheme=True))
1479
except URLError as exc:
1480
raise ValueError(exc.reason) from None
1481
if not path.is_absolute():
1482
raise ValueError(f"URI is not absolute: {uri!r}")
1483
return path
1484
1485
1486
class PosixPath(Path, PurePosixPath):
1487
"""Path subclass for non-Windows systems.
1488
1489
On a POSIX system, instantiating a Path should return this object.
1490
"""
1491
__slots__ = ()
1492
1493
if os.name == 'nt':
1494
def __new__(cls, *args, **kwargs):
1495
raise UnsupportedOperation(
1496
f"cannot instantiate {cls.__name__!r} on your system")
1497
1498
class WindowsPath(Path, PureWindowsPath):
1499
"""Path subclass for Windows systems.
1500
1501
On a Windows system, instantiating a Path should return this object.
1502
"""
1503
__slots__ = ()
1504
1505
if os.name != 'nt':
1506
def __new__(cls, *args, **kwargs):
1507
raise UnsupportedOperation(
1508
f"cannot instantiate {cls.__name__!r} on your system")