kleinkram 0.44.0.dev20250409080532__py3-none-any.whl → 0.44.1.dev20250414055151__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 +67 -17
- kleinkram/printing.py +2 -2
- kleinkram/utils.py +2 -2
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/METADATA +1 -1
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/RECORD +9 -9
- tests/test_printing.py +8 -8
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/WHEEL +0 -0
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/entry_points.txt +0 -0
- {kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/top_level.txt +0 -0
kleinkram/api/file_transfer.py
CHANGED
|
@@ -17,6 +17,7 @@ from uuid import UUID
|
|
|
17
17
|
import boto3.s3.transfer
|
|
18
18
|
import botocore.config
|
|
19
19
|
import httpx
|
|
20
|
+
from httpx import RemoteProtocolError
|
|
20
21
|
from rich.console import Console
|
|
21
22
|
from tqdm import tqdm
|
|
22
23
|
|
|
@@ -44,6 +45,9 @@ MAX_UPLOAD_RETRIES = 3
|
|
|
44
45
|
S3_MAX_RETRIES = 60 # same as frontend
|
|
45
46
|
S3_READ_TIMEOUT = 60 * 5 # 5 minutes
|
|
46
47
|
|
|
48
|
+
RETRY_BACKOFF_BASE = 2 # exponential backoff base
|
|
49
|
+
MAX_RETRIES = 5
|
|
50
|
+
|
|
47
51
|
|
|
48
52
|
class UploadCredentials(NamedTuple):
|
|
49
53
|
access_key: str
|
|
@@ -264,23 +268,69 @@ def _get_file_download(client: AuthenticatedClient, id: UUID) -> str:
|
|
|
264
268
|
def _url_download(
|
|
265
269
|
url: str, *, path: Path, size: int, overwrite: bool = False, verbose: bool = False
|
|
266
270
|
) -> None:
|
|
267
|
-
if path.exists()
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
271
|
+
if path.exists():
|
|
272
|
+
if overwrite:
|
|
273
|
+
path.unlink()
|
|
274
|
+
downloaded = 0
|
|
275
|
+
else:
|
|
276
|
+
downloaded = path.stat().st_size
|
|
277
|
+
if downloaded >= size:
|
|
278
|
+
raise FileExistsError(f"file already exists and is complete: {path}")
|
|
279
|
+
else:
|
|
280
|
+
downloaded = 0
|
|
281
|
+
|
|
282
|
+
attempt = 0
|
|
283
|
+
while downloaded < size:
|
|
284
|
+
try:
|
|
285
|
+
headers = {"Range": f"bytes={downloaded}-"}
|
|
286
|
+
with httpx.stream(
|
|
287
|
+
"GET", url, headers=headers, timeout=S3_READ_TIMEOUT
|
|
288
|
+
) as response:
|
|
289
|
+
# Accept both 206 Partial Content and 200 OK if starting from 0
|
|
290
|
+
if not (
|
|
291
|
+
response.status_code == 206
|
|
292
|
+
or (downloaded == 0 and response.status_code == 200)
|
|
293
|
+
):
|
|
294
|
+
response.raise_for_status()
|
|
295
|
+
raise RuntimeError(
|
|
296
|
+
f"Expected 206 Partial Content, got {response.status_code}"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
mode = "ab" if downloaded > 0 else "wb"
|
|
300
|
+
with open(path, mode) as f:
|
|
301
|
+
with tqdm(
|
|
302
|
+
total=size,
|
|
303
|
+
initial=downloaded,
|
|
304
|
+
desc=f"downloading {path.name}",
|
|
305
|
+
unit="B",
|
|
306
|
+
unit_scale=True,
|
|
307
|
+
leave=False,
|
|
308
|
+
disable=not verbose,
|
|
309
|
+
) as pbar:
|
|
310
|
+
for chunk in response.iter_bytes(
|
|
311
|
+
chunk_size=DOWNLOAD_CHUNK_SIZE
|
|
312
|
+
):
|
|
313
|
+
attempt = 0 # reset attempt counter on successful download of non-empty chunk
|
|
314
|
+
if not chunk:
|
|
315
|
+
break
|
|
316
|
+
f.write(chunk)
|
|
317
|
+
downloaded += len(chunk)
|
|
318
|
+
pbar.update(len(chunk))
|
|
319
|
+
break # download complete
|
|
320
|
+
except RemoteProtocolError as e:
|
|
321
|
+
logger.info(f"Error: {e}, retrying...")
|
|
322
|
+
attempt += 1
|
|
323
|
+
if attempt > MAX_RETRIES:
|
|
324
|
+
raise RuntimeError(
|
|
325
|
+
f"Download failed after {MAX_RETRIES} retries due to RemoteProtocolError"
|
|
326
|
+
) from e
|
|
327
|
+
if verbose:
|
|
328
|
+
print(
|
|
329
|
+
f"RemoteProtocolError on attempt {attempt}/{MAX_RETRIES}, retrying after backoff..."
|
|
330
|
+
)
|
|
331
|
+
sleep(RETRY_BACKOFF_BASE**attempt)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
raise RuntimeError(f"Failed to download: {e}") from e
|
|
284
334
|
|
|
285
335
|
|
|
286
336
|
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}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.44.
|
|
3
|
+
Version: 0.44.1.dev20250414055151
|
|
4
4
|
Summary: give me your bags
|
|
5
5
|
Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
|
|
6
6
|
Author-email: pucyril@ethz.ch, dgarmier@ethz.ch, jschwab@ethz.ch
|
{kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/RECORD
RENAMED
|
@@ -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=9UMEBZP7IV75IHg_tBEnBMQTsT_SIBjG0HDHijAfXco,20094
|
|
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.dev20250414055151.dist-info/METADATA,sha256=mFTGic-s2WeJ3_tGd_508rnK82QRKhtOHHIR-iRfXzY,2825
|
|
47
|
+
kleinkram-0.44.1.dev20250414055151.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
48
|
+
kleinkram-0.44.1.dev20250414055151.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
+
kleinkram-0.44.1.dev20250414055151.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
+
kleinkram-0.44.1.dev20250414055151.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():
|
{kleinkram-0.44.0.dev20250409080532.dist-info → kleinkram-0.44.1.dev20250414055151.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|