kleinkram 0.44.0.dev20250409080532__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.

@@ -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() and not overwrite:
268
- raise FileExistsError(f"file already exists: {path}")
269
-
270
- with httpx.stream("GET", url, timeout=S3_READ_TIMEOUT) as response:
271
- response.raise_for_status()
272
- with open(path, "wb") as f:
273
- with tqdm(
274
- total=size,
275
- desc=f"downloading {path.name}",
276
- unit="B",
277
- unit_scale=True,
278
- leave=False,
279
- disable=not verbose,
280
- ) as pbar:
281
- for chunk in response.iter_bytes(chunk_size=DOWNLOAD_CHUNK_SIZE):
282
- f.write(chunk)
283
- pbar.update(len(chunk))
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.44.0.dev20250409080532
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
@@ -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=9YLEpbR77LTmK5YKPGC1Eil1_HnyuXOdW1Q1bRd0Wnk,18053
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.0.dev20250409080532.dist-info/METADATA,sha256=XmWS4nDjTzSptkV6eW9mFzVnT9NOoMncz6BPcsRnce4,2825
47
- kleinkram-0.44.0.dev20250409080532.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
48
- kleinkram-0.44.0.dev20250409080532.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
49
- kleinkram-0.44.0.dev20250409080532.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
50
- kleinkram-0.44.0.dev20250409080532.dist-info/RECORD,,
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,,