kleinkram 0.43.6.dev20250401131403__tar.gz → 0.44.0.dev20250407065841__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.

Files changed (57) hide show
  1. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/PKG-INFO +1 -1
  2. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/file_transfer.py +2 -2
  3. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_verify.py +11 -1
  4. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/core.py +34 -7
  5. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/models.py +1 -0
  6. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/printing.py +1 -0
  7. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/wrappers.py +46 -1
  8. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/PKG-INFO +1 -1
  9. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/setup.cfg +1 -1
  10. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/README.md +0 -0
  11. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/__init__.py +0 -0
  12. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/__main__.py +0 -0
  13. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/_version.py +0 -0
  14. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/__init__.py +0 -0
  15. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/client.py +0 -0
  16. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/deser.py +0 -0
  17. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/pagination.py +0 -0
  18. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/query.py +0 -0
  19. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/api/routes.py +0 -0
  20. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/auth.py +0 -0
  21. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/__init__.py +0 -0
  22. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_download.py +0 -0
  23. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_endpoint.py +0 -0
  24. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_file.py +0 -0
  25. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_list.py +0 -0
  26. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_mission.py +0 -0
  27. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_project.py +0 -0
  28. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/_upload.py +0 -0
  29. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/app.py +0 -0
  30. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/cli/error_handling.py +0 -0
  31. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/config.py +0 -0
  32. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/errors.py +0 -0
  33. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/main.py +0 -0
  34. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/py.typed +0 -0
  35. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/types.py +0 -0
  36. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram/utils.py +0 -0
  37. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/SOURCES.txt +0 -0
  38. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/dependency_links.txt +0 -0
  39. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/entry_points.txt +0 -0
  40. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/requires.txt +0 -0
  41. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/kleinkram.egg-info/top_level.txt +0 -0
  42. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/pyproject.toml +0 -0
  43. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/requirements.txt +0 -0
  44. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/setup.py +0 -0
  45. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/testing/__init__.py +0 -0
  46. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/testing/backend_fixtures.py +0 -0
  47. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/__init__.py +0 -0
  48. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/conftest.py +0 -0
  49. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_config.py +0 -0
  50. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_core.py +0 -0
  51. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_end_to_end.py +0 -0
  52. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_error_handling.py +0 -0
  53. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_fixtures.py +0 -0
  54. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_printing.py +0 -0
  55. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_query.py +0 -0
  56. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_utils.py +0 -0
  57. {kleinkram-0.43.6.dev20250401131403 → kleinkram-0.44.0.dev20250407065841}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.43.6.dev20250401131403
3
+ Version: 0.44.0.dev20250407065841
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
@@ -309,10 +309,10 @@ def download_file(
309
309
  if path.exists():
310
310
  local_hash = b64_md5(path)
311
311
  if local_hash != file.hash and not overwrite and file.hash is not None:
312
- return DownloadState.SKIPPED_INVALID_HASH
312
+ return DownloadState.SKIPPED_INVALID_HASH, 0
313
313
 
314
314
  elif local_hash == file.hash:
315
- return DownloadState.SKIPPED_OK
315
+ return DownloadState.SKIPPED_OK, 0
316
316
 
317
317
  # this has to be here
318
318
  if verbose:
@@ -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)
@@ -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.UPLAODED
184
+
185
+ if check_file_size:
186
+ if remote_file.size == file.stat().st_size:
187
+ file_status[file] = FileVerificationStatus.UPLAODED
188
+ else:
189
+ file_status[file] = FileVerificationStatus.MISMATCHED_SIZE
190
+
191
+ if file_status[file] != FileVerificationStatus.UPLAODED:
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.UPLAODED
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
 
@@ -82,4 +82,5 @@ class FileVerificationStatus(str, Enum):
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"
@@ -42,6 +42,7 @@ FILE_VERIFICATION_STATUS_STYLES = {
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
  }
@@ -9,7 +9,7 @@ conversion to the internal representation
9
9
  from __future__ import annotations
10
10
 
11
11
  from pathlib import Path
12
- from typing import Collection
12
+ from typing import Collection, Any
13
13
  from typing import Dict
14
14
  from typing import List
15
15
  from typing import Literal
@@ -39,6 +39,11 @@ def _args_to_project_query(
39
39
  project_names: Optional[Sequence[str]] = None,
40
40
  project_ids: Optional[Sequence[IdLike]] = None,
41
41
  ) -> ProjectQuery:
42
+
43
+ # verify types of passed arguments
44
+ _verify_string_sequence("project_names", project_names)
45
+ _verify_sequence("project_ids", project_ids)
46
+
42
47
  return ProjectQuery(
43
48
  ids=[parse_uuid_like(_id) for _id in project_ids or []],
44
49
  patterns=list(project_names or []),
@@ -51,6 +56,13 @@ def _args_to_mission_query(
51
56
  project_names: Optional[Sequence[str]] = None,
52
57
  project_ids: Optional[Sequence[IdLike]] = None,
53
58
  ) -> MissionQuery:
59
+
60
+ # verify types of passed arguments
61
+ _verify_string_sequence("mission_names", mission_names)
62
+ _verify_sequence("mission_ids", mission_ids)
63
+ _verify_string_sequence("project_names", project_names)
64
+ _verify_sequence("project_ids", project_ids)
65
+
54
66
  return MissionQuery(
55
67
  ids=[parse_uuid_like(_id) for _id in mission_ids or []],
56
68
  patterns=list(mission_names or []),
@@ -60,6 +72,27 @@ def _args_to_mission_query(
60
72
  )
61
73
 
62
74
 
75
+ def _verify_sequence(arg_name: str, arg_value: Optional[Sequence[Any]]) -> None:
76
+ """Verifies that an argument is either None, or a sequence."""
77
+ if arg_value is not None:
78
+ if not isinstance(arg_value, Sequence):
79
+ raise TypeError(f"{arg_name} must be a Sequence, None, or empty array.")
80
+
81
+
82
+ def _verify_string_sequence(arg_name: str, arg_value: Optional[Sequence[Any]]) -> None:
83
+ """Verifies that an argument is either None, an empty sequence, or a sequence of strings."""
84
+ if arg_value is not None:
85
+ if not isinstance(arg_value, Sequence):
86
+ raise TypeError(f"{arg_name} must be a Sequence, None, or empty array.")
87
+ if isinstance(arg_value, str):
88
+ raise TypeError(
89
+ f"{arg_name} cannot be a string, but a sequence of strings."
90
+ )
91
+ for item in arg_value:
92
+ if not isinstance(item, str):
93
+ raise TypeError(f"{arg_name} must contain strings only.")
94
+
95
+
63
96
  def _args_to_file_query(
64
97
  file_names: Optional[Sequence[str]] = None,
65
98
  file_ids: Optional[Sequence[IdLike]] = None,
@@ -68,6 +101,15 @@ def _args_to_file_query(
68
101
  project_names: Optional[Sequence[str]] = None,
69
102
  project_ids: Optional[Sequence[IdLike]] = None,
70
103
  ) -> FileQuery:
104
+
105
+ # verify types of passed arguments
106
+ _verify_string_sequence("file_names", file_names)
107
+ _verify_sequence("file_ids", file_ids)
108
+ _verify_string_sequence("mission_names", mission_names)
109
+ _verify_sequence("mission_ids", mission_ids)
110
+ _verify_string_sequence("project_names", project_names)
111
+ _verify_sequence("project_ids", project_ids)
112
+
71
113
  return FileQuery(
72
114
  ids=[parse_uuid_like(_id) for _id in file_ids or []],
73
115
  patterns=list(file_names or []),
@@ -288,6 +330,9 @@ def verify(
288
330
  project_names=singleton_list(project_name),
289
331
  project_ids=singleton_list(project_id),
290
332
  )
333
+
334
+ _verify_string_sequence("files", files)
335
+
291
336
  return kleinkram.core.verify(
292
337
  client=AuthenticatedClient(),
293
338
  query=query,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.43.6.dev20250401131403
3
+ Version: 0.44.0.dev20250407065841
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
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = kleinkram
3
- version = 0.43.6-dev20250401131403
3
+ version = 0.44.0-dev20250407065841
4
4
  description = give me your bags
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown