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.

@@ -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
- elif local_hash == file.hash:
315
- return DownloadState.SKIPPED_OK, 0
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
- # this has to be here
318
- if verbose:
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}, hash missmatch", style="yellow")
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:
@@ -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, help="overwrite files if they already exist and don't match the filehash"
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(False, help="skip hash check"),
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 = False,
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
- if remote_file.hash is None:
171
- file_status[file] = FileVerificationStatus.COMPUTING_HASH
172
- elif skip_hash or remote_file.hash == b64_md5(file):
173
- file_status[file] = FileVerificationStatus.UPLAODED
174
- else:
175
- file_status[file] = FileVerificationStatus.MISMATCHED_HASH
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
- UPLAODED = "uploaded"
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.UPLAODED: "green",
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.UPLAODED else sys.stderr
300
+ sys.stdout if status == FileVerificationStatus.UPLOADED else sys.stderr
300
301
  )
301
302
  print(path, file=stream, flush=True)
302
303
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.43.6.dev20250401140552
3
+ Version: 0.44.0
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
@@ -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=kD2aqN_VhlUGZV4ocij8M_VZCQwFsCf42mJmdupRQVU,8678
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=8nJlPrKVLSmehspeuQSFV6nUo76JzehUn6KIZYH1xy4,1832
10
- kleinkram/printing.py,sha256=_vIjs-lPVgv21ER5q5iYtx44OTs5y8wyd32w27WqocM,12161
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=IqQoKdpK9oqPllE4deell64VoP__vjKOU8lY2gao4vQ,17458
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=H4YlXJkZE4Md02nzgrO_i8Hsm4ZIejPsxBEKkcn4KHs,2371
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=0ABVa4U_WzaV36ClR8NsOIG7KAMRlnFmsbtnHhbWVj4,1742
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=9oAb-jxWlRI6EzMwe-oStUFpK4R6ad5vzy95gZgAeuk,5623
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.43.6.dev20250401140552.dist-info/METADATA,sha256=ZFWm2KBlpIMEGzaQQjPDXoIrXPUwRZ0KDjrLYvLCm08,2825
47
- kleinkram-0.43.6.dev20250401140552.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
48
- kleinkram-0.43.6.dev20250401140552.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
49
- kleinkram-0.43.6.dev20250401140552.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
50
- kleinkram-0.43.6.dev20250401140552.dist-info/RECORD,,
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
@@ -167,7 +167,7 @@ def test_verify(mission):
167
167
  )
168
168
 
169
169
  assert all(
170
- status == FileVerificationStatus.UPLAODED for status in verify_status.values()
170
+ status == FileVerificationStatus.UPLOADED for status in verify_status.values()
171
171
  )
172
172
 
173
173