python-fsutil 0.16.1__tar.gz → 0.17.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.
- {python_fsutil-0.16.1/src/python_fsutil.egg-info → python_fsutil-0.17.0}/PKG-INFO +2 -1
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/README.md +1 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/converters.py +1 -1
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/info.py +21 -7
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/metadata.py +1 -1
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/operations.py +3 -1
- {python_fsutil-0.16.1 → python_fsutil-0.17.0/src/python_fsutil.egg-info}/PKG-INFO +2 -1
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_converters.py +9 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_info.py +15 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_operations.py +27 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/LICENSE.txt +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/MANIFEST.in +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/pyproject.toml +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/setup.cfg +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/setup.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/__init__.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/archives.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/args.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/checks.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/deps.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/io.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/paths.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/perms.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/py.typed +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/fsutil/types.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/python_fsutil.egg-info/SOURCES.txt +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/python_fsutil.egg-info/dependency_links.txt +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/python_fsutil.egg-info/top_level.txt +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/__init__.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/conftest.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_archives.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_args.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_checks.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_deps.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_io.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_metadata.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_paths.py +0 -0
- {python_fsutil-0.16.1 → python_fsutil-0.17.0}/tests/test_perms.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fsutil
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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>
|
|
@@ -69,6 +69,7 @@ Dynamic: license-file
|
|
|
69
69
|
[](https://www.codacy.com/app/fabiocaccamo/python-fsutil)
|
|
70
70
|
[](https://github.com/psf/black)
|
|
71
71
|
[](https://github.com/astral-sh/ruff)
|
|
72
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/fabiocaccamo/python-fsutil)
|
|
72
73
|
|
|
73
74
|
# python-fsutil
|
|
74
75
|
high-level file-system operations for lazy devs.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
[](https://www.codacy.com/app/fabiocaccamo/python-fsutil)
|
|
11
11
|
[](https://github.com/psf/black)
|
|
12
12
|
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/fabiocaccamo/python-fsutil)
|
|
13
14
|
|
|
14
15
|
# python-fsutil
|
|
15
16
|
high-level file-system operations for lazy devs.
|
|
@@ -11,7 +11,7 @@ def convert_size_bytes_to_string(size: int) -> str:
|
|
|
11
11
|
units = SIZE_UNITS
|
|
12
12
|
factor = 0
|
|
13
13
|
factor_limit = len(units) - 1
|
|
14
|
-
while (size_num >= 1024) and (factor
|
|
14
|
+
while (size_num >= 1024) and (factor < factor_limit):
|
|
15
15
|
size_num /= 1024
|
|
16
16
|
factor += 1
|
|
17
17
|
size_units = units[factor]
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import os
|
|
5
|
+
import pathlib
|
|
5
6
|
from datetime import datetime
|
|
7
|
+
from typing import IO
|
|
6
8
|
|
|
7
9
|
from fsutil.args import get_path as _get_path
|
|
8
10
|
from fsutil.checks import assert_dir, assert_file
|
|
@@ -129,17 +131,29 @@ def get_file_creation_date_formatted(
|
|
|
129
131
|
return date.strftime(format)
|
|
130
132
|
|
|
131
133
|
|
|
132
|
-
def get_file_hash(path: PathIn, *, func: str = "md5") -> str:
|
|
134
|
+
def get_file_hash(path: PathIn | IO[bytes], *, func: str = "md5") -> str:
|
|
133
135
|
"""
|
|
134
|
-
Get the hash of the file at the given path
|
|
135
|
-
|
|
136
|
+
Get the hash of the file at the given path (or of the given
|
|
137
|
+
binary file-like object) using the specified algorithm
|
|
138
|
+
function (md5 by default).
|
|
136
139
|
"""
|
|
137
|
-
path = _get_path(path)
|
|
138
|
-
assert_file(path)
|
|
139
140
|
hash = hashlib.new(func)
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
if isinstance(path, (str, pathlib.Path)):
|
|
142
|
+
path = _get_path(path)
|
|
143
|
+
assert_file(path)
|
|
144
|
+
with open(path, "rb") as file:
|
|
145
|
+
for chunk in iter(lambda: file.read(4096), b""):
|
|
146
|
+
hash.update(chunk)
|
|
147
|
+
else:
|
|
148
|
+
fileobj = path
|
|
149
|
+
position = None
|
|
150
|
+
if fileobj.seekable():
|
|
151
|
+
position = fileobj.tell()
|
|
152
|
+
fileobj.seek(0)
|
|
153
|
+
for chunk in iter(lambda: fileobj.read(4096), b""):
|
|
142
154
|
hash.update(chunk)
|
|
155
|
+
if position is not None:
|
|
156
|
+
fileobj.seek(position)
|
|
143
157
|
hash_hex = hash.hexdigest()
|
|
144
158
|
return hash_hex
|
|
145
159
|
|
|
@@ -213,7 +213,9 @@ def download_file(
|
|
|
213
213
|
filename_pattern = r'filename="(.*)"'
|
|
214
214
|
filename_match = re.search(filename_pattern, content_disposition)
|
|
215
215
|
if filename_match:
|
|
216
|
-
filename
|
|
216
|
+
# sanitize Content-Disposition filename to prevent path traversal
|
|
217
|
+
filename = filename_match.group(1).replace("\\", "/")
|
|
218
|
+
filename = os.path.basename(filename)
|
|
217
219
|
# or detect filename from url
|
|
218
220
|
if not filename:
|
|
219
221
|
filename = get_filename(url)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fsutil
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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>
|
|
@@ -69,6 +69,7 @@ Dynamic: license-file
|
|
|
69
69
|
[](https://www.codacy.com/app/fabiocaccamo/python-fsutil)
|
|
70
70
|
[](https://github.com/psf/black)
|
|
71
71
|
[](https://github.com/astral-sh/ruff)
|
|
72
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/fabiocaccamo/python-fsutil)
|
|
72
73
|
|
|
73
74
|
# python-fsutil
|
|
74
75
|
high-level file-system operations for lazy devs.
|
|
@@ -13,6 +13,9 @@ import fsutil
|
|
|
13
13
|
(1073741824, "1.00 GB"),
|
|
14
14
|
(1879048192, "1.75 GB"),
|
|
15
15
|
(1099511627776, "1.00 TB"),
|
|
16
|
+
(2**80, "1.00 YB"),
|
|
17
|
+
(2**90, "1024.00 YB"),
|
|
18
|
+
(2**100, "1048576.00 YB"),
|
|
16
19
|
],
|
|
17
20
|
)
|
|
18
21
|
def test_convert_size_bytes_to_string(size_bytes, expected_output):
|
|
@@ -51,6 +54,9 @@ def test_convert_size_bytes_to_string_and_convert_size_string_to_bytes(
|
|
|
51
54
|
("1.00 MB", 1048576),
|
|
52
55
|
("1.00 GB", 1073741824),
|
|
53
56
|
("1.00 TB", 1099511627776),
|
|
57
|
+
("1.00 YB", 2**80),
|
|
58
|
+
("1024.00 YB", 2**90),
|
|
59
|
+
("1048576.00 YB", 2**100),
|
|
54
60
|
],
|
|
55
61
|
)
|
|
56
62
|
def test_convert_size_string_to_bytes(size_string, expected_output):
|
|
@@ -69,6 +75,9 @@ def test_convert_size_string_to_bytes(size_string, expected_output):
|
|
|
69
75
|
(1170378588, 1170378588),
|
|
70
76
|
(2136746229, 2136746229),
|
|
71
77
|
(1099511627776, 1099511627776),
|
|
78
|
+
(2**80, 2**80),
|
|
79
|
+
(2**90, 2**90),
|
|
80
|
+
(2**100, 2**100),
|
|
72
81
|
],
|
|
73
82
|
)
|
|
74
83
|
def test_convert_size_string_to_bytes_and_convert_size_bytes_to_string(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import re
|
|
2
3
|
import time
|
|
3
4
|
from datetime import datetime, timedelta
|
|
@@ -127,6 +128,20 @@ def test_get_file_hash(temp_path):
|
|
|
127
128
|
assert file_hash == "b10a8db164e0754105b7a99be72e3fe5"
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
def test_get_file_hash_with_file_like_object():
|
|
132
|
+
file = io.BytesIO(b"Hello World")
|
|
133
|
+
file_hash = fsutil.get_file_hash(file)
|
|
134
|
+
assert file_hash == "b10a8db164e0754105b7a99be72e3fe5"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_get_file_hash_with_file_like_object_restores_position():
|
|
138
|
+
file = io.BytesIO(b"Hello World")
|
|
139
|
+
file.seek(6)
|
|
140
|
+
file_hash = fsutil.get_file_hash(file)
|
|
141
|
+
assert file_hash == "b10a8db164e0754105b7a99be72e3fe5"
|
|
142
|
+
assert file.tell() == 6
|
|
143
|
+
|
|
144
|
+
|
|
130
145
|
def test_get_file_last_modified_date(temp_path):
|
|
131
146
|
path = temp_path("a/b/c.txt")
|
|
132
147
|
fsutil.create_file(path, content="Hello")
|
|
@@ -232,6 +232,33 @@ def test_download_file_without_requests_installed(temp_path):
|
|
|
232
232
|
fsutil.download_file(url, dirpath=temp_path())
|
|
233
233
|
|
|
234
234
|
|
|
235
|
+
def test_download_file_content_disposition_filename_is_sanitized(temp_path):
|
|
236
|
+
class MockResponse:
|
|
237
|
+
headers = {"content-disposition": 'attachment; filename="..\\..\\evil.txt"'}
|
|
238
|
+
|
|
239
|
+
def __enter__(self):
|
|
240
|
+
return self
|
|
241
|
+
|
|
242
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def raise_for_status(self):
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
def iter_content(self, chunk_size=1):
|
|
249
|
+
yield b"hello world"
|
|
250
|
+
|
|
251
|
+
class MockRequests:
|
|
252
|
+
def get(self, *args, **kwargs):
|
|
253
|
+
return MockResponse()
|
|
254
|
+
|
|
255
|
+
with patch("fsutil.operations.require_requests", return_value=MockRequests()):
|
|
256
|
+
path = fsutil.download_file("https://example.com/file.txt", dirpath=temp_path())
|
|
257
|
+
assert fsutil.exists(path)
|
|
258
|
+
assert fsutil.get_filename(path) == "evil.txt"
|
|
259
|
+
assert path == temp_path("evil.txt")
|
|
260
|
+
|
|
261
|
+
|
|
235
262
|
def test_list_dirs(temp_path):
|
|
236
263
|
for i in range(0, 5):
|
|
237
264
|
fsutil.create_dir(temp_path(f"a/b/c/d-{i}"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fsutil-0.16.1 → python_fsutil-0.17.0}/src/python_fsutil.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|