python-fsutil 0.14.1__py3-none-any.whl → 0.16.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.
- fsutil/__init__.py +98 -1394
- fsutil/archives.py +163 -0
- fsutil/args.py +13 -0
- fsutil/checks.py +114 -0
- fsutil/converters.py +34 -0
- fsutil/deps.py +13 -0
- fsutil/info.py +187 -0
- fsutil/io.py +231 -0
- fsutil/metadata.py +1 -1
- fsutil/operations.py +538 -0
- fsutil/paths.py +199 -0
- fsutil/perms.py +28 -0
- fsutil/types.py +6 -0
- {python_fsutil-0.14.1.dist-info → python_fsutil-0.16.0.dist-info}/METADATA +9 -9
- python_fsutil-0.16.0.dist-info/RECORD +19 -0
- {python_fsutil-0.14.1.dist-info → python_fsutil-0.16.0.dist-info}/WHEEL +1 -1
- python_fsutil-0.14.1.dist-info/RECORD +0 -8
- {python_fsutil-0.14.1.dist-info → python_fsutil-0.16.0.dist-info/licenses}/LICENSE.txt +0 -0
- {python_fsutil-0.14.1.dist-info → python_fsutil-0.16.0.dist-info}/top_level.txt +0 -0
fsutil/__init__.py
CHANGED
|
@@ -1,27 +1,51 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
3
|
+
from fsutil.archives import (
|
|
4
|
+
create_tar_file,
|
|
5
|
+
create_zip_file,
|
|
6
|
+
extract_tar_file,
|
|
7
|
+
extract_zip_file,
|
|
8
|
+
)
|
|
9
|
+
from fsutil.checks import (
|
|
10
|
+
assert_dir,
|
|
11
|
+
assert_exists,
|
|
12
|
+
assert_file,
|
|
13
|
+
assert_not_dir,
|
|
14
|
+
assert_not_exists,
|
|
15
|
+
assert_not_file,
|
|
16
|
+
exists,
|
|
17
|
+
is_dir,
|
|
18
|
+
is_empty,
|
|
19
|
+
is_empty_dir,
|
|
20
|
+
is_empty_file,
|
|
21
|
+
is_file,
|
|
22
|
+
)
|
|
23
|
+
from fsutil.converters import convert_size_bytes_to_string, convert_size_string_to_bytes
|
|
24
|
+
from fsutil.info import (
|
|
25
|
+
get_dir_creation_date,
|
|
26
|
+
get_dir_creation_date_formatted,
|
|
27
|
+
get_dir_hash,
|
|
28
|
+
get_dir_last_modified_date,
|
|
29
|
+
get_dir_last_modified_date_formatted,
|
|
30
|
+
get_dir_size,
|
|
31
|
+
get_dir_size_formatted,
|
|
32
|
+
get_file_creation_date,
|
|
33
|
+
get_file_creation_date_formatted,
|
|
34
|
+
get_file_hash,
|
|
35
|
+
get_file_last_modified_date,
|
|
36
|
+
get_file_last_modified_date_formatted,
|
|
37
|
+
get_file_size,
|
|
38
|
+
get_file_size_formatted,
|
|
39
|
+
)
|
|
40
|
+
from fsutil.io import (
|
|
41
|
+
read_file,
|
|
42
|
+
read_file_from_url,
|
|
43
|
+
read_file_json,
|
|
44
|
+
read_file_lines,
|
|
45
|
+
read_file_lines_count,
|
|
46
|
+
write_file,
|
|
47
|
+
write_file_json,
|
|
48
|
+
)
|
|
25
49
|
from fsutil.metadata import (
|
|
26
50
|
__author__,
|
|
27
51
|
__copyright__,
|
|
@@ -31,6 +55,54 @@ from fsutil.metadata import (
|
|
|
31
55
|
__title__,
|
|
32
56
|
__version__,
|
|
33
57
|
)
|
|
58
|
+
from fsutil.operations import (
|
|
59
|
+
clean_dir,
|
|
60
|
+
copy_dir,
|
|
61
|
+
copy_dir_content,
|
|
62
|
+
copy_file,
|
|
63
|
+
create_dir,
|
|
64
|
+
create_file,
|
|
65
|
+
delete_dir,
|
|
66
|
+
delete_dir_content,
|
|
67
|
+
delete_dirs,
|
|
68
|
+
delete_file,
|
|
69
|
+
delete_files,
|
|
70
|
+
download_file,
|
|
71
|
+
list_dirs,
|
|
72
|
+
list_files,
|
|
73
|
+
make_dirs,
|
|
74
|
+
make_dirs_for_file,
|
|
75
|
+
move_dir,
|
|
76
|
+
move_file,
|
|
77
|
+
remove_dir,
|
|
78
|
+
remove_dir_content,
|
|
79
|
+
remove_dirs,
|
|
80
|
+
remove_file,
|
|
81
|
+
remove_files,
|
|
82
|
+
rename_dir,
|
|
83
|
+
rename_file,
|
|
84
|
+
rename_file_basename,
|
|
85
|
+
rename_file_extension,
|
|
86
|
+
replace_dir,
|
|
87
|
+
replace_file,
|
|
88
|
+
search_dirs,
|
|
89
|
+
search_files,
|
|
90
|
+
)
|
|
91
|
+
from fsutil.paths import (
|
|
92
|
+
get_file_basename,
|
|
93
|
+
get_file_extension,
|
|
94
|
+
get_filename,
|
|
95
|
+
get_parent_dir,
|
|
96
|
+
get_unique_name,
|
|
97
|
+
join_filename,
|
|
98
|
+
join_filepath,
|
|
99
|
+
join_path,
|
|
100
|
+
split_filename,
|
|
101
|
+
split_filepath,
|
|
102
|
+
split_path,
|
|
103
|
+
transform_filepath,
|
|
104
|
+
)
|
|
105
|
+
from fsutil.perms import get_permissions, set_permissions
|
|
34
106
|
|
|
35
107
|
__all__ = [
|
|
36
108
|
"__author__",
|
|
@@ -54,6 +126,7 @@ __all__ = [
|
|
|
54
126
|
"copy_file",
|
|
55
127
|
"create_dir",
|
|
56
128
|
"create_file",
|
|
129
|
+
"create_tar_file",
|
|
57
130
|
"create_zip_file",
|
|
58
131
|
"delete_dir",
|
|
59
132
|
"delete_dir_content",
|
|
@@ -62,9 +135,11 @@ __all__ = [
|
|
|
62
135
|
"delete_files",
|
|
63
136
|
"download_file",
|
|
64
137
|
"exists",
|
|
138
|
+
"extract_tar_file",
|
|
65
139
|
"extract_zip_file",
|
|
66
140
|
"get_dir_creation_date",
|
|
67
141
|
"get_dir_creation_date_formatted",
|
|
142
|
+
"get_dir_hash",
|
|
68
143
|
"get_dir_last_modified_date",
|
|
69
144
|
"get_dir_last_modified_date_formatted",
|
|
70
145
|
"get_dir_size",
|
|
@@ -118,1378 +193,7 @@ __all__ = [
|
|
|
118
193
|
"split_filename",
|
|
119
194
|
"split_filepath",
|
|
120
195
|
"split_path",
|
|
196
|
+
"transform_filepath",
|
|
121
197
|
"write_file",
|
|
122
198
|
"write_file_json",
|
|
123
199
|
]
|
|
124
|
-
|
|
125
|
-
PathIn = Union[str, pathlib.Path]
|
|
126
|
-
|
|
127
|
-
SIZE_UNITS = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def _require_requests_installed() -> None:
|
|
131
|
-
if not requests_installed:
|
|
132
|
-
raise ModuleNotFoundError(
|
|
133
|
-
"'requests' module is not installed, "
|
|
134
|
-
"it can be installed by running: 'pip install requests'"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _get_path(path: PathIn) -> str:
|
|
139
|
-
if isinstance(path, str):
|
|
140
|
-
return path
|
|
141
|
-
return str(path)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def assert_dir(path: PathIn) -> None:
|
|
145
|
-
"""
|
|
146
|
-
Raise an OSError if the given path doesn't exist or it is not a directory.
|
|
147
|
-
"""
|
|
148
|
-
path = _get_path(path)
|
|
149
|
-
if not is_dir(path):
|
|
150
|
-
raise OSError(f"Invalid directory path: {path}")
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def assert_exists(path: PathIn) -> None:
|
|
154
|
-
"""
|
|
155
|
-
Raise an OSError if the given path doesn't exist.
|
|
156
|
-
"""
|
|
157
|
-
path = _get_path(path)
|
|
158
|
-
if not exists(path):
|
|
159
|
-
raise OSError(f"Invalid item path: {path}")
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def assert_file(path: PathIn) -> None:
|
|
163
|
-
"""
|
|
164
|
-
Raise an OSError if the given path doesn't exist or it is not a file.
|
|
165
|
-
"""
|
|
166
|
-
path = _get_path(path)
|
|
167
|
-
if not is_file(path):
|
|
168
|
-
raise OSError(f"Invalid file path: {path}")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def assert_not_dir(path: PathIn) -> None:
|
|
172
|
-
"""
|
|
173
|
-
Raise an OSError if the given path is an existing directory.
|
|
174
|
-
"""
|
|
175
|
-
path = _get_path(path)
|
|
176
|
-
if is_dir(path):
|
|
177
|
-
raise OSError(f"Invalid path, directory already exists: {path}")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def assert_not_exists(path: PathIn) -> None:
|
|
181
|
-
"""
|
|
182
|
-
Raise an OSError if the given path already exists.
|
|
183
|
-
"""
|
|
184
|
-
path = _get_path(path)
|
|
185
|
-
if exists(path):
|
|
186
|
-
raise OSError(f"Invalid path, item already exists: {path}")
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def assert_not_file(path: PathIn) -> None:
|
|
190
|
-
"""
|
|
191
|
-
Raise an OSError if the given path is an existing file.
|
|
192
|
-
"""
|
|
193
|
-
path = _get_path(path)
|
|
194
|
-
if is_file(path):
|
|
195
|
-
raise OSError(f"Invalid path, file already exists: {path}")
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _clean_dir_empty_dirs(path: PathIn) -> None:
|
|
199
|
-
path = _get_path(path)
|
|
200
|
-
for basepath, dirnames, _ in os.walk(path, topdown=False):
|
|
201
|
-
for dirname in dirnames:
|
|
202
|
-
dirpath = os.path.join(basepath, dirname)
|
|
203
|
-
if is_empty_dir(dirpath):
|
|
204
|
-
remove_dir(dirpath)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def _clean_dir_empty_files(path: PathIn) -> None:
|
|
208
|
-
path = _get_path(path)
|
|
209
|
-
for basepath, _, filenames in os.walk(path, topdown=False):
|
|
210
|
-
for filename in filenames:
|
|
211
|
-
filepath = os.path.join(basepath, filename)
|
|
212
|
-
if is_empty_file(filepath):
|
|
213
|
-
remove_file(filepath)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def clean_dir(path: PathIn, *, dirs: bool = True, files: bool = True) -> None:
|
|
217
|
-
"""
|
|
218
|
-
Clean a directory by removing empty directories and/or empty files.
|
|
219
|
-
"""
|
|
220
|
-
path = _get_path(path)
|
|
221
|
-
assert_dir(path)
|
|
222
|
-
if files:
|
|
223
|
-
_clean_dir_empty_files(path)
|
|
224
|
-
if dirs:
|
|
225
|
-
_clean_dir_empty_dirs(path)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def convert_size_bytes_to_string(size: int) -> str:
|
|
229
|
-
"""
|
|
230
|
-
Convert the given size bytes to string using the right unit suffix.
|
|
231
|
-
"""
|
|
232
|
-
size_num = float(size)
|
|
233
|
-
units = SIZE_UNITS
|
|
234
|
-
factor = 0
|
|
235
|
-
factor_limit = len(units) - 1
|
|
236
|
-
while (size_num >= 1024) and (factor <= factor_limit):
|
|
237
|
-
size_num /= 1024
|
|
238
|
-
factor += 1
|
|
239
|
-
size_units = units[factor]
|
|
240
|
-
size_str = f"{size_num:.2f}" if (factor > 1) else f"{size_num:.0f}"
|
|
241
|
-
size_str = f"{size_str} {size_units}"
|
|
242
|
-
return size_str
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def convert_size_string_to_bytes(size: str) -> float | int:
|
|
246
|
-
"""
|
|
247
|
-
Convert the given size string to bytes.
|
|
248
|
-
"""
|
|
249
|
-
units = [item.lower() for item in SIZE_UNITS]
|
|
250
|
-
parts = size.strip().replace(" ", " ").split(" ")
|
|
251
|
-
amount = float(parts[0])
|
|
252
|
-
unit = parts[1]
|
|
253
|
-
factor = units.index(unit.lower())
|
|
254
|
-
if not factor:
|
|
255
|
-
return amount
|
|
256
|
-
return int((1024**factor) * amount)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def copy_dir(
|
|
260
|
-
path: PathIn, dest: PathIn, *, overwrite: bool = False, **kwargs: Any
|
|
261
|
-
) -> None:
|
|
262
|
-
"""
|
|
263
|
-
Copy the directory at the given path and all its content to dest path.
|
|
264
|
-
If overwrite is not allowed and dest path exists, an OSError is raised.
|
|
265
|
-
More informations about kwargs supported options here:
|
|
266
|
-
https://docs.python.org/3/library/shutil.html#shutil.copytree
|
|
267
|
-
"""
|
|
268
|
-
path = _get_path(path)
|
|
269
|
-
dest = _get_path(dest)
|
|
270
|
-
assert_dir(path)
|
|
271
|
-
dirname = os.path.basename(os.path.normpath(path))
|
|
272
|
-
dest = os.path.join(dest, dirname)
|
|
273
|
-
assert_not_file(dest)
|
|
274
|
-
if not overwrite:
|
|
275
|
-
assert_not_exists(dest)
|
|
276
|
-
copy_dir_content(path, dest, **kwargs)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def copy_dir_content(path: PathIn, dest: PathIn, **kwargs: Any) -> None:
|
|
280
|
-
"""
|
|
281
|
-
Copy the content of the directory at the given path to dest path.
|
|
282
|
-
More informations about kwargs supported options here:
|
|
283
|
-
https://docs.python.org/3/library/shutil.html#shutil.copytree
|
|
284
|
-
"""
|
|
285
|
-
path = _get_path(path)
|
|
286
|
-
dest = _get_path(dest)
|
|
287
|
-
assert_dir(path)
|
|
288
|
-
assert_not_file(dest)
|
|
289
|
-
make_dirs(dest)
|
|
290
|
-
kwargs.setdefault("dirs_exist_ok", True)
|
|
291
|
-
shutil.copytree(path, dest, **kwargs)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def copy_file(
|
|
295
|
-
path: PathIn, dest: PathIn, *, overwrite: bool = False, **kwargs: Any
|
|
296
|
-
) -> None:
|
|
297
|
-
"""
|
|
298
|
-
Copy the file at the given path and its metadata to dest path.
|
|
299
|
-
If overwrite is not allowed and dest path exists, an OSError is raised.
|
|
300
|
-
More informations about kwargs supported options here:
|
|
301
|
-
https://docs.python.org/3/library/shutil.html#shutil.copy2
|
|
302
|
-
"""
|
|
303
|
-
path = _get_path(path)
|
|
304
|
-
dest = _get_path(dest)
|
|
305
|
-
assert_file(path)
|
|
306
|
-
assert_not_dir(dest)
|
|
307
|
-
if not overwrite:
|
|
308
|
-
assert_not_exists(dest)
|
|
309
|
-
make_dirs_for_file(dest)
|
|
310
|
-
shutil.copy2(path, dest, **kwargs)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def create_dir(path: PathIn, *, overwrite: bool = False) -> None:
|
|
314
|
-
"""
|
|
315
|
-
Create directory at the given path.
|
|
316
|
-
If overwrite is not allowed and path exists, an OSError is raised.
|
|
317
|
-
"""
|
|
318
|
-
path = _get_path(path)
|
|
319
|
-
assert_not_file(path)
|
|
320
|
-
if not overwrite:
|
|
321
|
-
assert_not_exists(path)
|
|
322
|
-
make_dirs(path)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def create_file(path: PathIn, content: str = "", *, overwrite: bool = False) -> None:
|
|
326
|
-
"""
|
|
327
|
-
Create file with the specified content at the given path.
|
|
328
|
-
If overwrite is not allowed and path exists, an OSError is raised.
|
|
329
|
-
"""
|
|
330
|
-
path = _get_path(path)
|
|
331
|
-
assert_not_dir(path)
|
|
332
|
-
if not overwrite:
|
|
333
|
-
assert_not_exists(path)
|
|
334
|
-
write_file(path, content)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def create_tar_file(
|
|
338
|
-
path: PathIn,
|
|
339
|
-
content_paths: list[PathIn],
|
|
340
|
-
*,
|
|
341
|
-
overwrite: bool = True,
|
|
342
|
-
compression: str = "",
|
|
343
|
-
) -> None:
|
|
344
|
-
"""
|
|
345
|
-
Create tar file at path compressing directories/files listed in content_paths.
|
|
346
|
-
If overwrite is allowed and dest tar already exists, it will be overwritten.
|
|
347
|
-
"""
|
|
348
|
-
path = _get_path(path)
|
|
349
|
-
assert_not_dir(path)
|
|
350
|
-
if not overwrite:
|
|
351
|
-
assert_not_exists(path)
|
|
352
|
-
make_dirs_for_file(path)
|
|
353
|
-
|
|
354
|
-
def _write_content_to_tar_file(
|
|
355
|
-
file: tarfile.TarFile, content_path: PathIn, basedir: str = ""
|
|
356
|
-
) -> None:
|
|
357
|
-
path = _get_path(content_path)
|
|
358
|
-
assert_exists(path)
|
|
359
|
-
if is_file(path):
|
|
360
|
-
filename = get_filename(path)
|
|
361
|
-
filepath = join_path(basedir, filename)
|
|
362
|
-
file.add(path, filepath)
|
|
363
|
-
elif is_dir(path):
|
|
364
|
-
for item_name in os.listdir(path):
|
|
365
|
-
item_path = join_path(path, item_name)
|
|
366
|
-
item_basedir = (
|
|
367
|
-
join_path(basedir, item_name) if is_dir(item_path) else basedir
|
|
368
|
-
)
|
|
369
|
-
_write_content_to_tar_file(file, item_path, item_basedir)
|
|
370
|
-
|
|
371
|
-
compression = f"w:{compression}" if compression else "w"
|
|
372
|
-
with tarfile.open(path, compression) as file:
|
|
373
|
-
for content_path in content_paths:
|
|
374
|
-
_write_content_to_tar_file(file, content_path)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def create_zip_file(
|
|
378
|
-
path: PathIn,
|
|
379
|
-
content_paths: list[PathIn],
|
|
380
|
-
*,
|
|
381
|
-
overwrite: bool = True,
|
|
382
|
-
compression: int = zipfile.ZIP_DEFLATED,
|
|
383
|
-
) -> None:
|
|
384
|
-
"""
|
|
385
|
-
Create zip file at path compressing directories/files listed in content_paths.
|
|
386
|
-
If overwrite is allowed and dest zip already exists, it will be overwritten.
|
|
387
|
-
"""
|
|
388
|
-
path = _get_path(path)
|
|
389
|
-
assert_not_dir(path)
|
|
390
|
-
if not overwrite:
|
|
391
|
-
assert_not_exists(path)
|
|
392
|
-
make_dirs_for_file(path)
|
|
393
|
-
|
|
394
|
-
def _write_content_to_zip_file(
|
|
395
|
-
file: zipfile.ZipFile, content_path: PathIn, basedir: str = ""
|
|
396
|
-
) -> None:
|
|
397
|
-
path = _get_path(content_path)
|
|
398
|
-
assert_exists(path)
|
|
399
|
-
if is_file(path):
|
|
400
|
-
filename = get_filename(path)
|
|
401
|
-
filepath = join_path(basedir, filename)
|
|
402
|
-
file.write(path, filepath)
|
|
403
|
-
elif is_dir(path):
|
|
404
|
-
for item_name in os.listdir(path):
|
|
405
|
-
item_path = join_path(path, item_name)
|
|
406
|
-
item_basedir = (
|
|
407
|
-
join_path(basedir, item_name) if is_dir(item_path) else basedir
|
|
408
|
-
)
|
|
409
|
-
_write_content_to_zip_file(file, item_path, item_basedir)
|
|
410
|
-
|
|
411
|
-
with zipfile.ZipFile(path, "w", compression) as file:
|
|
412
|
-
for content_path in content_paths:
|
|
413
|
-
_write_content_to_zip_file(file, content_path)
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
def delete_dir(path: PathIn) -> bool:
|
|
417
|
-
"""
|
|
418
|
-
Alias for remove_dir.
|
|
419
|
-
"""
|
|
420
|
-
removed = remove_dir(path)
|
|
421
|
-
return removed
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def delete_dir_content(path: PathIn) -> None:
|
|
425
|
-
"""
|
|
426
|
-
Alias for remove_dir_content.
|
|
427
|
-
"""
|
|
428
|
-
remove_dir_content(path)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def delete_dirs(*paths: PathIn) -> None:
|
|
432
|
-
"""
|
|
433
|
-
Alias for remove_dirs.
|
|
434
|
-
"""
|
|
435
|
-
remove_dirs(*paths)
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def delete_file(path: PathIn) -> bool:
|
|
439
|
-
"""
|
|
440
|
-
Alias for remove_file.
|
|
441
|
-
"""
|
|
442
|
-
removed = remove_file(path)
|
|
443
|
-
return removed
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
def delete_files(*paths: PathIn) -> None:
|
|
447
|
-
"""
|
|
448
|
-
Alias for remove_files.
|
|
449
|
-
"""
|
|
450
|
-
remove_files(*paths)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
def download_file(
|
|
454
|
-
url: str,
|
|
455
|
-
*,
|
|
456
|
-
dirpath: PathIn | None = None,
|
|
457
|
-
filename: str | None = None,
|
|
458
|
-
chunk_size: int = 8192,
|
|
459
|
-
**kwargs: Any,
|
|
460
|
-
) -> str:
|
|
461
|
-
"""
|
|
462
|
-
Download a file from url to dirpath.
|
|
463
|
-
If dirpath is not provided, the file will be downloaded to a temp directory.
|
|
464
|
-
If filename is provided, the file will be named using filename.
|
|
465
|
-
It is possible to pass extra request options
|
|
466
|
-
(eg. for authentication) using **kwargs.
|
|
467
|
-
"""
|
|
468
|
-
_require_requests_installed()
|
|
469
|
-
# https://stackoverflow.com/a/16696317/2096218
|
|
470
|
-
|
|
471
|
-
kwargs["stream"] = True
|
|
472
|
-
with requests.get(url, **kwargs) as response:
|
|
473
|
-
response.raise_for_status()
|
|
474
|
-
|
|
475
|
-
# build filename
|
|
476
|
-
if not filename:
|
|
477
|
-
# detect filename from headers
|
|
478
|
-
content_disposition = response.headers.get("content-disposition", "") or ""
|
|
479
|
-
filename_pattern = r'filename="(.*)"'
|
|
480
|
-
filename_match = re.search(filename_pattern, content_disposition)
|
|
481
|
-
if filename_match:
|
|
482
|
-
filename = filename_match.group(1)
|
|
483
|
-
# or detect filename from url
|
|
484
|
-
if not filename:
|
|
485
|
-
filename = get_filename(url)
|
|
486
|
-
# or fallback to a unique name
|
|
487
|
-
if not filename:
|
|
488
|
-
filename_uuid = str(uuid.uuid4())
|
|
489
|
-
filename = f"download-{filename_uuid}"
|
|
490
|
-
|
|
491
|
-
# build filepath
|
|
492
|
-
dirpath = dirpath or tempfile.gettempdir()
|
|
493
|
-
dirpath = _get_path(dirpath)
|
|
494
|
-
filepath = join_path(dirpath, filename)
|
|
495
|
-
make_dirs_for_file(filepath)
|
|
496
|
-
|
|
497
|
-
# write file to disk
|
|
498
|
-
with open(filepath, "wb") as file:
|
|
499
|
-
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
500
|
-
if chunk:
|
|
501
|
-
file.write(chunk)
|
|
502
|
-
return filepath
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
def exists(path: PathIn) -> bool:
|
|
506
|
-
"""
|
|
507
|
-
Check if a directory of a file exists at the given path.
|
|
508
|
-
"""
|
|
509
|
-
path = _get_path(path)
|
|
510
|
-
return os.path.exists(path)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def extract_tar_file(
|
|
514
|
-
path: PathIn,
|
|
515
|
-
dest: PathIn,
|
|
516
|
-
*,
|
|
517
|
-
autodelete: bool = False,
|
|
518
|
-
content_paths: Iterable[tarfile.TarInfo] | None = None,
|
|
519
|
-
) -> None:
|
|
520
|
-
"""
|
|
521
|
-
Extract tar file at path to dest path.
|
|
522
|
-
If autodelete, the archive will be deleted after extraction.
|
|
523
|
-
If content_paths list is defined,
|
|
524
|
-
only listed items will be extracted, otherwise all.
|
|
525
|
-
"""
|
|
526
|
-
path = _get_path(path)
|
|
527
|
-
dest = _get_path(dest)
|
|
528
|
-
assert_file(path)
|
|
529
|
-
assert_not_file(dest)
|
|
530
|
-
make_dirs(dest)
|
|
531
|
-
with tarfile.TarFile(path, "r") as file:
|
|
532
|
-
file.extractall(dest, members=content_paths)
|
|
533
|
-
if autodelete:
|
|
534
|
-
remove_file(path)
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def extract_zip_file(
|
|
538
|
-
path: PathIn,
|
|
539
|
-
dest: PathIn,
|
|
540
|
-
*,
|
|
541
|
-
autodelete: bool = False,
|
|
542
|
-
content_paths: Iterable[str | zipfile.ZipInfo] | None = None,
|
|
543
|
-
) -> None:
|
|
544
|
-
"""
|
|
545
|
-
Extract zip file at path to dest path.
|
|
546
|
-
If autodelete, the archive will be deleted after extraction.
|
|
547
|
-
If content_paths list is defined,
|
|
548
|
-
only listed items will be extracted, otherwise all.
|
|
549
|
-
"""
|
|
550
|
-
path = _get_path(path)
|
|
551
|
-
dest = _get_path(dest)
|
|
552
|
-
assert_file(path)
|
|
553
|
-
assert_not_file(dest)
|
|
554
|
-
make_dirs(dest)
|
|
555
|
-
with zipfile.ZipFile(path, "r") as file:
|
|
556
|
-
file.extractall(dest, members=content_paths)
|
|
557
|
-
if autodelete:
|
|
558
|
-
remove_file(path)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
def _filter_paths(
|
|
562
|
-
basepath: str,
|
|
563
|
-
relpaths: list[str],
|
|
564
|
-
*,
|
|
565
|
-
predicate: Callable[[str], bool] | None = None,
|
|
566
|
-
) -> list[str]:
|
|
567
|
-
"""
|
|
568
|
-
Filter paths relative to basepath according to the optional predicate function.
|
|
569
|
-
If predicate is defined, paths are filtered using it,
|
|
570
|
-
otherwise all paths will be listed.
|
|
571
|
-
"""
|
|
572
|
-
paths = []
|
|
573
|
-
for relpath in relpaths:
|
|
574
|
-
abspath = os.path.join(basepath, relpath)
|
|
575
|
-
if predicate is None or predicate(abspath):
|
|
576
|
-
paths.append(abspath)
|
|
577
|
-
paths.sort()
|
|
578
|
-
return paths
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
def get_dir_creation_date(path: PathIn) -> datetime:
|
|
582
|
-
"""
|
|
583
|
-
Get the directory creation date.
|
|
584
|
-
"""
|
|
585
|
-
path = _get_path(path)
|
|
586
|
-
assert_dir(path)
|
|
587
|
-
creation_timestamp = os.path.getctime(path)
|
|
588
|
-
creation_date = datetime.fromtimestamp(creation_timestamp)
|
|
589
|
-
return creation_date
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def get_dir_creation_date_formatted(
|
|
593
|
-
path: PathIn, *, format: str = "%Y-%m-%d %H:%M:%S"
|
|
594
|
-
) -> str:
|
|
595
|
-
"""
|
|
596
|
-
Get the directory creation date formatted using the given format.
|
|
597
|
-
"""
|
|
598
|
-
path = _get_path(path)
|
|
599
|
-
date = get_dir_creation_date(path)
|
|
600
|
-
return date.strftime(format)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
def get_dir_hash(path: PathIn, *, func: str = "md5") -> str:
|
|
604
|
-
"""
|
|
605
|
-
Get the hash of the directory at the given path using
|
|
606
|
-
the specified algorithm function (md5 by default).
|
|
607
|
-
"""
|
|
608
|
-
path = _get_path(path)
|
|
609
|
-
assert_dir(path)
|
|
610
|
-
hash = hashlib.new(func)
|
|
611
|
-
files = search_files(path)
|
|
612
|
-
for file in sorted(files):
|
|
613
|
-
file_hash = get_file_hash(file, func=func)
|
|
614
|
-
file_hash_b = bytes(file_hash, "utf-8")
|
|
615
|
-
hash.update(file_hash_b)
|
|
616
|
-
hash_hex = hash.hexdigest()
|
|
617
|
-
return hash_hex
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
def get_dir_last_modified_date(path: PathIn) -> datetime:
|
|
621
|
-
"""
|
|
622
|
-
Get the directory last modification date.
|
|
623
|
-
"""
|
|
624
|
-
path = _get_path(path)
|
|
625
|
-
assert_dir(path)
|
|
626
|
-
last_modified_timestamp = os.path.getmtime(path)
|
|
627
|
-
for basepath, dirnames, filenames in os.walk(path):
|
|
628
|
-
for dirname in dirnames:
|
|
629
|
-
dirpath = os.path.join(basepath, dirname)
|
|
630
|
-
last_modified_timestamp = max(
|
|
631
|
-
last_modified_timestamp, os.path.getmtime(dirpath)
|
|
632
|
-
)
|
|
633
|
-
for filename in filenames:
|
|
634
|
-
filepath = os.path.join(basepath, filename)
|
|
635
|
-
last_modified_timestamp = max(
|
|
636
|
-
last_modified_timestamp, os.path.getmtime(filepath)
|
|
637
|
-
)
|
|
638
|
-
last_modified_date = datetime.fromtimestamp(last_modified_timestamp)
|
|
639
|
-
return last_modified_date
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
def get_dir_last_modified_date_formatted(
|
|
643
|
-
path: PathIn, *, format: str = "%Y-%m-%d %H:%M:%S"
|
|
644
|
-
) -> str:
|
|
645
|
-
"""
|
|
646
|
-
Get the directory last modification date formatted using the given format.
|
|
647
|
-
"""
|
|
648
|
-
path = _get_path(path)
|
|
649
|
-
date = get_dir_last_modified_date(path)
|
|
650
|
-
return date.strftime(format)
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
def get_dir_size(path: PathIn) -> int:
|
|
654
|
-
"""
|
|
655
|
-
Get the directory size in bytes.
|
|
656
|
-
"""
|
|
657
|
-
path = _get_path(path)
|
|
658
|
-
assert_dir(path)
|
|
659
|
-
size = 0
|
|
660
|
-
for basepath, _, filenames in os.walk(path):
|
|
661
|
-
for filename in filenames:
|
|
662
|
-
filepath = os.path.join(basepath, filename)
|
|
663
|
-
if not os.path.islink(filepath):
|
|
664
|
-
size += get_file_size(filepath)
|
|
665
|
-
return size
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
def get_dir_size_formatted(path: PathIn) -> str:
|
|
669
|
-
"""
|
|
670
|
-
Get the directory size formatted using the right unit suffix.
|
|
671
|
-
"""
|
|
672
|
-
size = get_dir_size(path)
|
|
673
|
-
size_formatted = convert_size_bytes_to_string(size)
|
|
674
|
-
return size_formatted
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
def get_file_basename(path: PathIn) -> str:
|
|
678
|
-
"""
|
|
679
|
-
Get the file basename from the given path/url.
|
|
680
|
-
"""
|
|
681
|
-
path = _get_path(path)
|
|
682
|
-
basename, _ = split_filename(path)
|
|
683
|
-
return basename
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
def get_file_creation_date(path: PathIn) -> datetime:
|
|
687
|
-
"""
|
|
688
|
-
Get the file creation date.
|
|
689
|
-
"""
|
|
690
|
-
path = _get_path(path)
|
|
691
|
-
assert_file(path)
|
|
692
|
-
creation_timestamp = os.path.getctime(path)
|
|
693
|
-
creation_date = datetime.fromtimestamp(creation_timestamp)
|
|
694
|
-
return creation_date
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
def get_file_creation_date_formatted(
|
|
698
|
-
path: PathIn, *, format: str = "%Y-%m-%d %H:%M:%S"
|
|
699
|
-
) -> str:
|
|
700
|
-
"""
|
|
701
|
-
Get the file creation date formatted using the given format.
|
|
702
|
-
"""
|
|
703
|
-
path = _get_path(path)
|
|
704
|
-
date = get_file_creation_date(path)
|
|
705
|
-
return date.strftime(format)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
def get_file_extension(path: PathIn) -> str:
|
|
709
|
-
"""
|
|
710
|
-
Get the file extension from the given path/url.
|
|
711
|
-
"""
|
|
712
|
-
path = _get_path(path)
|
|
713
|
-
_, extension = split_filename(path)
|
|
714
|
-
return extension
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
def get_file_hash(path: PathIn, *, func: str = "md5") -> str:
|
|
718
|
-
"""
|
|
719
|
-
Get the hash of the file at the given path using
|
|
720
|
-
the specified algorithm function (md5 by default).
|
|
721
|
-
"""
|
|
722
|
-
path = _get_path(path)
|
|
723
|
-
assert_file(path)
|
|
724
|
-
hash = hashlib.new(func)
|
|
725
|
-
with open(path, "rb") as file:
|
|
726
|
-
for chunk in iter(lambda: file.read(4096), b""):
|
|
727
|
-
hash.update(chunk)
|
|
728
|
-
hash_hex = hash.hexdigest()
|
|
729
|
-
return hash_hex
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
def get_file_last_modified_date(path: PathIn) -> datetime:
|
|
733
|
-
"""
|
|
734
|
-
Get the file last modification date.
|
|
735
|
-
"""
|
|
736
|
-
path = _get_path(path)
|
|
737
|
-
assert_file(path)
|
|
738
|
-
last_modified_timestamp = os.path.getmtime(path)
|
|
739
|
-
last_modified_date = datetime.fromtimestamp(last_modified_timestamp)
|
|
740
|
-
return last_modified_date
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
def get_file_last_modified_date_formatted(
|
|
744
|
-
path: PathIn, *, format: str = "%Y-%m-%d %H:%M:%S"
|
|
745
|
-
) -> str:
|
|
746
|
-
"""
|
|
747
|
-
Get the file last modification date formatted using the given format.
|
|
748
|
-
"""
|
|
749
|
-
path = _get_path(path)
|
|
750
|
-
date = get_file_last_modified_date(path)
|
|
751
|
-
return date.strftime(format)
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
def get_file_size(path: PathIn) -> int:
|
|
755
|
-
"""
|
|
756
|
-
Get the directory size in bytes.
|
|
757
|
-
"""
|
|
758
|
-
path = _get_path(path)
|
|
759
|
-
assert_file(path)
|
|
760
|
-
# size = os.stat(path).st_size
|
|
761
|
-
size = os.path.getsize(path)
|
|
762
|
-
return size
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
def get_file_size_formatted(path: PathIn) -> str:
|
|
766
|
-
"""
|
|
767
|
-
Get the directory size formatted using the right unit suffix.
|
|
768
|
-
"""
|
|
769
|
-
path = _get_path(path)
|
|
770
|
-
size = get_file_size(path)
|
|
771
|
-
size_formatted = convert_size_bytes_to_string(size)
|
|
772
|
-
return size_formatted
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
def get_filename(path: PathIn) -> str:
|
|
776
|
-
"""
|
|
777
|
-
Get the filename from the given path/url.
|
|
778
|
-
"""
|
|
779
|
-
path = _get_path(path)
|
|
780
|
-
filepath = urlsplit(path).path
|
|
781
|
-
filename = os.path.basename(filepath)
|
|
782
|
-
return filename
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
def get_parent_dir(path: PathIn, *, levels: int = 1) -> str:
|
|
786
|
-
"""
|
|
787
|
-
Get the parent directory for the given path going up N levels.
|
|
788
|
-
"""
|
|
789
|
-
path = _get_path(path)
|
|
790
|
-
return join_path(path, *([os.pardir] * max(1, levels)))
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
def get_permissions(path: PathIn) -> int:
|
|
794
|
-
"""
|
|
795
|
-
Get the file/directory permissions.
|
|
796
|
-
"""
|
|
797
|
-
path = _get_path(path)
|
|
798
|
-
assert_exists(path)
|
|
799
|
-
st_mode = os.stat(path).st_mode
|
|
800
|
-
permissions = int(str(oct(st_mode & 0o777))[2:])
|
|
801
|
-
return permissions
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
def get_unique_name(
|
|
805
|
-
path: PathIn,
|
|
806
|
-
*,
|
|
807
|
-
prefix: str = "",
|
|
808
|
-
suffix: str = "",
|
|
809
|
-
extension: str = "",
|
|
810
|
-
separator: str = "-",
|
|
811
|
-
) -> str:
|
|
812
|
-
"""
|
|
813
|
-
Get a unique name for a directory/file ath the given directory path.
|
|
814
|
-
"""
|
|
815
|
-
path = _get_path(path)
|
|
816
|
-
assert_dir(path)
|
|
817
|
-
name = ""
|
|
818
|
-
while True:
|
|
819
|
-
if prefix:
|
|
820
|
-
name += f"{prefix}{separator}"
|
|
821
|
-
uid = uuid.uuid4()
|
|
822
|
-
name += f"{uid}"
|
|
823
|
-
if suffix:
|
|
824
|
-
name += f"{separator}{suffix}"
|
|
825
|
-
if extension:
|
|
826
|
-
extension = extension.lstrip(".").lower()
|
|
827
|
-
name += f".{extension}"
|
|
828
|
-
if exists(join_path(path, name)):
|
|
829
|
-
continue
|
|
830
|
-
break
|
|
831
|
-
return name
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
def is_dir(path: PathIn) -> bool:
|
|
835
|
-
"""
|
|
836
|
-
Determine whether the specified path represents an existing directory.
|
|
837
|
-
"""
|
|
838
|
-
path = _get_path(path)
|
|
839
|
-
return os.path.isdir(path)
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
def is_empty(path: PathIn) -> bool:
|
|
843
|
-
"""
|
|
844
|
-
Determine whether the specified path represents an empty directory or an empty file.
|
|
845
|
-
"""
|
|
846
|
-
path = _get_path(path)
|
|
847
|
-
assert_exists(path)
|
|
848
|
-
if is_dir(path):
|
|
849
|
-
return is_empty_dir(path)
|
|
850
|
-
return is_empty_file(path)
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
def is_empty_dir(path: PathIn) -> bool:
|
|
854
|
-
"""
|
|
855
|
-
Determine whether the specified path represents an empty directory.
|
|
856
|
-
"""
|
|
857
|
-
path = _get_path(path)
|
|
858
|
-
assert_dir(path)
|
|
859
|
-
return len(os.listdir(path)) == 0
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
def is_empty_file(path: PathIn) -> bool:
|
|
863
|
-
"""
|
|
864
|
-
Determine whether the specified path represents an empty file.
|
|
865
|
-
"""
|
|
866
|
-
path = _get_path(path)
|
|
867
|
-
return get_file_size(path) == 0
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
def is_file(path: PathIn) -> bool:
|
|
871
|
-
"""
|
|
872
|
-
Determine whether the specified path represents an existing file.
|
|
873
|
-
"""
|
|
874
|
-
path = _get_path(path)
|
|
875
|
-
return os.path.isfile(path)
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
def join_filename(basename: str, extension: str) -> str:
|
|
879
|
-
"""
|
|
880
|
-
Create a filename joining the file basename and the extension.
|
|
881
|
-
"""
|
|
882
|
-
basename = basename.rstrip(".").strip()
|
|
883
|
-
extension = extension.replace(".", "").strip()
|
|
884
|
-
if basename and extension:
|
|
885
|
-
filename = f"{basename}.{extension}"
|
|
886
|
-
return filename
|
|
887
|
-
return basename or extension
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
def join_filepath(dirpath: PathIn, filename: str) -> str:
|
|
891
|
-
"""
|
|
892
|
-
Create a filepath joining the directory path and the filename.
|
|
893
|
-
"""
|
|
894
|
-
dirpath = _get_path(dirpath)
|
|
895
|
-
return join_path(dirpath, filename)
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
def join_path(path: PathIn, *paths: PathIn) -> str:
|
|
899
|
-
"""
|
|
900
|
-
Create a path joining path and paths.
|
|
901
|
-
If path is __file__ (or a .py file), the resulting path will be relative
|
|
902
|
-
to the directory path of the module in which it's used.
|
|
903
|
-
"""
|
|
904
|
-
path = _get_path(path)
|
|
905
|
-
basepath = path
|
|
906
|
-
if get_file_extension(path) in ["py", "pyc", "pyo"]:
|
|
907
|
-
basepath = os.path.dirname(os.path.realpath(path))
|
|
908
|
-
paths_str = [_get_path(path).lstrip(os.sep) for path in paths]
|
|
909
|
-
return os.path.normpath(os.path.join(basepath, *paths_str))
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
def list_dirs(path: PathIn) -> list[str]:
|
|
913
|
-
"""
|
|
914
|
-
List all directories contained at the given directory path.
|
|
915
|
-
"""
|
|
916
|
-
path = _get_path(path)
|
|
917
|
-
return _filter_paths(path, os.listdir(path), predicate=is_dir)
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
def list_files(path: PathIn) -> list[str]:
|
|
921
|
-
"""
|
|
922
|
-
List all files contained at the given directory path.
|
|
923
|
-
"""
|
|
924
|
-
path = _get_path(path)
|
|
925
|
-
return _filter_paths(path, os.listdir(path), predicate=is_file)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
def make_dirs(path: PathIn) -> None:
|
|
929
|
-
"""
|
|
930
|
-
Create the directories needed to ensure that the given path exists.
|
|
931
|
-
If a file already exists at the given path an OSError is raised.
|
|
932
|
-
"""
|
|
933
|
-
path = _get_path(path)
|
|
934
|
-
if is_dir(path):
|
|
935
|
-
return
|
|
936
|
-
assert_not_file(path)
|
|
937
|
-
os.makedirs(path, exist_ok=True)
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
def make_dirs_for_file(path: PathIn) -> None:
|
|
941
|
-
"""
|
|
942
|
-
Create the directories needed to ensure that the given path exists.
|
|
943
|
-
If a directory already exists at the given path an OSError is raised.
|
|
944
|
-
"""
|
|
945
|
-
path = _get_path(path)
|
|
946
|
-
if is_file(path):
|
|
947
|
-
return
|
|
948
|
-
assert_not_dir(path)
|
|
949
|
-
dirpath, _ = split_filepath(path)
|
|
950
|
-
if dirpath:
|
|
951
|
-
make_dirs(dirpath)
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
def move_dir(
|
|
955
|
-
path: PathIn, dest: PathIn, *, overwrite: bool = False, **kwargs: Any
|
|
956
|
-
) -> None:
|
|
957
|
-
"""
|
|
958
|
-
Move an existing dir from path to dest directory.
|
|
959
|
-
If overwrite is not allowed and dest path exists, an OSError is raised.
|
|
960
|
-
More informations about kwargs supported options here:
|
|
961
|
-
https://docs.python.org/3/library/shutil.html#shutil.move
|
|
962
|
-
"""
|
|
963
|
-
path = _get_path(path)
|
|
964
|
-
dest = _get_path(dest)
|
|
965
|
-
assert_dir(path)
|
|
966
|
-
assert_not_file(dest)
|
|
967
|
-
if not overwrite:
|
|
968
|
-
assert_not_exists(dest)
|
|
969
|
-
make_dirs(dest)
|
|
970
|
-
shutil.move(path, dest, **kwargs)
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
def move_file(
|
|
974
|
-
path: PathIn, dest: PathIn, *, overwrite: bool = False, **kwargs: Any
|
|
975
|
-
) -> None:
|
|
976
|
-
"""
|
|
977
|
-
Move an existing file from path to dest directory.
|
|
978
|
-
If overwrite is not allowed and dest path exists, an OSError is raised.
|
|
979
|
-
More informations about kwargs supported options here:
|
|
980
|
-
https://docs.python.org/3/library/shutil.html#shutil.move
|
|
981
|
-
"""
|
|
982
|
-
path = _get_path(path)
|
|
983
|
-
dest = _get_path(dest)
|
|
984
|
-
assert_file(path)
|
|
985
|
-
assert_not_file(dest)
|
|
986
|
-
dest = os.path.join(dest, get_filename(path))
|
|
987
|
-
assert_not_dir(dest)
|
|
988
|
-
if not overwrite:
|
|
989
|
-
assert_not_exists(dest)
|
|
990
|
-
make_dirs_for_file(dest)
|
|
991
|
-
shutil.move(path, dest, **kwargs)
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
def read_file(path: PathIn, *, encoding: str = "utf-8") -> str:
|
|
995
|
-
"""
|
|
996
|
-
Read the content of the file at the given path using the specified encoding.
|
|
997
|
-
"""
|
|
998
|
-
path = _get_path(path)
|
|
999
|
-
assert_file(path)
|
|
1000
|
-
content = ""
|
|
1001
|
-
with open(path, encoding=encoding) as file:
|
|
1002
|
-
content = file.read()
|
|
1003
|
-
return content
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
def read_file_from_url(url: str, **kwargs: Any) -> str:
|
|
1007
|
-
"""
|
|
1008
|
-
Read the content of the file at the given url.
|
|
1009
|
-
"""
|
|
1010
|
-
_require_requests_installed()
|
|
1011
|
-
response = requests.get(url, **kwargs)
|
|
1012
|
-
response.raise_for_status()
|
|
1013
|
-
content = response.text
|
|
1014
|
-
return content
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
def read_file_json(path: PathIn, **kwargs: Any) -> Any:
|
|
1018
|
-
"""
|
|
1019
|
-
Read and decode a json encoded file at the given path.
|
|
1020
|
-
"""
|
|
1021
|
-
path = _get_path(path)
|
|
1022
|
-
content = read_file(path)
|
|
1023
|
-
data = json.loads(content, **kwargs)
|
|
1024
|
-
return data
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
def _read_file_lines_in_range(
|
|
1028
|
-
path: PathIn,
|
|
1029
|
-
*,
|
|
1030
|
-
line_start: int = 0,
|
|
1031
|
-
line_end: int = -1,
|
|
1032
|
-
encoding: str = "utf-8",
|
|
1033
|
-
) -> Generator[str, None, None]:
|
|
1034
|
-
path = _get_path(path)
|
|
1035
|
-
line_start_negative = line_start < 0
|
|
1036
|
-
line_end_negative = line_end < 0
|
|
1037
|
-
if line_start_negative or line_end_negative:
|
|
1038
|
-
# pre-calculate lines count only if using negative line indexes
|
|
1039
|
-
lines_count = read_file_lines_count(path)
|
|
1040
|
-
# normalize negative indexes
|
|
1041
|
-
if line_start_negative:
|
|
1042
|
-
line_start = max(0, line_start + lines_count)
|
|
1043
|
-
if line_end_negative:
|
|
1044
|
-
line_end = min(line_end + lines_count, lines_count - 1)
|
|
1045
|
-
with open(path, "rb") as file:
|
|
1046
|
-
file.seek(0)
|
|
1047
|
-
line_index = 0
|
|
1048
|
-
for line in file:
|
|
1049
|
-
if line_index >= line_start and line_index <= line_end:
|
|
1050
|
-
yield line.decode(encoding)
|
|
1051
|
-
line_index += 1
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
def read_file_lines(
|
|
1055
|
-
path: PathIn,
|
|
1056
|
-
*,
|
|
1057
|
-
line_start: int = 0,
|
|
1058
|
-
line_end: int = -1,
|
|
1059
|
-
strip_white: bool = True,
|
|
1060
|
-
skip_empty: bool = True,
|
|
1061
|
-
encoding: str = "utf-8",
|
|
1062
|
-
) -> list[str]:
|
|
1063
|
-
"""
|
|
1064
|
-
Read file content lines.
|
|
1065
|
-
It is possible to specify the line indexes (negative indexes too),
|
|
1066
|
-
very useful especially when reading large files.
|
|
1067
|
-
"""
|
|
1068
|
-
path = _get_path(path)
|
|
1069
|
-
assert_file(path)
|
|
1070
|
-
if line_start == 0 and line_end == -1:
|
|
1071
|
-
content = read_file(path, encoding=encoding)
|
|
1072
|
-
lines = content.splitlines()
|
|
1073
|
-
else:
|
|
1074
|
-
lines = list(
|
|
1075
|
-
_read_file_lines_in_range(
|
|
1076
|
-
path,
|
|
1077
|
-
line_start=line_start,
|
|
1078
|
-
line_end=line_end,
|
|
1079
|
-
encoding=encoding,
|
|
1080
|
-
)
|
|
1081
|
-
)
|
|
1082
|
-
if strip_white:
|
|
1083
|
-
lines = [line.strip() for line in lines]
|
|
1084
|
-
if skip_empty:
|
|
1085
|
-
lines = [line for line in lines if line]
|
|
1086
|
-
return lines
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
def read_file_lines_count(path: PathIn) -> int:
|
|
1090
|
-
"""
|
|
1091
|
-
Read file lines count.
|
|
1092
|
-
"""
|
|
1093
|
-
path = _get_path(path)
|
|
1094
|
-
assert_file(path)
|
|
1095
|
-
lines_count = 0
|
|
1096
|
-
with open(path, "rb") as file:
|
|
1097
|
-
file.seek(0)
|
|
1098
|
-
lines_count = sum(1 for line in file)
|
|
1099
|
-
return lines_count
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
def remove_dir(path: PathIn, **kwargs: Any) -> bool:
|
|
1103
|
-
"""
|
|
1104
|
-
Remove a directory at the given path and all its content.
|
|
1105
|
-
If the directory is removed with success returns True, otherwise False.
|
|
1106
|
-
More informations about kwargs supported options here:
|
|
1107
|
-
https://docs.python.org/3/library/shutil.html#shutil.rmtree
|
|
1108
|
-
"""
|
|
1109
|
-
path = _get_path(path)
|
|
1110
|
-
if not exists(path):
|
|
1111
|
-
return False
|
|
1112
|
-
assert_dir(path)
|
|
1113
|
-
shutil.rmtree(path, **kwargs)
|
|
1114
|
-
return not exists(path)
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
def remove_dir_content(path: PathIn) -> None:
|
|
1118
|
-
"""
|
|
1119
|
-
Removes all directory content (both sub-directories and files).
|
|
1120
|
-
"""
|
|
1121
|
-
path = _get_path(path)
|
|
1122
|
-
assert_dir(path)
|
|
1123
|
-
remove_dirs(*list_dirs(path))
|
|
1124
|
-
remove_files(*list_files(path))
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
def remove_dirs(*paths: PathIn) -> None:
|
|
1128
|
-
"""
|
|
1129
|
-
Remove multiple directories at the given paths and all their content.
|
|
1130
|
-
"""
|
|
1131
|
-
for path in paths:
|
|
1132
|
-
remove_dir(path)
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
def remove_file(path: PathIn) -> bool:
|
|
1136
|
-
"""
|
|
1137
|
-
Remove a file at the given path.
|
|
1138
|
-
If the file is removed with success returns True, otherwise False.
|
|
1139
|
-
"""
|
|
1140
|
-
path = _get_path(path)
|
|
1141
|
-
if not exists(path):
|
|
1142
|
-
return False
|
|
1143
|
-
assert_file(path)
|
|
1144
|
-
os.remove(path)
|
|
1145
|
-
return not exists(path)
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
def remove_files(*paths: PathIn) -> None:
|
|
1149
|
-
"""
|
|
1150
|
-
Remove multiple files at the given paths.
|
|
1151
|
-
"""
|
|
1152
|
-
for path in paths:
|
|
1153
|
-
remove_file(path)
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
def rename_dir(path: PathIn, name: str) -> None:
|
|
1157
|
-
"""
|
|
1158
|
-
Rename a directory with the given name.
|
|
1159
|
-
If a directory or a file with the given name already exists, an OSError is raised.
|
|
1160
|
-
"""
|
|
1161
|
-
path = _get_path(path)
|
|
1162
|
-
assert_dir(path)
|
|
1163
|
-
comps = list(os.path.split(path))
|
|
1164
|
-
comps[-1] = name
|
|
1165
|
-
dest = os.path.join(*comps)
|
|
1166
|
-
assert_not_exists(dest)
|
|
1167
|
-
os.rename(path, dest)
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
def rename_file(path: PathIn, name: str) -> None:
|
|
1171
|
-
"""
|
|
1172
|
-
Rename a file with the given name.
|
|
1173
|
-
If a directory or a file with the given name already exists, an OSError is raised.
|
|
1174
|
-
"""
|
|
1175
|
-
path = _get_path(path)
|
|
1176
|
-
assert_file(path)
|
|
1177
|
-
dirpath, filename = split_filepath(path)
|
|
1178
|
-
dest = join_filepath(dirpath, name)
|
|
1179
|
-
assert_not_exists(dest)
|
|
1180
|
-
os.rename(path, dest)
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
def rename_file_basename(path: PathIn, basename: str) -> None:
|
|
1184
|
-
"""
|
|
1185
|
-
Rename a file basename with the given basename.
|
|
1186
|
-
"""
|
|
1187
|
-
path = _get_path(path)
|
|
1188
|
-
extension = get_file_extension(path)
|
|
1189
|
-
filename = join_filename(basename, extension)
|
|
1190
|
-
rename_file(path, filename)
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
def rename_file_extension(path: PathIn, extension: str) -> None:
|
|
1194
|
-
"""
|
|
1195
|
-
Rename a file extension with the given extension.
|
|
1196
|
-
"""
|
|
1197
|
-
path = _get_path(path)
|
|
1198
|
-
basename = get_file_basename(path)
|
|
1199
|
-
filename = join_filename(basename, extension)
|
|
1200
|
-
rename_file(path, filename)
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
def replace_dir(path: PathIn, src: PathIn, *, autodelete: bool = False) -> None:
|
|
1204
|
-
"""
|
|
1205
|
-
Replace directory at the specified path with the directory located at src.
|
|
1206
|
-
If autodelete, the src directory will be removed at the end of the operation.
|
|
1207
|
-
Optimized for large files.
|
|
1208
|
-
"""
|
|
1209
|
-
path = _get_path(path)
|
|
1210
|
-
src = _get_path(src)
|
|
1211
|
-
assert_not_file(path)
|
|
1212
|
-
assert_dir(src)
|
|
1213
|
-
|
|
1214
|
-
if path == src:
|
|
1215
|
-
return
|
|
1216
|
-
|
|
1217
|
-
make_dirs(path)
|
|
1218
|
-
|
|
1219
|
-
dirpath, dirname = split_filepath(path)
|
|
1220
|
-
# safe temporary name to avoid clashes with existing files/directories
|
|
1221
|
-
temp_dirname = get_unique_name(dirpath)
|
|
1222
|
-
temp_dest = join_path(dirpath, temp_dirname)
|
|
1223
|
-
copy_dir_content(src, temp_dest)
|
|
1224
|
-
|
|
1225
|
-
if exists(path):
|
|
1226
|
-
temp_dirname = get_unique_name(dirpath)
|
|
1227
|
-
temp_path = join_path(dirpath, temp_dirname)
|
|
1228
|
-
rename_dir(path=path, name=temp_dirname)
|
|
1229
|
-
rename_dir(path=temp_dest, name=dirname)
|
|
1230
|
-
remove_dir(path=temp_path)
|
|
1231
|
-
else:
|
|
1232
|
-
rename_dir(path=temp_dest, name=dirname)
|
|
1233
|
-
|
|
1234
|
-
if autodelete:
|
|
1235
|
-
remove_dir(path=src)
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
def replace_file(path: PathIn, src: PathIn, *, autodelete: bool = False) -> None:
|
|
1239
|
-
"""
|
|
1240
|
-
Replace file at the specified path with the file located at src.
|
|
1241
|
-
If autodelete, the src file will be removed at the end of the operation.
|
|
1242
|
-
Optimized for large files.
|
|
1243
|
-
"""
|
|
1244
|
-
path = _get_path(path)
|
|
1245
|
-
src = _get_path(src)
|
|
1246
|
-
assert_not_dir(path)
|
|
1247
|
-
assert_file(src)
|
|
1248
|
-
if path == src:
|
|
1249
|
-
return
|
|
1250
|
-
|
|
1251
|
-
make_dirs_for_file(path)
|
|
1252
|
-
|
|
1253
|
-
dirpath, filename = split_filepath(path)
|
|
1254
|
-
_, extension = split_filename(filename)
|
|
1255
|
-
# safe temporary name to avoid clashes with existing files/directories
|
|
1256
|
-
temp_filename = get_unique_name(dirpath, extension=extension)
|
|
1257
|
-
temp_dest = join_path(dirpath, temp_filename)
|
|
1258
|
-
copy_file(path=src, dest=temp_dest, overwrite=False)
|
|
1259
|
-
|
|
1260
|
-
if exists(path):
|
|
1261
|
-
temp_filename = get_unique_name(dirpath, extension=extension)
|
|
1262
|
-
temp_path = join_path(dirpath, temp_filename)
|
|
1263
|
-
rename_file(path=path, name=temp_filename)
|
|
1264
|
-
rename_file(path=temp_dest, name=filename)
|
|
1265
|
-
remove_file(path=temp_path)
|
|
1266
|
-
else:
|
|
1267
|
-
rename_file(path=temp_dest, name=filename)
|
|
1268
|
-
|
|
1269
|
-
if autodelete:
|
|
1270
|
-
remove_file(path=src)
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
def _search_paths(path: PathIn, pattern: str) -> list[str]:
|
|
1274
|
-
"""
|
|
1275
|
-
Search all paths relative to path matching the given pattern.
|
|
1276
|
-
"""
|
|
1277
|
-
path = _get_path(path)
|
|
1278
|
-
assert_dir(path)
|
|
1279
|
-
pathname = os.path.join(path, pattern)
|
|
1280
|
-
paths = glob.glob(pathname, recursive=True)
|
|
1281
|
-
return paths
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
def search_dirs(path: PathIn, pattern: str = "**/*") -> list[str]:
|
|
1285
|
-
"""
|
|
1286
|
-
Search for directories at path matching the given pattern.
|
|
1287
|
-
"""
|
|
1288
|
-
path = _get_path(path)
|
|
1289
|
-
return _filter_paths(path, _search_paths(path, pattern), predicate=is_dir)
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
def search_files(path: PathIn, pattern: str = "**/*.*") -> list[str]:
|
|
1293
|
-
"""
|
|
1294
|
-
Search for files at path matching the given pattern.
|
|
1295
|
-
"""
|
|
1296
|
-
path = _get_path(path)
|
|
1297
|
-
return _filter_paths(path, _search_paths(path, pattern), predicate=is_file)
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
def set_permissions(path: PathIn, value: int) -> None:
|
|
1301
|
-
"""
|
|
1302
|
-
Set the file/directory permissions.
|
|
1303
|
-
"""
|
|
1304
|
-
path = _get_path(path)
|
|
1305
|
-
assert_exists(path)
|
|
1306
|
-
permissions = int(str(value), 8) & 0o777
|
|
1307
|
-
os.chmod(path, permissions)
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
def split_filename(path: PathIn) -> tuple[str, str]:
|
|
1311
|
-
"""
|
|
1312
|
-
Split a filename and returns its basename and extension.
|
|
1313
|
-
"""
|
|
1314
|
-
path = _get_path(path)
|
|
1315
|
-
filename = get_filename(path)
|
|
1316
|
-
basename, extension = os.path.splitext(filename)
|
|
1317
|
-
extension = extension.replace(".", "").strip()
|
|
1318
|
-
return (basename, extension)
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
def split_filepath(path: PathIn) -> tuple[str, str]:
|
|
1322
|
-
"""
|
|
1323
|
-
Split a filepath and returns its directory-path and filename.
|
|
1324
|
-
"""
|
|
1325
|
-
path = _get_path(path)
|
|
1326
|
-
dirpath = os.path.dirname(path)
|
|
1327
|
-
filename = get_filename(path)
|
|
1328
|
-
return (dirpath, filename)
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
def split_path(path: PathIn) -> list[str]:
|
|
1332
|
-
"""
|
|
1333
|
-
Split a path and returns its path-names.
|
|
1334
|
-
"""
|
|
1335
|
-
path = _get_path(path)
|
|
1336
|
-
head, tail = os.path.split(path)
|
|
1337
|
-
names = head.split(os.sep) + [tail]
|
|
1338
|
-
names = list(filter(None, names))
|
|
1339
|
-
return names
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
def transform_filepath(
|
|
1343
|
-
path: PathIn,
|
|
1344
|
-
*,
|
|
1345
|
-
dirpath: str | Callable[[str], str] | None = None,
|
|
1346
|
-
basename: str | Callable[[str], str] | None = None,
|
|
1347
|
-
extension: str | Callable[[str], str] | None = None,
|
|
1348
|
-
) -> str:
|
|
1349
|
-
"""
|
|
1350
|
-
Trasform a filepath by applying the provided optional changes.
|
|
1351
|
-
|
|
1352
|
-
:param path: The path.
|
|
1353
|
-
:type path: PathIn
|
|
1354
|
-
:param dirpath: The new dirpath or a callable.
|
|
1355
|
-
:type dirpath: str | Callable[[str], str] | None
|
|
1356
|
-
:param basename: The new basename or a callable.
|
|
1357
|
-
:type basename: str | Callable[[str], str] | None
|
|
1358
|
-
:param extension: The new extension or a callable.
|
|
1359
|
-
:type extension: str | Callable[[str], str] | None
|
|
1360
|
-
|
|
1361
|
-
:returns: The filepath with the applied changes.
|
|
1362
|
-
:rtype: str
|
|
1363
|
-
"""
|
|
1364
|
-
|
|
1365
|
-
def _get_value(
|
|
1366
|
-
new_value: str | Callable[[str], str] | None,
|
|
1367
|
-
old_value: str,
|
|
1368
|
-
) -> str:
|
|
1369
|
-
value = old_value
|
|
1370
|
-
if new_value is not None:
|
|
1371
|
-
if callable(new_value):
|
|
1372
|
-
value = new_value(old_value)
|
|
1373
|
-
elif isinstance(new_value, str):
|
|
1374
|
-
value = new_value
|
|
1375
|
-
else:
|
|
1376
|
-
value = old_value
|
|
1377
|
-
return value
|
|
1378
|
-
|
|
1379
|
-
if all([dirpath is None, basename is None, extension is None]):
|
|
1380
|
-
raise ValueError(
|
|
1381
|
-
"Invalid arguments: at least one of "
|
|
1382
|
-
"'dirpath', 'basename' or 'extension' is required."
|
|
1383
|
-
)
|
|
1384
|
-
old_dirpath, old_filename = split_filepath(path)
|
|
1385
|
-
old_basename, old_extension = split_filename(old_filename)
|
|
1386
|
-
new_dirpath = _get_value(dirpath, old_dirpath)
|
|
1387
|
-
new_basename = _get_value(basename, old_basename)
|
|
1388
|
-
new_extension = _get_value(extension, old_extension)
|
|
1389
|
-
if not any([new_dirpath, new_basename, new_extension]):
|
|
1390
|
-
raise ValueError(
|
|
1391
|
-
"Invalid arguments: at least one of "
|
|
1392
|
-
"'dirpath', 'basename' or 'extension' is required."
|
|
1393
|
-
)
|
|
1394
|
-
new_filename = join_filename(new_basename, new_extension)
|
|
1395
|
-
new_filepath = join_filepath(new_dirpath, new_filename)
|
|
1396
|
-
return new_filepath
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
def _write_file_atomic(
|
|
1400
|
-
path: PathIn,
|
|
1401
|
-
content: str,
|
|
1402
|
-
*,
|
|
1403
|
-
append: bool = False,
|
|
1404
|
-
encoding: str = "utf-8",
|
|
1405
|
-
) -> None:
|
|
1406
|
-
path = _get_path(path)
|
|
1407
|
-
mode = "a" if append else "w"
|
|
1408
|
-
if append:
|
|
1409
|
-
content = read_file(path, encoding=encoding) + content
|
|
1410
|
-
dirpath, _ = split_filepath(path)
|
|
1411
|
-
try:
|
|
1412
|
-
with tempfile.NamedTemporaryFile(
|
|
1413
|
-
mode=mode,
|
|
1414
|
-
dir=dirpath,
|
|
1415
|
-
delete=True,
|
|
1416
|
-
encoding=encoding,
|
|
1417
|
-
) as file:
|
|
1418
|
-
file.write(content)
|
|
1419
|
-
file.flush()
|
|
1420
|
-
os.fsync(file.fileno())
|
|
1421
|
-
temp_path = file.name
|
|
1422
|
-
permissions = get_permissions(path) if exists(path) else None
|
|
1423
|
-
os.replace(temp_path, path)
|
|
1424
|
-
if permissions:
|
|
1425
|
-
set_permissions(path, permissions)
|
|
1426
|
-
except FileNotFoundError:
|
|
1427
|
-
# success - the NamedTemporaryFile has not been able
|
|
1428
|
-
# to remove the temp file on __exit__ because the temp file
|
|
1429
|
-
# has replaced atomically the file at path.
|
|
1430
|
-
pass
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
def _write_file_non_atomic(
|
|
1434
|
-
path: PathIn,
|
|
1435
|
-
content: str,
|
|
1436
|
-
*,
|
|
1437
|
-
append: bool = False,
|
|
1438
|
-
encoding: str = "utf-8",
|
|
1439
|
-
) -> None:
|
|
1440
|
-
mode = "a" if append else "w"
|
|
1441
|
-
with open(path, mode, encoding=encoding) as file:
|
|
1442
|
-
file.write(content)
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
def write_file(
|
|
1446
|
-
path: PathIn,
|
|
1447
|
-
content: str,
|
|
1448
|
-
*,
|
|
1449
|
-
append: bool = False,
|
|
1450
|
-
encoding: str = "utf-8",
|
|
1451
|
-
atomic: bool = False,
|
|
1452
|
-
) -> None:
|
|
1453
|
-
"""
|
|
1454
|
-
Write file with the specified content at the given path.
|
|
1455
|
-
"""
|
|
1456
|
-
path = _get_path(path)
|
|
1457
|
-
assert_not_dir(path)
|
|
1458
|
-
make_dirs_for_file(path)
|
|
1459
|
-
write_file_func = _write_file_atomic if atomic else _write_file_non_atomic
|
|
1460
|
-
write_file_func(
|
|
1461
|
-
path,
|
|
1462
|
-
content,
|
|
1463
|
-
append=append,
|
|
1464
|
-
encoding=encoding,
|
|
1465
|
-
)
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
def write_file_json(
|
|
1469
|
-
path: PathIn,
|
|
1470
|
-
data: Any,
|
|
1471
|
-
encoding: str = "utf-8",
|
|
1472
|
-
atomic: bool = False,
|
|
1473
|
-
**kwargs: Any,
|
|
1474
|
-
) -> None:
|
|
1475
|
-
"""
|
|
1476
|
-
Write a json file at the given path with the specified data encoded in json format.
|
|
1477
|
-
"""
|
|
1478
|
-
path = _get_path(path)
|
|
1479
|
-
|
|
1480
|
-
def default_encoder(obj: Any) -> Any:
|
|
1481
|
-
if isinstance(obj, datetime):
|
|
1482
|
-
return obj.isoformat()
|
|
1483
|
-
elif isinstance(obj, set):
|
|
1484
|
-
return list(obj)
|
|
1485
|
-
return str(obj)
|
|
1486
|
-
|
|
1487
|
-
kwargs.setdefault("default", default_encoder)
|
|
1488
|
-
content = json.dumps(data, **kwargs)
|
|
1489
|
-
write_file(
|
|
1490
|
-
path,
|
|
1491
|
-
content,
|
|
1492
|
-
append=False,
|
|
1493
|
-
encoding=encoding,
|
|
1494
|
-
atomic=atomic,
|
|
1495
|
-
)
|