kleinkram 0.44.0.dev20250409080103__py3-none-any.whl → 0.44.1.dev20250409111712__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 +69 -21
- {kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/METADATA +1 -1
- {kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/RECORD +6 -6
- {kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/WHEEL +0 -0
- {kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/entry_points.txt +0 -0
- {kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.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):
|
|
@@ -320,7 +370,7 @@ def download_file(
|
|
|
320
370
|
|
|
321
371
|
elif verbose:
|
|
322
372
|
tqdm.write(
|
|
323
|
-
styled_string(f"overwriting {path}, hash
|
|
373
|
+
styled_string(f"overwriting {path}, hash mismatch", style="yellow")
|
|
324
374
|
)
|
|
325
375
|
|
|
326
376
|
elif not overwrite and file.size is not None:
|
|
@@ -328,9 +378,7 @@ def download_file(
|
|
|
328
378
|
|
|
329
379
|
elif verbose:
|
|
330
380
|
tqdm.write(
|
|
331
|
-
styled_string(
|
|
332
|
-
f"overwriting {path}, file size missmatch", style="yellow"
|
|
333
|
-
)
|
|
381
|
+
styled_string(f"overwriting {path}, file size mismatch", style="yellow")
|
|
334
382
|
)
|
|
335
383
|
|
|
336
384
|
# request a download url
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.44.
|
|
3
|
+
Version: 0.44.1.dev20250409111712
|
|
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.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/RECORD
RENAMED
|
@@ -15,7 +15,7 @@ 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
|
|
@@ -43,8 +43,8 @@ tests/test_printing.py,sha256=Jz1AjqmqBRjp1JLm6H1oVJyvGaMPlahVXdKnd7UDQFc,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.dev20250409111712.dist-info/METADATA,sha256=2IKtDT39yl-yI4fvJqCsR2oSfLXtAtBp4As7vMQauAE,2825
|
|
47
|
+
kleinkram-0.44.1.dev20250409111712.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
48
|
+
kleinkram-0.44.1.dev20250409111712.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
+
kleinkram-0.44.1.dev20250409111712.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
+
kleinkram-0.44.1.dev20250409111712.dist-info/RECORD,,
|
{kleinkram-0.44.0.dev20250409080103.dist-info → kleinkram-0.44.1.dev20250409111712.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|