kleinkram 0.44.0.dev20250409080532__py3-none-any.whl → 0.44.1__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.
Potentially problematic release.
This version of kleinkram might be problematic. Click here for more details.
- kleinkram/api/file_transfer.py +64 -17
- kleinkram/printing.py +2 -2
- kleinkram/utils.py +2 -2
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/METADATA +1 -1
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/RECORD +9 -9
- tests/test_printing.py +8 -8
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/WHEEL +0 -0
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/entry_points.txt +0 -0
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/top_level.txt +0 -0
kleinkram/api/file_transfer.py
CHANGED
|
@@ -44,6 +44,9 @@ MAX_UPLOAD_RETRIES = 3
|
|
|
44
44
|
S3_MAX_RETRIES = 60 # same as frontend
|
|
45
45
|
S3_READ_TIMEOUT = 60 * 5 # 5 minutes
|
|
46
46
|
|
|
47
|
+
RETRY_BACKOFF_BASE = 2 # exponential backoff base
|
|
48
|
+
MAX_RETRIES = 5
|
|
49
|
+
|
|
47
50
|
|
|
48
51
|
class UploadCredentials(NamedTuple):
|
|
49
52
|
access_key: str
|
|
@@ -264,23 +267,67 @@ def _get_file_download(client: AuthenticatedClient, id: UUID) -> str:
|
|
|
264
267
|
def _url_download(
|
|
265
268
|
url: str, *, path: Path, size: int, overwrite: bool = False, verbose: bool = False
|
|
266
269
|
) -> None:
|
|
267
|
-
if path.exists()
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
270
|
+
if path.exists():
|
|
271
|
+
if overwrite:
|
|
272
|
+
path.unlink()
|
|
273
|
+
downloaded = 0
|
|
274
|
+
else:
|
|
275
|
+
downloaded = path.stat().st_size
|
|
276
|
+
if downloaded >= size:
|
|
277
|
+
raise FileExistsError(f"file already exists and is complete: {path}")
|
|
278
|
+
else:
|
|
279
|
+
downloaded = 0
|
|
280
|
+
|
|
281
|
+
attempt = 0
|
|
282
|
+
while downloaded < size:
|
|
283
|
+
try:
|
|
284
|
+
headers = {"Range": f"bytes={downloaded}-"}
|
|
285
|
+
with httpx.stream(
|
|
286
|
+
"GET", url, headers=headers, timeout=S3_READ_TIMEOUT
|
|
287
|
+
) as response:
|
|
288
|
+
# Accept both 206 Partial Content and 200 OK if starting from 0
|
|
289
|
+
if not (
|
|
290
|
+
response.status_code == 206
|
|
291
|
+
or (downloaded == 0 and response.status_code == 200)
|
|
292
|
+
):
|
|
293
|
+
response.raise_for_status()
|
|
294
|
+
raise RuntimeError(
|
|
295
|
+
f"Expected 206 Partial Content, got {response.status_code}"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
mode = "ab" if downloaded > 0 else "wb"
|
|
299
|
+
with open(path, mode) as f:
|
|
300
|
+
with tqdm(
|
|
301
|
+
total=size,
|
|
302
|
+
initial=downloaded,
|
|
303
|
+
desc=f"downloading {path.name}",
|
|
304
|
+
unit="B",
|
|
305
|
+
unit_scale=True,
|
|
306
|
+
leave=False,
|
|
307
|
+
disable=not verbose,
|
|
308
|
+
) as pbar:
|
|
309
|
+
for chunk in response.iter_bytes(
|
|
310
|
+
chunk_size=DOWNLOAD_CHUNK_SIZE
|
|
311
|
+
):
|
|
312
|
+
attempt = 0 # reset attempt counter on successful download of non-empty chunk
|
|
313
|
+
if not chunk:
|
|
314
|
+
break
|
|
315
|
+
f.write(chunk)
|
|
316
|
+
downloaded += len(chunk)
|
|
317
|
+
pbar.update(len(chunk))
|
|
318
|
+
break # download complete
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.info(f"Error: {e}, retrying...")
|
|
321
|
+
attempt += 1
|
|
322
|
+
if attempt > MAX_RETRIES:
|
|
323
|
+
raise RuntimeError(
|
|
324
|
+
f"Download failed after {MAX_RETRIES} retries due to {e}"
|
|
325
|
+
) from e
|
|
326
|
+
if verbose:
|
|
327
|
+
print(
|
|
328
|
+
f"{e} on attempt {attempt}/{MAX_RETRIES}, retrying after backoff..."
|
|
329
|
+
)
|
|
330
|
+
sleep(RETRY_BACKOFF_BASE**attempt)
|
|
284
331
|
|
|
285
332
|
|
|
286
333
|
class DownloadState(Enum):
|
kleinkram/printing.py
CHANGED
|
@@ -85,8 +85,8 @@ def format_bytes(size: int) -> str:
|
|
|
85
85
|
index = 0
|
|
86
86
|
|
|
87
87
|
fsize: float = size
|
|
88
|
-
while fsize >=
|
|
89
|
-
fsize /=
|
|
88
|
+
while fsize >= 1000 and index < len(units) - 1:
|
|
89
|
+
fsize /= 1000.0
|
|
90
90
|
index += 1
|
|
91
91
|
|
|
92
92
|
# Format to 2 decimal places if needed
|
kleinkram/utils.py
CHANGED
|
@@ -229,10 +229,10 @@ def format_bytes(size_bytes: int | float, speed: bool = False) -> str:
|
|
|
229
229
|
return "0 B/s" if speed else "0 B"
|
|
230
230
|
|
|
231
231
|
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
232
|
-
power = math.floor(math.log(size_bytes,
|
|
232
|
+
power = math.floor(math.log(size_bytes, 1000))
|
|
233
233
|
unit_index = min(power, len(units) - 1)
|
|
234
234
|
|
|
235
|
-
value = size_bytes / (
|
|
235
|
+
value = size_bytes / (1000**unit_index)
|
|
236
236
|
|
|
237
237
|
unit_suffix = "/s" if speed else ""
|
|
238
238
|
return f"{value:.2f} {units[unit_index]}{unit_suffix}"
|
|
@@ -7,15 +7,15 @@ kleinkram/core.py,sha256=COJcUx8tP6EQIyg4OiVprJfQUCMbiq2QGB9xZ2CPUCo,9621
|
|
|
7
7
|
kleinkram/errors.py,sha256=qa98YvhDbLqX60P8bcMcFmHy4HxgYNlSROXud8Kj-P4,965
|
|
8
8
|
kleinkram/main.py,sha256=BTE0mZN__xd46wBhFi6iBlK9eGGQvJ1LdUMsbnysLi0,172
|
|
9
9
|
kleinkram/models.py,sha256=MkWdGIWuCSnyg45DbdEUhtGIprwPYwKppYumHkRAS18,1870
|
|
10
|
-
kleinkram/printing.py,sha256=
|
|
10
|
+
kleinkram/printing.py,sha256=gVLQPmAhyO1Fc5kKLBC77KvoEguBaAReTU3h-QQCx58,12212
|
|
11
11
|
kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
|
|
13
|
-
kleinkram/utils.py,sha256=
|
|
13
|
+
kleinkram/utils.py,sha256=lNtmjKTZj4ANndERqfL3QD8VI2B4ZavdW66lxNDz_IY,6767
|
|
14
14
|
kleinkram/wrappers.py,sha256=ZScoEov5Q6D2rvaJJ8E-4f58P_NGWrGc9mRPYxSqOC0,13127
|
|
15
15
|
kleinkram/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
kleinkram/api/client.py,sha256=VwuT97_WdbDpcVGwMXB0fRnUoQnUSf7BOP5eXUFokfI,5932
|
|
17
17
|
kleinkram/api/deser.py,sha256=xRpYUFKZ0Luoo7XyAtYblJvprmpjNSZOiFVnFKmOzcM,4819
|
|
18
|
-
kleinkram/api/file_transfer.py,sha256=
|
|
18
|
+
kleinkram/api/file_transfer.py,sha256=eHJlK1FY0hHBmFHAves_p49hi4Mmpdjy1NNLeyLvK0w,19917
|
|
19
19
|
kleinkram/api/pagination.py,sha256=P_zPsBKlMWkmAv-YfUNHaGW-XLB_4U8BDMrKyiDFIXk,1370
|
|
20
20
|
kleinkram/api/query.py,sha256=9Exi4hJR7Ml38_zjAcOvSEoIAxZLlpM6QwwzO9fs5Gk,3293
|
|
21
21
|
kleinkram/api/routes.py,sha256=x0IfzQO5RAC3rohDSpdUNlVOWl221gh4e0fbMKfGrQA,12251
|
|
@@ -39,12 +39,12 @@ tests/test_core.py,sha256=tVvmC4yBkE00VspoowhfdNAUiBsmkIJQodaC3jjUqqM,5623
|
|
|
39
39
|
tests/test_end_to_end.py,sha256=0W5pUES5hek-pXq4NZtpPZqKTORkGCRsDv5_D3rDMjY,3372
|
|
40
40
|
tests/test_error_handling.py,sha256=qPSMKF1qsAHyUME0-krxbIrk38iGKkhAyAah-KwN4NE,1300
|
|
41
41
|
tests/test_fixtures.py,sha256=UlPmGbEsGvrDPsaStGMRjNvrVPGjCqOB0RMfLJq2VRA,1071
|
|
42
|
-
tests/test_printing.py,sha256=
|
|
42
|
+
tests/test_printing.py,sha256=kPzpIQOtQJ9yQ32mM8cMGDVOGsbrZZLQhfsXN1Pe68Q,2231
|
|
43
43
|
tests/test_query.py,sha256=fExmCKXLA7-9j2S2sF_sbvRX_2s6Cp3a7OTcqE25q9g,3864
|
|
44
44
|
tests/test_utils.py,sha256=eUBYrn3xrcgcaxm1X4fqZaX4tRvkbI6rh6BUbNbu9T0,4784
|
|
45
45
|
tests/test_wrappers.py,sha256=TbcTyO2L7fslbzgfDdcVZkencxNQ8cGPZm_iB6c9d6Q,2673
|
|
46
|
-
kleinkram-0.44.
|
|
47
|
-
kleinkram-0.44.
|
|
48
|
-
kleinkram-0.44.
|
|
49
|
-
kleinkram-0.44.
|
|
50
|
-
kleinkram-0.44.
|
|
46
|
+
kleinkram-0.44.1.dist-info/METADATA,sha256=yX6R8JZh8xG0FcfjE8fUaKOAfPoAxbg4TV_UN6k73ac,2807
|
|
47
|
+
kleinkram-0.44.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
48
|
+
kleinkram-0.44.1.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
+
kleinkram-0.44.1.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
+
kleinkram-0.44.1.dist-info/RECORD,,
|
tests/test_printing.py
CHANGED
|
@@ -15,14 +15,14 @@ from kleinkram.printing import parse_metadata_value
|
|
|
15
15
|
def test_format_bytes():
|
|
16
16
|
assert format_bytes(0) == "0 B"
|
|
17
17
|
assert format_bytes(1) == "1 B"
|
|
18
|
-
assert format_bytes(
|
|
19
|
-
assert format_bytes(
|
|
20
|
-
assert format_bytes(
|
|
21
|
-
assert format_bytes(
|
|
22
|
-
assert format_bytes(
|
|
23
|
-
assert format_bytes(
|
|
24
|
-
assert format_bytes(
|
|
25
|
-
assert format_bytes(
|
|
18
|
+
assert format_bytes(999) == "999 B"
|
|
19
|
+
assert format_bytes(1000) == "1.00 KB"
|
|
20
|
+
assert format_bytes(1001) == "1.00 KB"
|
|
21
|
+
assert format_bytes(2000) == "2.00 KB"
|
|
22
|
+
assert format_bytes(10**6) == "1.00 MB"
|
|
23
|
+
assert format_bytes(10**9) == "1.00 GB"
|
|
24
|
+
assert format_bytes(10**12) == "1.00 TB"
|
|
25
|
+
assert format_bytes(10**15) == "1.00 PB"
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def test_add_placeholder_row():
|
|
File without changes
|
{kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|