python-fsutil 0.14.0__tar.gz → 0.15.0__tar.gz

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.
Files changed (39) hide show
  1. {python-fsutil-0.14.0/python_fsutil.egg-info → python_fsutil-0.15.0}/PKG-INFO +4 -5
  2. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/README.md +1 -1
  3. python_fsutil-0.15.0/fsutil/__init__.py +199 -0
  4. python_fsutil-0.15.0/fsutil/archives.py +163 -0
  5. python_fsutil-0.15.0/fsutil/args.py +13 -0
  6. python_fsutil-0.15.0/fsutil/checks.py +114 -0
  7. python_fsutil-0.15.0/fsutil/converters.py +34 -0
  8. python_fsutil-0.15.0/fsutil/deps.py +13 -0
  9. python_fsutil-0.15.0/fsutil/info.py +187 -0
  10. python_fsutil-0.15.0/fsutil/io.py +231 -0
  11. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/fsutil/metadata.py +1 -1
  12. python_fsutil-0.15.0/fsutil/operations.py +536 -0
  13. python_fsutil-0.15.0/fsutil/paths.py +199 -0
  14. python_fsutil-0.15.0/fsutil/perms.py +28 -0
  15. python_fsutil-0.15.0/fsutil/types.py +6 -0
  16. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/pyproject.toml +2 -3
  17. {python-fsutil-0.14.0 → python_fsutil-0.15.0/python_fsutil.egg-info}/PKG-INFO +4 -5
  18. python_fsutil-0.15.0/python_fsutil.egg-info/SOURCES.txt +34 -0
  19. python_fsutil-0.15.0/tests/test_archives.py +133 -0
  20. python_fsutil-0.15.0/tests/test_args.py +29 -0
  21. python_fsutil-0.15.0/tests/test_checks.py +111 -0
  22. python_fsutil-0.15.0/tests/test_converters.py +86 -0
  23. python_fsutil-0.15.0/tests/test_deps.py +25 -0
  24. python_fsutil-0.15.0/tests/test_info.py +167 -0
  25. python_fsutil-0.15.0/tests/test_io.py +204 -0
  26. python_fsutil-0.15.0/tests/test_metadata.py +25 -0
  27. python_fsutil-0.15.0/tests/test_operations.py +538 -0
  28. python_fsutil-0.15.0/tests/test_paths.py +191 -0
  29. python_fsutil-0.15.0/tests/test_perms.py +26 -0
  30. python-fsutil-0.14.0/fsutil/__init__.py +0 -1495
  31. python-fsutil-0.14.0/python_fsutil.egg-info/SOURCES.txt +0 -13
  32. python-fsutil-0.14.0/tests/test.py +0 -1389
  33. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/LICENSE.txt +0 -0
  34. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/MANIFEST.in +0 -0
  35. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/fsutil/py.typed +0 -0
  36. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/python_fsutil.egg-info/dependency_links.txt +0 -0
  37. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/python_fsutil.egg-info/top_level.txt +0 -0
  38. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/setup.cfg +0 -0
  39. {python-fsutil-0.14.0 → python_fsutil-0.15.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: python-fsutil
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: high-level file-system operations for lazy devs.
5
5
  Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
6
6
  Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
@@ -42,11 +42,10 @@ Classifier: License :: OSI Approved :: MIT License
42
42
  Classifier: Natural Language :: English
43
43
  Classifier: Operating System :: OS Independent
44
44
  Classifier: Programming Language :: Python :: 3
45
- Classifier: Programming Language :: Python :: 3.8
46
- Classifier: Programming Language :: Python :: 3.9
47
45
  Classifier: Programming Language :: Python :: 3.10
48
46
  Classifier: Programming Language :: Python :: 3.11
49
47
  Classifier: Programming Language :: Python :: 3.12
48
+ Classifier: Programming Language :: Python :: 3.13
50
49
  Classifier: Programming Language :: Python :: Implementation :: PyPy
51
50
  Classifier: Topic :: Desktop Environment :: File Managers
52
51
  Classifier: Topic :: Software Development :: Build Tools
@@ -507,7 +506,7 @@ permissions = fsutil.get_permissions(path)
507
506
  #### `get_unique_name`
508
507
 
509
508
  ```python
510
- # Get a unique name for a directory/file ath the given directory path.
509
+ # Get a unique name for a directory/file at the given directory path.
511
510
  unique_name = fsutil.get_unique_name(path, prefix="", suffix="", extension="", separator="-")
512
511
  ```
513
512
 
@@ -449,7 +449,7 @@ permissions = fsutil.get_permissions(path)
449
449
  #### `get_unique_name`
450
450
 
451
451
  ```python
452
- # Get a unique name for a directory/file ath the given directory path.
452
+ # Get a unique name for a directory/file at the given directory path.
453
453
  unique_name = fsutil.get_unique_name(path, prefix="", suffix="", extension="", separator="-")
454
454
  ```
455
455
 
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+
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
+ )
49
+ from fsutil.metadata import (
50
+ __author__,
51
+ __copyright__,
52
+ __description__,
53
+ __email__,
54
+ __license__,
55
+ __title__,
56
+ __version__,
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
106
+
107
+ __all__ = [
108
+ "__author__",
109
+ "__copyright__",
110
+ "__description__",
111
+ "__email__",
112
+ "__license__",
113
+ "__title__",
114
+ "__version__",
115
+ "assert_dir",
116
+ "assert_exists",
117
+ "assert_file",
118
+ "assert_not_dir",
119
+ "assert_not_exists",
120
+ "assert_not_file",
121
+ "clean_dir",
122
+ "convert_size_bytes_to_string",
123
+ "convert_size_string_to_bytes",
124
+ "copy_dir",
125
+ "copy_dir_content",
126
+ "copy_file",
127
+ "create_dir",
128
+ "create_file",
129
+ "create_tar_file",
130
+ "create_zip_file",
131
+ "delete_dir",
132
+ "delete_dir_content",
133
+ "delete_dirs",
134
+ "delete_file",
135
+ "delete_files",
136
+ "download_file",
137
+ "exists",
138
+ "extract_tar_file",
139
+ "extract_zip_file",
140
+ "get_dir_creation_date",
141
+ "get_dir_creation_date_formatted",
142
+ "get_dir_hash",
143
+ "get_dir_last_modified_date",
144
+ "get_dir_last_modified_date_formatted",
145
+ "get_dir_size",
146
+ "get_dir_size_formatted",
147
+ "get_file_basename",
148
+ "get_file_creation_date",
149
+ "get_file_creation_date_formatted",
150
+ "get_file_extension",
151
+ "get_file_hash",
152
+ "get_file_last_modified_date",
153
+ "get_file_last_modified_date_formatted",
154
+ "get_file_size",
155
+ "get_file_size_formatted",
156
+ "get_filename",
157
+ "get_parent_dir",
158
+ "get_permissions",
159
+ "get_unique_name",
160
+ "is_dir",
161
+ "is_empty",
162
+ "is_empty_dir",
163
+ "is_empty_file",
164
+ "is_file",
165
+ "join_filename",
166
+ "join_filepath",
167
+ "join_path",
168
+ "list_dirs",
169
+ "list_files",
170
+ "make_dirs",
171
+ "make_dirs_for_file",
172
+ "move_dir",
173
+ "move_file",
174
+ "read_file",
175
+ "read_file_from_url",
176
+ "read_file_json",
177
+ "read_file_lines",
178
+ "read_file_lines_count",
179
+ "remove_dir",
180
+ "remove_dir_content",
181
+ "remove_dirs",
182
+ "remove_file",
183
+ "remove_files",
184
+ "rename_dir",
185
+ "rename_file",
186
+ "rename_file_basename",
187
+ "rename_file_extension",
188
+ "replace_dir",
189
+ "replace_file",
190
+ "search_dirs",
191
+ "search_files",
192
+ "set_permissions",
193
+ "split_filename",
194
+ "split_filepath",
195
+ "split_path",
196
+ "transform_filepath",
197
+ "write_file",
198
+ "write_file_json",
199
+ ]
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ import tarfile
6
+ import zipfile
7
+ from collections.abc import Callable, Iterable
8
+ from typing import Literal
9
+
10
+ from fsutil.args import get_path as _get_path
11
+ from fsutil.checks import (
12
+ assert_exists,
13
+ assert_file,
14
+ assert_not_dir,
15
+ assert_not_exists,
16
+ assert_not_file,
17
+ is_dir,
18
+ is_file,
19
+ )
20
+ from fsutil.operations import make_dirs, make_dirs_for_file, remove_file
21
+ from fsutil.paths import get_filename, join_path
22
+ from fsutil.types import PathIn
23
+
24
+
25
+ def create_tar_file(
26
+ path: PathIn,
27
+ content_paths: list[PathIn],
28
+ *,
29
+ overwrite: bool = True,
30
+ compression: str = "", # literal: gz, bz2, xz
31
+ ) -> None:
32
+ """
33
+ Create tar file at path compressing directories/files listed in content_paths.
34
+ If overwrite is allowed and dest tar already exists, it will be overwritten.
35
+ """
36
+ path = _get_path(path)
37
+ assert_not_dir(path)
38
+ if not overwrite:
39
+ assert_not_exists(path)
40
+ make_dirs_for_file(path)
41
+
42
+ def _write_content_to_tar_file(
43
+ file: tarfile.TarFile, content_path: PathIn, basedir: str = ""
44
+ ) -> None:
45
+ path = _get_path(content_path)
46
+ assert_exists(path)
47
+ if is_file(path):
48
+ filename = get_filename(path)
49
+ filepath = join_path(basedir, filename)
50
+ file.add(path, filepath)
51
+ elif is_dir(path):
52
+ for item_name in os.listdir(path):
53
+ item_path = join_path(path, item_name)
54
+ item_basedir = (
55
+ join_path(basedir, item_name) if is_dir(item_path) else basedir
56
+ )
57
+ _write_content_to_tar_file(file, item_path, item_basedir)
58
+
59
+ mode = f"w:{compression}" if compression else "w"
60
+ with tarfile.open(path, mode=mode) as file: # type: ignore
61
+ for content_path in content_paths:
62
+ _write_content_to_tar_file(file, content_path)
63
+
64
+
65
+ def create_zip_file(
66
+ path: PathIn,
67
+ content_paths: list[PathIn],
68
+ *,
69
+ overwrite: bool = True,
70
+ compression: int = zipfile.ZIP_DEFLATED,
71
+ ) -> None:
72
+ """
73
+ Create zip file at path compressing directories/files listed in content_paths.
74
+ If overwrite is allowed and dest zip already exists, it will be overwritten.
75
+ """
76
+ path = _get_path(path)
77
+ assert_not_dir(path)
78
+ if not overwrite:
79
+ assert_not_exists(path)
80
+ make_dirs_for_file(path)
81
+
82
+ def _write_content_to_zip_file(
83
+ file: zipfile.ZipFile, content_path: PathIn, basedir: str = ""
84
+ ) -> None:
85
+ path = _get_path(content_path)
86
+ assert_exists(path)
87
+ if is_file(path):
88
+ filename = get_filename(path)
89
+ filepath = join_path(basedir, filename)
90
+ file.write(path, filepath)
91
+ elif is_dir(path):
92
+ for item_name in os.listdir(path):
93
+ item_path = join_path(path, item_name)
94
+ item_basedir = (
95
+ join_path(basedir, item_name) if is_dir(item_path) else basedir
96
+ )
97
+ _write_content_to_zip_file(file, item_path, item_basedir)
98
+
99
+ with zipfile.ZipFile(path, "w", compression) as file:
100
+ for content_path in content_paths:
101
+ _write_content_to_zip_file(file, content_path)
102
+
103
+
104
+ def extract_tar_file(
105
+ path: PathIn,
106
+ dest: PathIn,
107
+ *,
108
+ autodelete: bool = False,
109
+ content_paths: Iterable[tarfile.TarInfo] | None = None,
110
+ filter: (
111
+ Callable[[tarfile.TarInfo, str], tarfile.TarInfo | None]
112
+ | Literal["fully_trusted", "tar", "data"]
113
+ )
114
+ | None = None,
115
+ ) -> None:
116
+ """
117
+ Extract tar file at path to dest path.
118
+ If autodelete, the archive will be deleted after extraction.
119
+ If content_paths list is defined,
120
+ only listed items will be extracted, otherwise all.
121
+ """
122
+ path = _get_path(path)
123
+ dest = _get_path(dest)
124
+ assert_file(path)
125
+ assert_not_file(dest)
126
+ make_dirs(dest)
127
+ with tarfile.TarFile(path, "r") as file:
128
+ if sys.version_info < (3, 12):
129
+ file.extractall(dest, members=content_paths)
130
+ else:
131
+ # https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter
132
+ file.extractall(
133
+ dest,
134
+ members=content_paths,
135
+ numeric_owner=False,
136
+ filter=(filter or "data"),
137
+ )
138
+ if autodelete:
139
+ remove_file(path)
140
+
141
+
142
+ def extract_zip_file(
143
+ path: PathIn,
144
+ dest: PathIn,
145
+ *,
146
+ autodelete: bool = False,
147
+ content_paths: Iterable[str | zipfile.ZipInfo] | None = None,
148
+ ) -> None:
149
+ """
150
+ Extract zip file at path to dest path.
151
+ If autodelete, the archive will be deleted after extraction.
152
+ If content_paths list is defined,
153
+ only listed items will be extracted, otherwise all.
154
+ """
155
+ path = _get_path(path)
156
+ dest = _get_path(dest)
157
+ assert_file(path)
158
+ assert_not_file(dest)
159
+ make_dirs(dest)
160
+ with zipfile.ZipFile(path, "r") as file:
161
+ file.extractall(dest, members=content_paths)
162
+ if autodelete:
163
+ remove_file(path)
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from fsutil.types import PathIn
6
+
7
+
8
+ def get_path(path: PathIn) -> str:
9
+ if path is None:
10
+ return None
11
+ if isinstance(path, str):
12
+ return os.path.normpath(path)
13
+ return str(path)
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from fsutil.args import get_path as _get_path
6
+ from fsutil.types import PathIn
7
+
8
+
9
+ def assert_dir(path: PathIn) -> None:
10
+ """
11
+ Raise an OSError if the given path doesn't exist or it is not a directory.
12
+ """
13
+ path = _get_path(path)
14
+ if not is_dir(path):
15
+ raise OSError(f"Invalid directory path: {path}")
16
+
17
+
18
+ def assert_exists(path: PathIn) -> None:
19
+ """
20
+ Raise an OSError if the given path doesn't exist.
21
+ """
22
+ path = _get_path(path)
23
+ if not exists(path):
24
+ raise OSError(f"Invalid item path: {path}")
25
+
26
+
27
+ def assert_file(path: PathIn) -> None:
28
+ """
29
+ Raise an OSError if the given path doesn't exist or it is not a file.
30
+ """
31
+ path = _get_path(path)
32
+ if not is_file(path):
33
+ raise OSError(f"Invalid file path: {path}")
34
+
35
+
36
+ def assert_not_dir(path: PathIn) -> None:
37
+ """
38
+ Raise an OSError if the given path is an existing directory.
39
+ """
40
+ path = _get_path(path)
41
+ if is_dir(path):
42
+ raise OSError(f"Invalid path, directory already exists: {path}")
43
+
44
+
45
+ def assert_not_exists(path: PathIn) -> None:
46
+ """
47
+ Raise an OSError if the given path already exists.
48
+ """
49
+ path = _get_path(path)
50
+ if exists(path):
51
+ raise OSError(f"Invalid path, item already exists: {path}")
52
+
53
+
54
+ def assert_not_file(path: PathIn) -> None:
55
+ """
56
+ Raise an OSError if the given path is an existing file.
57
+ """
58
+ path = _get_path(path)
59
+ if is_file(path):
60
+ raise OSError(f"Invalid path, file already exists: {path}")
61
+
62
+
63
+ def exists(path: PathIn) -> bool:
64
+ """
65
+ Check if a directory of a file exists at the given path.
66
+ """
67
+ path = _get_path(path)
68
+ return os.path.exists(path)
69
+
70
+
71
+ def is_dir(path: PathIn) -> bool:
72
+ """
73
+ Determine whether the specified path represents an existing directory.
74
+ """
75
+ path = _get_path(path)
76
+ return os.path.isdir(path)
77
+
78
+
79
+ def is_empty(path: PathIn) -> bool:
80
+ """
81
+ Determine whether the specified path represents an empty directory or an empty file.
82
+ """
83
+ path = _get_path(path)
84
+ assert_exists(path)
85
+ if is_dir(path):
86
+ return is_empty_dir(path)
87
+ return is_empty_file(path)
88
+
89
+
90
+ def is_empty_dir(path: PathIn) -> bool:
91
+ """
92
+ Determine whether the specified path represents an empty directory.
93
+ """
94
+ path = _get_path(path)
95
+ assert_dir(path)
96
+ return len(os.listdir(path)) == 0
97
+
98
+
99
+ def is_empty_file(path: PathIn) -> bool:
100
+ """
101
+ Determine whether the specified path represents an empty file.
102
+ """
103
+ from fsutil.info import get_file_size
104
+
105
+ path = _get_path(path)
106
+ return get_file_size(path) == 0
107
+
108
+
109
+ def is_file(path: PathIn) -> bool:
110
+ """
111
+ Determine whether the specified path represents an existing file.
112
+ """
113
+ path = _get_path(path)
114
+ return os.path.isfile(path)
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ SIZE_UNITS = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
4
+
5
+
6
+ def convert_size_bytes_to_string(size: int) -> str:
7
+ """
8
+ Convert the given size bytes to string using the right unit suffix.
9
+ """
10
+ size_num = float(size)
11
+ units = SIZE_UNITS
12
+ factor = 0
13
+ factor_limit = len(units) - 1
14
+ while (size_num >= 1024) and (factor <= factor_limit):
15
+ size_num /= 1024
16
+ factor += 1
17
+ size_units = units[factor]
18
+ size_str = f"{size_num:.2f}" if (factor > 1) else f"{size_num:.0f}"
19
+ size_str = f"{size_str} {size_units}"
20
+ return size_str
21
+
22
+
23
+ def convert_size_string_to_bytes(size: str) -> float | int:
24
+ """
25
+ Convert the given size string to bytes.
26
+ """
27
+ units = [item.lower() for item in SIZE_UNITS]
28
+ parts = size.strip().replace(" ", " ").split(" ")
29
+ amount = float(parts[0])
30
+ unit = parts[1]
31
+ factor = units.index(unit.lower())
32
+ if not factor:
33
+ return amount
34
+ return int((1024**factor) * amount)
@@ -0,0 +1,13 @@
1
+ from types import ModuleType
2
+
3
+
4
+ def require_requests() -> ModuleType:
5
+ try:
6
+ import requests
7
+
8
+ return requests
9
+ except ImportError as error:
10
+ raise ModuleNotFoundError(
11
+ "'requests' module is not installed, "
12
+ "it can be installed by running: 'pip install requests'"
13
+ ) from error