pathlib-next 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pathlib_next/__init__.py +7 -0
- pathlib_next/fspath.py +103 -0
- pathlib_next/path.py +618 -0
- pathlib_next/uri/__init__.py +471 -0
- pathlib_next/uri/query.py +58 -0
- pathlib_next/uri/schemes/__init__.py +10 -0
- pathlib_next/uri/schemes/file.py +62 -0
- pathlib_next/uri/schemes/http.py +136 -0
- pathlib_next/uri/schemes/sftp.py +101 -0
- pathlib_next/uri/source.py +73 -0
- pathlib_next/utils/__init__.py +93 -0
- pathlib_next/utils/glob.py +145 -0
- pathlib_next/utils/stat.py +77 -0
- pathlib_next/utils/sync.py +91 -0
- pathlib_next-0.1.0.dist-info/METADATA +25 -0
- pathlib_next-0.1.0.dist-info/RECORD +18 -0
- pathlib_next-0.1.0.dist-info/WHEEL +4 -0
- pathlib_next-0.1.0.dist-info/licenses/LICENSE +21 -0
pathlib_next/path.py
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
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 os as _os
|
|
9
|
+
import re as _re
|
|
10
|
+
import stat as _stat
|
|
11
|
+
from pathlib import _ignore_error
|
|
12
|
+
import typing as _ty
|
|
13
|
+
import abc as _abc
|
|
14
|
+
import io as _io
|
|
15
|
+
import shutil as _shutil
|
|
16
|
+
|
|
17
|
+
from . import utils as _utils
|
|
18
|
+
from .utils import glob as _glob
|
|
19
|
+
from .utils.stat import FileStatLike
|
|
20
|
+
|
|
21
|
+
P = _ty.TypeVar("P", bound="Path")
|
|
22
|
+
PN = _ty.TypeVar("PN", bound="Pathname")
|
|
23
|
+
|
|
24
|
+
_P = _ty.TypeVar("_P")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FsPathLike(_ty.Protocol):
|
|
28
|
+
__slots__ = ()
|
|
29
|
+
|
|
30
|
+
@_utils.notimplemented
|
|
31
|
+
def __fspath__(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_os.PathLike.register(FsPathLike)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_FsPathLike = str | FsPathLike
|
|
38
|
+
|
|
39
|
+
class _PathnameParents(_ty.Sequence[PN]):
|
|
40
|
+
"""This object provides sequence-like access to the logical ancestors
|
|
41
|
+
of a path. Don't try to construct it yourself."""
|
|
42
|
+
|
|
43
|
+
__slots__ = ("_path", "_segments")
|
|
44
|
+
|
|
45
|
+
def __init__(self, path: PN):
|
|
46
|
+
self._path = path
|
|
47
|
+
segments = path.segments
|
|
48
|
+
while segments and not segments[-1]:
|
|
49
|
+
segments = segments[:-1]
|
|
50
|
+
self._segments = segments
|
|
51
|
+
|
|
52
|
+
def __len__(self):
|
|
53
|
+
return len(self._segments)
|
|
54
|
+
|
|
55
|
+
@_ty.overload
|
|
56
|
+
def __getitem__(self, idx: slice) -> tuple[PN]: ...
|
|
57
|
+
@_ty.overload
|
|
58
|
+
def __getitem__(self, idx: int) -> PN: ...
|
|
59
|
+
def __getitem__(self, idx: int | slice) -> tuple[PN] | PN:
|
|
60
|
+
if isinstance(idx, slice):
|
|
61
|
+
return tuple(self[i] for i in range(*idx.indices(len(self))))
|
|
62
|
+
|
|
63
|
+
if idx >= len(self) or idx < -len(self):
|
|
64
|
+
raise IndexError(idx)
|
|
65
|
+
if idx < 0:
|
|
66
|
+
idx += len(self)
|
|
67
|
+
return self._path.with_segments(*self._segments[: -idx - 1])
|
|
68
|
+
|
|
69
|
+
def __repr__(self):
|
|
70
|
+
return "<{}.parents>".format(type(self._path).__name__)
|
|
71
|
+
|
|
72
|
+
class Pathname(FsPathLike, _ty.Generic[_P]):
|
|
73
|
+
"""Base class for manipulating paths without I/O."""
|
|
74
|
+
|
|
75
|
+
__slots__ = ()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def _is_case_sensitive(self) -> bool:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
@_abc.abstractmethod
|
|
82
|
+
def as_uri(self) -> str: ...
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def name(self) -> str:
|
|
86
|
+
"""The final path component, if any."""
|
|
87
|
+
segments = self.segments
|
|
88
|
+
return "" if not segments else segments[-1]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def suffix(self) -> str:
|
|
92
|
+
"""
|
|
93
|
+
The final component's last suffix, if any.
|
|
94
|
+
|
|
95
|
+
This includes the leading period. For example: '.txt'
|
|
96
|
+
"""
|
|
97
|
+
name = self.name
|
|
98
|
+
i = name.rfind(".")
|
|
99
|
+
if 0 < i < len(name) - 1:
|
|
100
|
+
return name[i:]
|
|
101
|
+
else:
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def suffixes(self):
|
|
106
|
+
"""
|
|
107
|
+
A list of the final component's suffixes, if any.
|
|
108
|
+
|
|
109
|
+
These include the leading periods. For example: ['.tar', '.gz']
|
|
110
|
+
"""
|
|
111
|
+
name = self.name
|
|
112
|
+
if name.endswith("."):
|
|
113
|
+
return []
|
|
114
|
+
name = name.lstrip(".")
|
|
115
|
+
return ["." + suffix for suffix in name.split(".")[1:]]
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def stem(self):
|
|
119
|
+
"""The final path component, minus its last suffix."""
|
|
120
|
+
name = self.name
|
|
121
|
+
i = name.rfind(".")
|
|
122
|
+
if 0 < i < len(name) - 1:
|
|
123
|
+
return name[:i]
|
|
124
|
+
else:
|
|
125
|
+
return name
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
@_abc.abstractmethod
|
|
129
|
+
def segments(self) -> _ty.Iterable[str]: ...
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
@_abc.abstractmethod
|
|
133
|
+
def parts(self) -> _P: ...
|
|
134
|
+
|
|
135
|
+
@_abc.abstractmethod
|
|
136
|
+
def with_segments(self, *segments: str) -> _ty.Self: ...
|
|
137
|
+
|
|
138
|
+
def with_name(self, name: str) -> _ty.Self:
|
|
139
|
+
if not self.name:
|
|
140
|
+
raise ValueError("%r has an empty name" % (self,))
|
|
141
|
+
segments = self.segments[:-1] + [name]
|
|
142
|
+
return self.with_segments(*segments)
|
|
143
|
+
|
|
144
|
+
def with_stem(self, stem: str) -> _ty.Self:
|
|
145
|
+
"""Return a new path with the stem changed."""
|
|
146
|
+
return self.with_name(stem + self.suffix)
|
|
147
|
+
|
|
148
|
+
def with_suffix(self, suffix: str) -> _ty.Self:
|
|
149
|
+
name = self.name
|
|
150
|
+
if suffix and not suffix.startswith(".") or suffix == ".":
|
|
151
|
+
raise ValueError("Invalid suffix %r" % (suffix))
|
|
152
|
+
if not name:
|
|
153
|
+
raise ValueError("%r has an empty name" % (self,))
|
|
154
|
+
old_suffix = self.suffix
|
|
155
|
+
if not old_suffix:
|
|
156
|
+
name = name + suffix
|
|
157
|
+
else:
|
|
158
|
+
name = name[: -len(old_suffix)] + suffix
|
|
159
|
+
return self.with_name(name)
|
|
160
|
+
|
|
161
|
+
@_abc.abstractmethod
|
|
162
|
+
def relative_to(self, other: _ty.Self | str) -> _ty.Self:
|
|
163
|
+
"""Return the relative path to another path identified by the passed
|
|
164
|
+
arguments. If the operation is not possible (because this is not
|
|
165
|
+
related to the other path), raise ValueError.
|
|
166
|
+
"""
|
|
167
|
+
...
|
|
168
|
+
|
|
169
|
+
def is_relative_to(self, other: _ty.Self | str):
|
|
170
|
+
"""Return True if the path is relative to another path or False."""
|
|
171
|
+
cls = type(self)
|
|
172
|
+
other = other if isinstance(other, cls) else cls(self, other)
|
|
173
|
+
return other == self or other in self.parents
|
|
174
|
+
|
|
175
|
+
def __truediv__(self, key: _ty.Self | str) -> _ty.Self:
|
|
176
|
+
try:
|
|
177
|
+
return type(self)(self, key)
|
|
178
|
+
except (TypeError, NotImplementedError):
|
|
179
|
+
return NotImplemented
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
@_abc.abstractmethod
|
|
183
|
+
def parent(self) -> _ty.Self:
|
|
184
|
+
"""The logical parent of the path."""
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def parents(self)->_ty.Sequence[_ty.Self]:
|
|
188
|
+
return _PathnameParents(self)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@_utils.notimplemented
|
|
192
|
+
def is_absolute(self) -> bool:
|
|
193
|
+
"""True if the path is absolute (has both a root and, if applicable,
|
|
194
|
+
a drive)."""
|
|
195
|
+
...
|
|
196
|
+
|
|
197
|
+
def match(self, path_pattern: str | _re.Pattern, *, case_sensitive=None):
|
|
198
|
+
"""
|
|
199
|
+
Return True if this path matches the given pattern.
|
|
200
|
+
"""
|
|
201
|
+
if case_sensitive is None:
|
|
202
|
+
case_sensitive = self._is_case_sensitive
|
|
203
|
+
path = str(self)
|
|
204
|
+
if not isinstance(path_pattern, _re.Pattern):
|
|
205
|
+
if isinstance(str, path_pattern):
|
|
206
|
+
path_pattern = type(self)(path_pattern).__path__()
|
|
207
|
+
path_pattern = _glob.compile_pattern(path_pattern, case_sensitive)
|
|
208
|
+
return path_pattern.match(path) is not None
|
|
209
|
+
|
|
210
|
+
@_utils.notimplemented
|
|
211
|
+
def as_posix(self) -> str: ...
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
PurePathLike = str | Pathname
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Path(Pathname):
|
|
218
|
+
"""Base class for manipulating paths with I/O."""
|
|
219
|
+
|
|
220
|
+
__slots__ = ()
|
|
221
|
+
|
|
222
|
+
def __new__(cls, *args, **kwargs):
|
|
223
|
+
if cls is Path:
|
|
224
|
+
from .fspath import LocalPath
|
|
225
|
+
|
|
226
|
+
cls = LocalPath
|
|
227
|
+
return Pathname.__new__(cls)
|
|
228
|
+
|
|
229
|
+
@_utils.notimplemented
|
|
230
|
+
def stat(self, *, follow_symlinks=True) -> FileStatLike: ...
|
|
231
|
+
|
|
232
|
+
def lstat(self) -> FileStatLike:
|
|
233
|
+
"""
|
|
234
|
+
Like stat(), except if the path points to a symlink, the symlink's
|
|
235
|
+
status information is returned, rather than its target's.
|
|
236
|
+
"""
|
|
237
|
+
return self.stat(follow_symlinks=False)
|
|
238
|
+
|
|
239
|
+
def _st_mode(self, *, follow_symlinks=True):
|
|
240
|
+
try:
|
|
241
|
+
return self.stat().st_mode
|
|
242
|
+
except FileNotFoundError:
|
|
243
|
+
return None
|
|
244
|
+
except OSError as e:
|
|
245
|
+
if not _ignore_error(e):
|
|
246
|
+
raise
|
|
247
|
+
return None
|
|
248
|
+
except ValueError:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
# Convenience functions for querying the stat results
|
|
252
|
+
def exists(self, *, follow_symlinks=True):
|
|
253
|
+
"""
|
|
254
|
+
Whether this path exists.
|
|
255
|
+
"""
|
|
256
|
+
return self._st_mode(follow_symlinks=follow_symlinks) != None
|
|
257
|
+
|
|
258
|
+
def is_hidden(self):
|
|
259
|
+
return self.name.startswith(".")
|
|
260
|
+
|
|
261
|
+
def is_dir(self):
|
|
262
|
+
"""
|
|
263
|
+
Whether this path is a directory.
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
return _stat.S_ISDIR(self._st_mode() or 0)
|
|
267
|
+
except NotImplementedError:
|
|
268
|
+
pass
|
|
269
|
+
try:
|
|
270
|
+
next(self.iterdir(), None)
|
|
271
|
+
return True
|
|
272
|
+
except NotADirectoryError:
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
raise NotImplementedError("is_dir")
|
|
276
|
+
|
|
277
|
+
def is_file(self):
|
|
278
|
+
"""
|
|
279
|
+
Whether this path is a regular file (also True for symlinks pointing
|
|
280
|
+
to regular files).
|
|
281
|
+
"""
|
|
282
|
+
return _stat.S_ISREG(self._st_mode() or 0)
|
|
283
|
+
|
|
284
|
+
def is_symlink(self):
|
|
285
|
+
"""
|
|
286
|
+
Whether this path is a symbolic link.
|
|
287
|
+
"""
|
|
288
|
+
return _stat.S_ISLNK(self._st_mode(follow_symlinks=True) or 0)
|
|
289
|
+
|
|
290
|
+
def is_block_device(self):
|
|
291
|
+
"""
|
|
292
|
+
Whether this path is a block device.
|
|
293
|
+
"""
|
|
294
|
+
return _stat.S_ISBLK(self._st_mode() or 0)
|
|
295
|
+
|
|
296
|
+
def is_char_device(self):
|
|
297
|
+
"""
|
|
298
|
+
Whether this path is a character device.
|
|
299
|
+
"""
|
|
300
|
+
return _stat.S_ISCHR(self._st_mode() or 0)
|
|
301
|
+
|
|
302
|
+
def is_fifo(self):
|
|
303
|
+
"""
|
|
304
|
+
Whether this path is a FIFO.
|
|
305
|
+
"""
|
|
306
|
+
return _stat.S_ISFIFO(self._st_mode() or 0)
|
|
307
|
+
|
|
308
|
+
def is_socket(self):
|
|
309
|
+
"""
|
|
310
|
+
Whether this path is a socket.
|
|
311
|
+
"""
|
|
312
|
+
return _stat.S_ISSOCK(self._st_mode() or 0)
|
|
313
|
+
|
|
314
|
+
@_utils.notimplemented
|
|
315
|
+
def samefile(self, other_path: str | _ty.Self):
|
|
316
|
+
"""Return whether other_path is the same or not as this file
|
|
317
|
+
(as returned by os.path.samefile()).
|
|
318
|
+
"""
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
@_utils.notimplemented
|
|
322
|
+
def _open(
|
|
323
|
+
self,
|
|
324
|
+
mode="r",
|
|
325
|
+
buffering=-1,
|
|
326
|
+
) -> _io.IOBase:
|
|
327
|
+
"""
|
|
328
|
+
All operations should be binary
|
|
329
|
+
To be used only by UriPath.open() to obtain binary stream
|
|
330
|
+
"""
|
|
331
|
+
...
|
|
332
|
+
|
|
333
|
+
def open(
|
|
334
|
+
self,
|
|
335
|
+
mode="r",
|
|
336
|
+
buffering=-1,
|
|
337
|
+
encoding: str = None,
|
|
338
|
+
errors: str = None,
|
|
339
|
+
newline: str = None,
|
|
340
|
+
) -> _io.IOBase:
|
|
341
|
+
"""
|
|
342
|
+
Open the file pointed by this path and return a file object, as
|
|
343
|
+
the built-in open() function does.
|
|
344
|
+
"""
|
|
345
|
+
fh = self._open(mode.replace("b", ""), buffering)
|
|
346
|
+
if "b" not in mode:
|
|
347
|
+
encoding = _io.text_encoding(encoding)
|
|
348
|
+
fh = _io.TextIOWrapper(fh, encoding, errors, newline)
|
|
349
|
+
return fh
|
|
350
|
+
|
|
351
|
+
def read_bytes(self) -> bytes:
|
|
352
|
+
"""
|
|
353
|
+
Open the file in bytes mode, read it, and close the file.
|
|
354
|
+
"""
|
|
355
|
+
with self.open(mode="rb") as f:
|
|
356
|
+
return f.read()
|
|
357
|
+
|
|
358
|
+
def read_text(self, encoding: str = None, errors: str = None) -> str:
|
|
359
|
+
"""
|
|
360
|
+
Open the file in text mode, read it, and close the file.
|
|
361
|
+
"""
|
|
362
|
+
with self.open(mode="r", encoding=encoding, errors=errors) as f:
|
|
363
|
+
return f.read()
|
|
364
|
+
|
|
365
|
+
def write_bytes(self, data: bytes):
|
|
366
|
+
"""
|
|
367
|
+
Open the file in bytes mode, write to it, and close the file.
|
|
368
|
+
"""
|
|
369
|
+
# type-check for the buffer interface before truncating the file
|
|
370
|
+
view = memoryview(data)
|
|
371
|
+
with self.open(mode="wb") as f:
|
|
372
|
+
return f.write(view)
|
|
373
|
+
|
|
374
|
+
def write_text(
|
|
375
|
+
self, data: str, encoding: str = None, errors: str = None, newline: str = None
|
|
376
|
+
):
|
|
377
|
+
"""
|
|
378
|
+
Open the file in text mode, write to it, and close the file.
|
|
379
|
+
"""
|
|
380
|
+
if not isinstance(data, str):
|
|
381
|
+
raise TypeError("data must be str, not %s" % data.__class__.__name__)
|
|
382
|
+
with self.open(
|
|
383
|
+
mode="w", encoding=encoding, errors=errors, newline=newline
|
|
384
|
+
) as f:
|
|
385
|
+
return f.write(data)
|
|
386
|
+
|
|
387
|
+
def __iter__(self):
|
|
388
|
+
return self.iterdir()
|
|
389
|
+
|
|
390
|
+
@_utils.notimplemented
|
|
391
|
+
def iterdir(self) -> "_ty.Iterator[_ty.Self]":
|
|
392
|
+
"""Yield path objects of the directory contents.
|
|
393
|
+
|
|
394
|
+
The children are yielded in arbitrary order, and the
|
|
395
|
+
special entries '.' and '..' are not included.
|
|
396
|
+
"""
|
|
397
|
+
...
|
|
398
|
+
|
|
399
|
+
def glob(
|
|
400
|
+
self,
|
|
401
|
+
pattern: str | _ty.Self,
|
|
402
|
+
*,
|
|
403
|
+
case_sensitive: bool = None,
|
|
404
|
+
include_hidden: bool = False,
|
|
405
|
+
recursive: bool = False,
|
|
406
|
+
dironly: bool = None,
|
|
407
|
+
):
|
|
408
|
+
"""Iterate over this subtree and yield all existing files (of any
|
|
409
|
+
kind, including directories) matching the given relative pattern.
|
|
410
|
+
"""
|
|
411
|
+
yield from _glob.glob(
|
|
412
|
+
self / pattern,
|
|
413
|
+
case_sensitive=case_sensitive,
|
|
414
|
+
include_hidden=include_hidden,
|
|
415
|
+
recursive=recursive,
|
|
416
|
+
dironly=dironly,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def rglob(
|
|
420
|
+
self,
|
|
421
|
+
pattern: str | _ty.Self,
|
|
422
|
+
*,
|
|
423
|
+
case_sensitive: bool = None,
|
|
424
|
+
include_hidden: bool = False,
|
|
425
|
+
dironly: bool = None,
|
|
426
|
+
):
|
|
427
|
+
"""Recursively yield all existing files (of any kind, including
|
|
428
|
+
directories) matching the given relative pattern, anywhere in
|
|
429
|
+
this subtree.
|
|
430
|
+
"""
|
|
431
|
+
yield from self.glob(
|
|
432
|
+
pattern,
|
|
433
|
+
case_sensitive=case_sensitive,
|
|
434
|
+
include_hidden=include_hidden,
|
|
435
|
+
recursive=True,
|
|
436
|
+
dironly=dironly,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def walk(
|
|
440
|
+
self,
|
|
441
|
+
top_down=True,
|
|
442
|
+
on_error: _ty.Callable[[OSError], None] = None,
|
|
443
|
+
follow_symlinks=False,
|
|
444
|
+
):
|
|
445
|
+
"""Walk the directory tree from this directory, similar to os.walk()."""
|
|
446
|
+
paths: "list[_ty.Self|tuple[_ty.Self, list[str], list[str]]]" = [self]
|
|
447
|
+
|
|
448
|
+
while paths:
|
|
449
|
+
path = paths.pop()
|
|
450
|
+
if isinstance(path, tuple):
|
|
451
|
+
yield path
|
|
452
|
+
continue
|
|
453
|
+
try:
|
|
454
|
+
scandir_it = path.iterdir()
|
|
455
|
+
except OSError as error:
|
|
456
|
+
if on_error is not None:
|
|
457
|
+
on_error(error)
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
dirnames: "list[str]" = []
|
|
461
|
+
filenames: "list[str]" = []
|
|
462
|
+
for entry in scandir_it:
|
|
463
|
+
try:
|
|
464
|
+
is_symlink = entry.is_symlink()
|
|
465
|
+
is_dir = entry.is_dir()
|
|
466
|
+
except OSError:
|
|
467
|
+
# Carried over from os.path.isdir().
|
|
468
|
+
is_dir = False
|
|
469
|
+
|
|
470
|
+
if is_dir and (follow_symlinks or not is_symlink):
|
|
471
|
+
dirnames.append(entry.name)
|
|
472
|
+
else:
|
|
473
|
+
filenames.append(entry.name)
|
|
474
|
+
|
|
475
|
+
if top_down:
|
|
476
|
+
yield path, dirnames, filenames
|
|
477
|
+
else:
|
|
478
|
+
paths.append((path, dirnames, filenames))
|
|
479
|
+
|
|
480
|
+
paths += [path / d for d in reversed(dirnames)]
|
|
481
|
+
|
|
482
|
+
def touch(self, mode=0o666, exist_ok=True):
|
|
483
|
+
"""
|
|
484
|
+
Create this file with the given access mode, if it doesn't exist.
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
if exist_ok:
|
|
488
|
+
if self.exists():
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
with self.open("w"):
|
|
492
|
+
...
|
|
493
|
+
try:
|
|
494
|
+
self.chmod(mode)
|
|
495
|
+
except NotImplementedError:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
@_utils.notimplemented
|
|
499
|
+
def _mkdir(self, mode: int): ...
|
|
500
|
+
|
|
501
|
+
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
|
502
|
+
"""
|
|
503
|
+
Create a new directory at this given path.
|
|
504
|
+
"""
|
|
505
|
+
try:
|
|
506
|
+
self._mkdir(mode)
|
|
507
|
+
except FileNotFoundError:
|
|
508
|
+
if not parents or self.parent == self:
|
|
509
|
+
raise
|
|
510
|
+
self.parent.mkdir(parents=True, exist_ok=True)
|
|
511
|
+
self.mkdir(mode, parents=False, exist_ok=exist_ok)
|
|
512
|
+
except OSError:
|
|
513
|
+
# Cannot rely on checking for EEXIST, since the operating system
|
|
514
|
+
# could give priority to other errors like EACCES or EROFS
|
|
515
|
+
if not exist_ok or not self.is_dir():
|
|
516
|
+
raise
|
|
517
|
+
|
|
518
|
+
@_utils.notimplemented
|
|
519
|
+
def chmod(self, mode: int, *, follow_symlinks=True):
|
|
520
|
+
"""
|
|
521
|
+
Change the permissions of the path, like os.chmod().
|
|
522
|
+
"""
|
|
523
|
+
...
|
|
524
|
+
|
|
525
|
+
def lchmod(self, mode: int):
|
|
526
|
+
"""
|
|
527
|
+
Like chmod(), except if the path points to a symlink, the symlink's
|
|
528
|
+
permissions are changed, rather than its target's.
|
|
529
|
+
"""
|
|
530
|
+
self.chmod(mode, follow_symlinks=False)
|
|
531
|
+
|
|
532
|
+
@_utils.notimplemented
|
|
533
|
+
def unlink(self, missing_ok=False):
|
|
534
|
+
"""
|
|
535
|
+
Remove this file or link.
|
|
536
|
+
If the path is a directory, use rmdir() instead.
|
|
537
|
+
"""
|
|
538
|
+
|
|
539
|
+
@_utils.notimplemented
|
|
540
|
+
def rmdir(self):
|
|
541
|
+
"""
|
|
542
|
+
Remove this directory. The directory must be empty.
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
def rm(
|
|
546
|
+
self,
|
|
547
|
+
/,
|
|
548
|
+
recursive=False,
|
|
549
|
+
missing_ok=False,
|
|
550
|
+
ignore_error: bool | _ty.Callable[[Exception, _ty.Self], bool] = False,
|
|
551
|
+
):
|
|
552
|
+
_onerror = lambda _err, _path: (
|
|
553
|
+
ignore_error if not callable(ignore_error) else ignore_error
|
|
554
|
+
)
|
|
555
|
+
try:
|
|
556
|
+
if not self.exists():
|
|
557
|
+
if missing_ok:
|
|
558
|
+
return
|
|
559
|
+
raise FileNotFoundError(self)
|
|
560
|
+
if self.is_dir():
|
|
561
|
+
if recursive:
|
|
562
|
+
for child in self.iterdir():
|
|
563
|
+
child.rm(recursive=recursive, ignore_error=ignore_error)
|
|
564
|
+
self.rmdir()
|
|
565
|
+
else:
|
|
566
|
+
self.unlink()
|
|
567
|
+
except Exception as error:
|
|
568
|
+
if not _onerror(error, self):
|
|
569
|
+
raise
|
|
570
|
+
|
|
571
|
+
@_utils.notimplemented
|
|
572
|
+
def rename(self, target: "_ty.Self | str"): ...
|
|
573
|
+
|
|
574
|
+
def copy(self, target: "Path | str", *, overwrite=False):
|
|
575
|
+
if isinstance(target, str):
|
|
576
|
+
target = type(self)(target)
|
|
577
|
+
src = self
|
|
578
|
+
if src is None:
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
if target.exists():
|
|
582
|
+
if overwrite:
|
|
583
|
+
target.unlink()
|
|
584
|
+
else:
|
|
585
|
+
raise FileExistsError(target)
|
|
586
|
+
|
|
587
|
+
with target.open("wb") as output, src.open("rb") as input:
|
|
588
|
+
_shutil.copyfileobj(input, output)
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
stat = src.stat()
|
|
592
|
+
target.chmod(stat.st_mode)
|
|
593
|
+
except NotImplementedError:
|
|
594
|
+
pass
|
|
595
|
+
|
|
596
|
+
def move(self, target: "Path|str", *, overwrite=False):
|
|
597
|
+
if isinstance(target, str):
|
|
598
|
+
target = type(self)(target)
|
|
599
|
+
src = self
|
|
600
|
+
if src is None:
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
if target.exists():
|
|
604
|
+
if overwrite:
|
|
605
|
+
target.unlink()
|
|
606
|
+
else:
|
|
607
|
+
raise FileExistsError(target)
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
return src.rename(target)
|
|
611
|
+
except NotImplementedError:
|
|
612
|
+
pass
|
|
613
|
+
|
|
614
|
+
src.copy(target)
|
|
615
|
+
src.unlink()
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
PathLike = str | Path
|