kleinkram 0.43.6.dev20250401140552__py3-none-any.whl → 0.44.0__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 +26 -12
- kleinkram/cli/_download.py +2 -1
- kleinkram/cli/_verify.py +11 -1
- kleinkram/core.py +34 -7
- kleinkram/models.py +2 -1
- kleinkram/printing.py +3 -2
- {kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/METADATA +1 -1
- {kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/RECORD +12 -12
- tests/test_core.py +1 -1
- {kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/WHEEL +0 -0
- {kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/entry_points.txt +0 -0
- {kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/top_level.txt +0 -0
kleinkram/api/file_transfer.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import signal
|
|
4
3
|
import logging
|
|
5
4
|
import sys
|
|
6
5
|
from concurrent.futures import Future
|
|
@@ -18,6 +17,9 @@ from uuid import UUID
|
|
|
18
17
|
import boto3.s3.transfer
|
|
19
18
|
import botocore.config
|
|
20
19
|
import httpx
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from tqdm import tqdm
|
|
22
|
+
|
|
21
23
|
from kleinkram.api.client import AuthenticatedClient
|
|
22
24
|
from kleinkram.config import get_config
|
|
23
25
|
from kleinkram.errors import AccessDenied
|
|
@@ -28,8 +30,6 @@ from kleinkram.utils import format_bytes
|
|
|
28
30
|
from kleinkram.utils import format_error
|
|
29
31
|
from kleinkram.utils import format_traceback
|
|
30
32
|
from kleinkram.utils import styled_string
|
|
31
|
-
from rich.console import Console
|
|
32
|
-
from tqdm import tqdm
|
|
33
33
|
|
|
34
34
|
logger = logging.getLogger(__name__)
|
|
35
35
|
|
|
@@ -267,7 +267,7 @@ def _url_download(
|
|
|
267
267
|
if path.exists() and not overwrite:
|
|
268
268
|
raise FileExistsError(f"file already exists: {path}")
|
|
269
269
|
|
|
270
|
-
with httpx.stream("GET", url) as response:
|
|
270
|
+
with httpx.stream("GET", url, timeout=S3_READ_TIMEOUT) as response:
|
|
271
271
|
response.raise_for_status()
|
|
272
272
|
with open(path, "wb") as f:
|
|
273
273
|
with tqdm(
|
|
@@ -289,6 +289,7 @@ class DownloadState(Enum):
|
|
|
289
289
|
DOWNLOADED_INVALID_HASH = 3
|
|
290
290
|
SKIPPED_INVALID_HASH = 4
|
|
291
291
|
SKIPPED_INVALID_REMOTE_STATE = 5
|
|
292
|
+
SKIPPED_FILE_SIZE_MISMATCH = 6
|
|
292
293
|
|
|
293
294
|
|
|
294
295
|
def download_file(
|
|
@@ -307,17 +308,27 @@ def download_file(
|
|
|
307
308
|
return DownloadState.SKIPPED_INVALID_REMOTE_STATE, 0
|
|
308
309
|
|
|
309
310
|
if path.exists():
|
|
310
|
-
local_hash = b64_md5(path)
|
|
311
|
-
if local_hash != file.hash and not overwrite and file.hash is not None:
|
|
312
|
-
return DownloadState.SKIPPED_INVALID_HASH, 0
|
|
313
311
|
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
# compare file size
|
|
313
|
+
if file.size == path.stat().st_size:
|
|
314
|
+
local_hash = b64_md5(path)
|
|
315
|
+
if local_hash != file.hash and not overwrite and file.hash is not None:
|
|
316
|
+
return DownloadState.SKIPPED_INVALID_HASH, 0
|
|
316
317
|
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
elif local_hash == file.hash:
|
|
319
|
+
return DownloadState.SKIPPED_OK, 0
|
|
320
|
+
|
|
321
|
+
elif verbose:
|
|
322
|
+
tqdm.write(
|
|
323
|
+
styled_string(f"overwriting {path}, hash mismatch", style="yellow")
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
elif not overwrite and file.size is not None:
|
|
327
|
+
return DownloadState.SKIPPED_FILE_SIZE_MISMATCH, 0
|
|
328
|
+
|
|
329
|
+
elif verbose:
|
|
319
330
|
tqdm.write(
|
|
320
|
-
styled_string(f"overwriting {path},
|
|
331
|
+
styled_string(f"overwriting {path}, file size mismatch", style="yellow")
|
|
321
332
|
)
|
|
322
333
|
|
|
323
334
|
# request a download url
|
|
@@ -400,6 +411,7 @@ DOWNLOAD_STATE_COLOR = {
|
|
|
400
411
|
DownloadState.SKIPPED_OK: "green",
|
|
401
412
|
DownloadState.DOWNLOADED_INVALID_HASH: "red",
|
|
402
413
|
DownloadState.SKIPPED_INVALID_HASH: "yellow",
|
|
414
|
+
DownloadState.SKIPPED_FILE_SIZE_MISMATCH: "yellow",
|
|
403
415
|
DownloadState.SKIPPED_INVALID_REMOTE_STATE: "purple",
|
|
404
416
|
}
|
|
405
417
|
|
|
@@ -432,6 +444,8 @@ def _download_handler(
|
|
|
432
444
|
msg = f"skipped {path} already downloaded (hash ok)"
|
|
433
445
|
elif state == DownloadState.SKIPPED_INVALID_HASH:
|
|
434
446
|
msg = f"skipped {path}, exists with hash mismatch (use --overwrite?)"
|
|
447
|
+
elif state == DownloadState.SKIPPED_FILE_SIZE_MISMATCH:
|
|
448
|
+
msg = f"skipped {path}, exists with file size mismatch (use --overwrite?)"
|
|
435
449
|
elif state == DownloadState.SKIPPED_INVALID_REMOTE_STATE:
|
|
436
450
|
msg = f"skipped {path}, remote file has invalid state ({file.state.value})"
|
|
437
451
|
else:
|
kleinkram/cli/_download.py
CHANGED
|
@@ -43,7 +43,8 @@ def download(
|
|
|
43
43
|
False, help="save files in nested directories, project-name/mission-name"
|
|
44
44
|
),
|
|
45
45
|
overwrite: bool = typer.Option(
|
|
46
|
-
False,
|
|
46
|
+
False,
|
|
47
|
+
help="overwrite files if they already exist and don't match the file size or file hash",
|
|
47
48
|
),
|
|
48
49
|
) -> None:
|
|
49
50
|
# create destionation directory
|
kleinkram/cli/_verify.py
CHANGED
|
@@ -32,7 +32,15 @@ def verify(
|
|
|
32
32
|
None, "--project", "-p", help="project id or name"
|
|
33
33
|
),
|
|
34
34
|
mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"),
|
|
35
|
-
skip_hash: bool = typer.Option(
|
|
35
|
+
skip_hash: bool = typer.Option(None, help="skip hash check"),
|
|
36
|
+
check_file_hash: bool = typer.Option(
|
|
37
|
+
True,
|
|
38
|
+
help="check file hash. If True, file names and file hashes are checked.",
|
|
39
|
+
),
|
|
40
|
+
check_file_size: bool = typer.Option(
|
|
41
|
+
True,
|
|
42
|
+
help="check file size. If True, file names and file sizes are checked.",
|
|
43
|
+
),
|
|
36
44
|
) -> None:
|
|
37
45
|
# get all filepaths
|
|
38
46
|
file_paths = [Path(file) for file in files]
|
|
@@ -51,6 +59,8 @@ def verify(
|
|
|
51
59
|
query=mission_query,
|
|
52
60
|
file_paths=file_paths,
|
|
53
61
|
skip_hash=skip_hash,
|
|
62
|
+
check_file_hash=check_file_hash,
|
|
63
|
+
check_file_size=check_file_size,
|
|
54
64
|
verbose=verbose,
|
|
55
65
|
)
|
|
56
66
|
print_file_verification_status(file_status, pprint=verbose)
|
kleinkram/core.py
CHANGED
|
@@ -133,9 +133,20 @@ def verify(
|
|
|
133
133
|
client: AuthenticatedClient,
|
|
134
134
|
query: MissionQuery,
|
|
135
135
|
file_paths: Sequence[Path],
|
|
136
|
-
skip_hash: bool =
|
|
136
|
+
skip_hash: Optional[bool] = None,
|
|
137
|
+
check_file_hash: bool = True,
|
|
138
|
+
check_file_size: bool = False,
|
|
137
139
|
verbose: bool = False,
|
|
138
140
|
) -> Dict[Path, FileVerificationStatus]:
|
|
141
|
+
|
|
142
|
+
# add deprecated warning for skip_hash
|
|
143
|
+
if skip_hash is not None:
|
|
144
|
+
print(
|
|
145
|
+
"Warning: --skip-hash is deprecated and will be removed in a future version. "
|
|
146
|
+
"Use --check-file-hash=False instead.",
|
|
147
|
+
)
|
|
148
|
+
check_file_hash = not skip_hash
|
|
149
|
+
|
|
139
150
|
# check that file paths are for valid files and have valid suffixes
|
|
140
151
|
check_file_paths(file_paths)
|
|
141
152
|
|
|
@@ -167,14 +178,30 @@ def verify(
|
|
|
167
178
|
if remote_file.state == FileState.UPLOADING:
|
|
168
179
|
file_status[file] = FileVerificationStatus.UPLOADING
|
|
169
180
|
elif remote_file.state == FileState.OK:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
|
|
182
|
+
# default case, will be overwritten if we find a mismatch
|
|
183
|
+
file_status[file] = FileVerificationStatus.UPLOADED
|
|
184
|
+
|
|
185
|
+
if check_file_size:
|
|
186
|
+
if remote_file.size == file.stat().st_size:
|
|
187
|
+
file_status[file] = FileVerificationStatus.UPLOADED
|
|
188
|
+
else:
|
|
189
|
+
file_status[file] = FileVerificationStatus.MISMATCHED_SIZE
|
|
190
|
+
|
|
191
|
+
if file_status[file] != FileVerificationStatus.UPLOADED:
|
|
192
|
+
continue # abort if we already found a mismatch
|
|
193
|
+
|
|
194
|
+
if check_file_hash:
|
|
195
|
+
if remote_file.hash is None:
|
|
196
|
+
file_status[file] = FileVerificationStatus.COMPUTING_HASH
|
|
197
|
+
elif remote_file.hash == b64_md5(file):
|
|
198
|
+
file_status[file] = FileVerificationStatus.UPLOADED
|
|
199
|
+
else:
|
|
200
|
+
file_status[file] = FileVerificationStatus.MISMATCHED_HASH
|
|
201
|
+
|
|
176
202
|
else:
|
|
177
203
|
file_status[file] = FileVerificationStatus.UNKNOWN
|
|
204
|
+
|
|
178
205
|
return file_status
|
|
179
206
|
|
|
180
207
|
|
kleinkram/models.py
CHANGED
|
@@ -77,9 +77,10 @@ class File:
|
|
|
77
77
|
|
|
78
78
|
# this is the file state for the verify command
|
|
79
79
|
class FileVerificationStatus(str, Enum):
|
|
80
|
-
|
|
80
|
+
UPLOADED = "uploaded"
|
|
81
81
|
UPLOADING = "uploading"
|
|
82
82
|
COMPUTING_HASH = "computing hash"
|
|
83
83
|
MISSING = "missing"
|
|
84
84
|
MISMATCHED_HASH = "hash mismatch"
|
|
85
|
+
MISMATCHED_SIZE = "size mismatch"
|
|
85
86
|
UNKNOWN = "unknown"
|
kleinkram/printing.py
CHANGED
|
@@ -38,10 +38,11 @@ FILE_STATE_COLOR = {
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
FILE_VERIFICATION_STATUS_STYLES = {
|
|
41
|
-
FileVerificationStatus.
|
|
41
|
+
FileVerificationStatus.UPLOADED: "green",
|
|
42
42
|
FileVerificationStatus.UPLOADING: "yellow",
|
|
43
43
|
FileVerificationStatus.MISSING: "yellow",
|
|
44
44
|
FileVerificationStatus.MISMATCHED_HASH: "red",
|
|
45
|
+
FileVerificationStatus.MISMATCHED_SIZE: "red",
|
|
45
46
|
FileVerificationStatus.UNKNOWN: "gray",
|
|
46
47
|
FileVerificationStatus.COMPUTING_HASH: "purple",
|
|
47
48
|
}
|
|
@@ -296,7 +297,7 @@ def print_file_verification_status(
|
|
|
296
297
|
else:
|
|
297
298
|
for path, status in file_status.items():
|
|
298
299
|
stream = (
|
|
299
|
-
sys.stdout if status == FileVerificationStatus.
|
|
300
|
+
sys.stdout if status == FileVerificationStatus.UPLOADED else sys.stderr
|
|
300
301
|
)
|
|
301
302
|
print(path, file=stream, flush=True)
|
|
302
303
|
|
|
@@ -3,11 +3,11 @@ kleinkram/__main__.py,sha256=B9RiZxfO4jpCmWPUHyKJ7_EoZlEG4sPpH-nz7T_YhhQ,125
|
|
|
3
3
|
kleinkram/_version.py,sha256=QYJyRTcqFcJj4qWYpqs7WcoOP6jxDMqyvxLY-cD6KcE,129
|
|
4
4
|
kleinkram/auth.py,sha256=XD_rHOyJmYYfO7QJf3TLYH5qXA22gXGWi7PT3jujlVs,2968
|
|
5
5
|
kleinkram/config.py,sha256=nx6uSM5nLP4SKe8b9VAx4KDtCCwtyshXmzbEJcUwpsY,7411
|
|
6
|
-
kleinkram/core.py,sha256=
|
|
6
|
+
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
|
-
kleinkram/models.py,sha256=
|
|
10
|
-
kleinkram/printing.py,sha256=
|
|
9
|
+
kleinkram/models.py,sha256=MkWdGIWuCSnyg45DbdEUhtGIprwPYwKppYumHkRAS18,1870
|
|
10
|
+
kleinkram/printing.py,sha256=q29iYGkBUCmeJNzNacLHRP-8BqF2UhoXirwX7ja7a1k,12212
|
|
11
11
|
kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
|
|
13
13
|
kleinkram/utils.py,sha256=AsKZxEGStn03L2tqqiMVCQCrDyl8HhwOfpa3no4dfYc,6767
|
|
@@ -15,19 +15,19 @@ 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=9YLEpbR77LTmK5YKPGC1Eil1_HnyuXOdW1Q1bRd0Wnk,18053
|
|
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
|
|
22
22
|
kleinkram/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
kleinkram/cli/_download.py,sha256=
|
|
23
|
+
kleinkram/cli/_download.py,sha256=e0fDyp_CFOdbKIUGKmtITvAVINa6STYJk5w5QlElXSs,2394
|
|
24
24
|
kleinkram/cli/_endpoint.py,sha256=oY0p4bnuHLEDJCXtTmir4AHswcKAygZ8I4IWC3RFcKc,1796
|
|
25
25
|
kleinkram/cli/_file.py,sha256=Q2fLDdUyfHFmdGC6wIxMqgEl0F76qszhzWJrRV5rTBM,2973
|
|
26
26
|
kleinkram/cli/_list.py,sha256=5gI3aIUeKC0_eWPQqdFXSBBFvpkTTJSm31TamHa197c,3090
|
|
27
27
|
kleinkram/cli/_mission.py,sha256=zDFnOozOFckpuREFgIPt1IzG5q3b1bsNxYlWQoHoz5A,5301
|
|
28
28
|
kleinkram/cli/_project.py,sha256=N0C96NC_onCEwTteYp2wgkkwkdJt-1q43LFdqNXfjC8,3398
|
|
29
29
|
kleinkram/cli/_upload.py,sha256=gOhbjbmqhmwW7p6bWlSvI53vLHvBFO9QqD1kdU92I2k,2813
|
|
30
|
-
kleinkram/cli/_verify.py,sha256=
|
|
30
|
+
kleinkram/cli/_verify.py,sha256=n9QThY0JnqaIqw6udYXdRQGcpUl2lIbFXGQIgpTnDPE,2112
|
|
31
31
|
kleinkram/cli/app.py,sha256=m2qq4z95QllvXnxh3koPp0kq06I5R9etsJV8qSV8TMk,7037
|
|
32
32
|
kleinkram/cli/error_handling.py,sha256=wK3tzeKVSrZm-xmiyzGLnGT2E4TRpyxhaak6GWGP7P8,1921
|
|
33
33
|
testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -35,7 +35,7 @@ testing/backend_fixtures.py,sha256=t5QWwyezHUhxxAlbUuE_eFmpyRaGbnWNNcGPwrO17JM,1
|
|
|
35
35
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
tests/conftest.py,sha256=5MLYQOtQoXWl0TRkYntYKNdqpd4hl9m0XTRi5OXanYI,104
|
|
37
37
|
tests/test_config.py,sha256=uvVh1iUSCoZc8YioxS_GjF-J7m4JE2z4XUbwK_4wDV0,5855
|
|
38
|
-
tests/test_core.py,sha256=
|
|
38
|
+
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
|
|
@@ -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.
|
|
47
|
-
kleinkram-0.
|
|
48
|
-
kleinkram-0.
|
|
49
|
-
kleinkram-0.
|
|
50
|
-
kleinkram-0.
|
|
46
|
+
kleinkram-0.44.0.dist-info/METADATA,sha256=1d3gNavFGPfJcAAgXyknZYQZ1zjpVcpI1lXFlxmR12A,2807
|
|
47
|
+
kleinkram-0.44.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
48
|
+
kleinkram-0.44.0.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
+
kleinkram-0.44.0.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
+
kleinkram-0.44.0.dist-info/RECORD,,
|
tests/test_core.py
CHANGED
|
File without changes
|
{kleinkram-0.43.6.dev20250401140552.dist-info → kleinkram-0.44.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|