kleinkram 0.43.3.dev20250331145455__tar.gz → 0.43.4__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.
Potentially problematic release.
This version of kleinkram might be problematic. Click here for more details.
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/PKG-INFO +1 -1
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/file_transfer.py +99 -29
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/PKG-INFO +1 -1
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/setup.cfg +1 -1
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/README.md +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/__init__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/__main__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/_version.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/__init__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/client.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/deser.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/pagination.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/query.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/api/routes.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/auth.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/__init__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_download.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_endpoint.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_file.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_list.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_mission.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_project.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_upload.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/_verify.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/app.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/cli/error_handling.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/config.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/core.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/errors.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/main.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/models.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/printing.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/py.typed +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/types.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/utils.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram/wrappers.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/SOURCES.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/dependency_links.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/entry_points.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/requires.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.egg-info/top_level.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/pyproject.toml +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/requirements.txt +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/setup.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/testing/__init__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/testing/backend_fixtures.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/__init__.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/conftest.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_config.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_core.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_end_to_end.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_error_handling.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_fixtures.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_printing.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_query.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_utils.py +0 -0
- {kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/tests/test_wrappers.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import signal
|
|
3
4
|
import logging
|
|
4
5
|
import sys
|
|
5
6
|
from concurrent.futures import Future
|
|
@@ -7,7 +8,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
7
8
|
from concurrent.futures import as_completed
|
|
8
9
|
from enum import Enum
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from time import monotonic
|
|
11
|
+
from time import monotonic, sleep
|
|
11
12
|
from typing import Dict
|
|
12
13
|
from typing import NamedTuple
|
|
13
14
|
from typing import Optional
|
|
@@ -39,6 +40,7 @@ UPLOAD_CANCEL = "/files/cancelUpload"
|
|
|
39
40
|
DOWNLOAD_CHUNK_SIZE = 1024 * 1024 * 16
|
|
40
41
|
DOWNLOAD_URL = "/files/download"
|
|
41
42
|
|
|
43
|
+
MAX_UPLOAD_RETRIES = 3
|
|
42
44
|
S3_MAX_RETRIES = 60 # same as frontend
|
|
43
45
|
S3_READ_TIMEOUT = 60 * 5 # 5 minutes
|
|
44
46
|
|
|
@@ -66,8 +68,8 @@ def _cancel_file_upload(
|
|
|
66
68
|
client: AuthenticatedClient, file_id: UUID, mission_id: UUID
|
|
67
69
|
) -> None:
|
|
68
70
|
data = {
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
+
"uuids": [str(file_id)],
|
|
72
|
+
"missionUuid": str(mission_id),
|
|
71
73
|
}
|
|
72
74
|
resp = client.post(UPLOAD_CANCEL, json=data)
|
|
73
75
|
resp.raise_for_status()
|
|
@@ -151,6 +153,37 @@ class UploadState(Enum):
|
|
|
151
153
|
CANCELED = 3
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
def _get_upload_credentials_with_retry(
|
|
157
|
+
client, pbar, filename, mission_id, max_attempts=5
|
|
158
|
+
):
|
|
159
|
+
"""
|
|
160
|
+
Retrieves upload credentials with retry logic.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
client: The client object used for retrieving credentials.
|
|
164
|
+
filename: The internal filename.
|
|
165
|
+
mission_id: The mission ID.
|
|
166
|
+
max_attempts: Maximum number of retry attempts.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The upload credentials or None if retrieval fails after all attempts.
|
|
170
|
+
"""
|
|
171
|
+
attempt = 0
|
|
172
|
+
while attempt < max_attempts:
|
|
173
|
+
creds = _get_upload_creditials(
|
|
174
|
+
client, internal_filename=filename, mission_id=mission_id
|
|
175
|
+
)
|
|
176
|
+
if creds is not None:
|
|
177
|
+
return creds
|
|
178
|
+
|
|
179
|
+
attempt += 1
|
|
180
|
+
if attempt < max_attempts:
|
|
181
|
+
delay = 2**attempt # Exponential backoff (2, 4, 8, 16...)
|
|
182
|
+
sleep(delay)
|
|
183
|
+
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
154
187
|
# TODO: i dont want to handle errors at this level
|
|
155
188
|
def upload_file(
|
|
156
189
|
client: AuthenticatedClient,
|
|
@@ -161,40 +194,54 @@ def upload_file(
|
|
|
161
194
|
verbose: bool = False,
|
|
162
195
|
s3_endpoint: Optional[str] = None,
|
|
163
196
|
) -> Tuple[UploadState, int]:
|
|
164
|
-
"""
|
|
197
|
+
"""
|
|
165
198
|
returns UploadState and bytes uploaded (0 if not uploaded)
|
|
199
|
+
Retries up to 3 times on failure.
|
|
166
200
|
"""
|
|
167
201
|
if s3_endpoint is None:
|
|
168
202
|
s3_endpoint = get_config().endpoint.s3
|
|
169
203
|
|
|
170
204
|
total_size = path.stat().st_size
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
for attempt in range(MAX_UPLOAD_RETRIES):
|
|
206
|
+
with tqdm(
|
|
207
|
+
total=total_size,
|
|
208
|
+
unit="B",
|
|
209
|
+
unit_scale=True,
|
|
210
|
+
desc=f"uploading {path}...",
|
|
211
|
+
leave=False,
|
|
212
|
+
disable=not verbose,
|
|
213
|
+
) as pbar:
|
|
214
|
+
|
|
215
|
+
# get per file upload credentials
|
|
216
|
+
creds = _get_upload_credentials_with_retry(
|
|
217
|
+
client, pbar, filename, mission_id, max_attempts=5 if attempt > 0 else 1
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if creds is None:
|
|
221
|
+
return UploadState.EXISTS, 0
|
|
185
222
|
|
|
186
|
-
try:
|
|
187
|
-
_s3_upload(path, endpoint=s3_endpoint, credentials=creds, pbar=pbar)
|
|
188
|
-
except Exception as e:
|
|
189
|
-
logger.error(format_traceback(e))
|
|
190
223
|
try:
|
|
191
|
-
|
|
192
|
-
except Exception as
|
|
193
|
-
logger.error(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
224
|
+
_s3_upload(path, endpoint=s3_endpoint, credentials=creds, pbar=pbar)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(format_traceback(e))
|
|
227
|
+
try:
|
|
228
|
+
_cancel_file_upload(client, creds.file_id, mission_id)
|
|
229
|
+
except Exception as cancel_e:
|
|
230
|
+
logger.error(
|
|
231
|
+
f"Failed to cancel upload for {creds.file_id}: {cancel_e}"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if attempt < 2: # Retry if not the last attempt
|
|
235
|
+
pbar.update(0)
|
|
236
|
+
logger.error(f"Retrying upload for {attempt + 1}")
|
|
237
|
+
continue
|
|
238
|
+
else:
|
|
239
|
+
logger.error(f"Cancelling upload for {attempt}")
|
|
240
|
+
raise e from e
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
_confirm_file_upload(client, creds.file_id, b64_md5(path))
|
|
244
|
+
return UploadState.UPLOADED, total_size
|
|
198
245
|
|
|
199
246
|
|
|
200
247
|
def _get_file_download(client: AuthenticatedClient, id: UUID) -> str:
|
|
@@ -420,6 +467,9 @@ def upload_files(
|
|
|
420
467
|
) as pbar:
|
|
421
468
|
start = monotonic()
|
|
422
469
|
futures: Dict[Future[Tuple[UploadState, int]], Path] = {}
|
|
470
|
+
|
|
471
|
+
skipped_files = 0
|
|
472
|
+
failed_files = 0
|
|
423
473
|
with ThreadPoolExecutor(max_workers=n_workers) as executor:
|
|
424
474
|
for name, path in files.items():
|
|
425
475
|
if not path.is_file():
|
|
@@ -441,6 +491,16 @@ def upload_files(
|
|
|
441
491
|
|
|
442
492
|
total_uploaded_bytes = 0
|
|
443
493
|
for future in as_completed(futures):
|
|
494
|
+
|
|
495
|
+
if future.exception():
|
|
496
|
+
failed_files += 1
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
future.exception() is None
|
|
500
|
+
and future.result()[0] == UploadState.EXISTS
|
|
501
|
+
):
|
|
502
|
+
skipped_files += 1
|
|
503
|
+
|
|
444
504
|
path = futures[future]
|
|
445
505
|
uploaded_bytes = _upload_handler(future, path, verbose=verbose)
|
|
446
506
|
total_uploaded_bytes += uploaded_bytes
|
|
@@ -455,6 +515,16 @@ def upload_files(
|
|
|
455
515
|
console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}")
|
|
456
516
|
console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}")
|
|
457
517
|
|
|
518
|
+
if failed_files > 0:
|
|
519
|
+
console.print(
|
|
520
|
+
f"\nUploaded {len(files) - failed_files - skipped_files} files, {skipped_files} skipped, {failed_files} uploads failed",
|
|
521
|
+
style="red",
|
|
522
|
+
)
|
|
523
|
+
else:
|
|
524
|
+
console.print(
|
|
525
|
+
f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped"
|
|
526
|
+
)
|
|
527
|
+
|
|
458
528
|
|
|
459
529
|
def download_files(
|
|
460
530
|
client: AuthenticatedClient,
|
|
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
|
|
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
|
|
File without changes
|
{kleinkram-0.43.3.dev20250331145455 → kleinkram-0.43.4}/kleinkram.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|