kleinkram 0.41.3.dev20250304135104__tar.gz → 0.53.0__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.
Files changed (63) hide show
  1. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/PKG-INFO +4 -2
  2. kleinkram-0.53.0/kleinkram/api/deser.py +350 -0
  3. kleinkram-0.53.0/kleinkram/api/file_transfer.py +639 -0
  4. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/api/pagination.py +3 -0
  5. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/api/query.py +8 -0
  6. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/api/routes.py +198 -25
  7. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/auth.py +5 -3
  8. kleinkram-0.53.0/kleinkram/cli/_action.py +140 -0
  9. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_download.py +2 -1
  10. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_endpoint.py +1 -1
  11. kleinkram-0.53.0/kleinkram/cli/_file_validator.py +130 -0
  12. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_mission.py +16 -4
  13. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_project.py +2 -2
  14. kleinkram-0.53.0/kleinkram/cli/_run.py +112 -0
  15. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_upload.py +63 -20
  16. kleinkram-0.53.0/kleinkram/cli/_verify.py +106 -0
  17. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/app.py +33 -4
  18. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/core.py +51 -12
  19. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/errors.py +12 -0
  20. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/models.py +48 -1
  21. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/printing.py +228 -6
  22. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/utils.py +18 -4
  23. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/wrappers.py +46 -1
  24. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/PKG-INFO +4 -2
  25. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/SOURCES.txt +3 -0
  26. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/requires.txt +1 -0
  27. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/requirements.txt +1 -0
  28. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/setup.cfg +2 -2
  29. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_core.py +1 -1
  30. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_printing.py +8 -8
  31. kleinkram-0.41.3.dev20250304135104/kleinkram/api/deser.py +0 -162
  32. kleinkram-0.41.3.dev20250304135104/kleinkram/api/file_transfer.py +0 -455
  33. kleinkram-0.41.3.dev20250304135104/kleinkram/cli/_verify.py +0 -56
  34. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/README.md +0 -0
  35. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/__init__.py +0 -0
  36. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/__main__.py +0 -0
  37. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/_version.py +0 -0
  38. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/api/__init__.py +0 -0
  39. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/api/client.py +0 -0
  40. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/__init__.py +0 -0
  41. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_file.py +0 -0
  42. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/_list.py +0 -0
  43. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/cli/error_handling.py +0 -0
  44. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/config.py +0 -0
  45. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/main.py +0 -0
  46. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/py.typed +0 -0
  47. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram/types.py +0 -0
  48. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/dependency_links.txt +0 -0
  49. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/entry_points.txt +0 -0
  50. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/kleinkram.egg-info/top_level.txt +0 -0
  51. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/pyproject.toml +0 -0
  52. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/setup.py +0 -0
  53. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/testing/__init__.py +0 -0
  54. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/testing/backend_fixtures.py +0 -0
  55. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/__init__.py +0 -0
  56. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/conftest.py +0 -0
  57. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_config.py +0 -0
  58. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_end_to_end.py +0 -0
  59. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_error_handling.py +0 -0
  60. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_fixtures.py +0 -0
  61. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_query.py +0 -0
  62. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_utils.py +0 -0
  63. {kleinkram-0.41.3.dev20250304135104 → kleinkram-0.53.0}/tests/test_wrappers.py +0 -0
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.41.3.dev20250304135104
3
+ Version: 0.53.0
4
4
  Summary: give me your bags
5
5
  Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
6
+ Author-email: pucyril@ethz.ch, dgarmier@ethz.ch, jschwab@ethz.ch
6
7
  Classifier: Programming Language :: Python :: 3
7
8
  Classifier: Programming Language :: Python :: 3 :: Only
8
9
  Classifier: Programming Language :: Python :: 3.8
@@ -21,6 +22,7 @@ Requires-Dist: pyyaml
21
22
  Requires-Dist: rich
22
23
  Requires-Dist: tqdm
23
24
  Requires-Dist: typer
25
+ Requires-Dist: click
24
26
 
25
27
  # Kleinkram: CLI
26
28
 
@@ -0,0 +1,350 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Any
6
+ from typing import Dict
7
+ from typing import List
8
+ from typing import Literal
9
+ from typing import NewType
10
+ from typing import Tuple
11
+ from uuid import UUID
12
+
13
+ import dateutil.parser
14
+
15
+ from kleinkram.errors import ParsingError
16
+ from kleinkram.models import File, Run, LogEntry, ActionTemplate
17
+ from kleinkram.models import FileState
18
+ from kleinkram.models import MetadataValue
19
+ from kleinkram.models import Mission
20
+ from kleinkram.models import Project
21
+
22
+ __all__ = [
23
+ "_parse_project",
24
+ "_parse_mission",
25
+ "_parse_file",
26
+ ]
27
+
28
+
29
+ ProjectObject = NewType("ProjectObject", Dict[str, Any])
30
+ MissionObject = NewType("MissionObject", Dict[str, Any])
31
+ FileObject = NewType("FileObject", Dict[str, Any])
32
+ RunObject = NewType("RunObject", Dict[str, Any])
33
+
34
+ MISSION = "mission"
35
+ PROJECT = "project"
36
+
37
+
38
+ class FileObjectKeys(str, Enum):
39
+ UUID = "uuid"
40
+ FILENAME = "filename"
41
+ DATE = "date" # at some point this will become a metadata
42
+ CREATED_AT = "createdAt"
43
+ UPDATED_AT = "updatedAt"
44
+ STATE = "state"
45
+ SIZE = "size"
46
+ HASH = "hash"
47
+ TYPE = "type"
48
+ CATEGORIES = "categories"
49
+
50
+
51
+ class MissionObjectKeys(str, Enum):
52
+ UUID = "uuid"
53
+ NAME = "name"
54
+ DESCRIPTION = "description"
55
+ CREATED_AT = "createdAt"
56
+ UPDATED_AT = "updatedAt"
57
+ TAGS = "tags"
58
+ FILESIZE = "size"
59
+ FILECOUNT = "filesCount"
60
+
61
+
62
+ class ProjectObjectKeys(str, Enum):
63
+ UUID = "uuid"
64
+ NAME = "name"
65
+ DESCRIPTION = "description"
66
+ CREATED_AT = "createdAt"
67
+ UPDATED_AT = "updatedAt"
68
+ REQUIRED_TAGS = "requiredTags"
69
+
70
+
71
+ class RunObjectKeys(str, Enum):
72
+ UUID = "uuid"
73
+ STATE = "state"
74
+ CREATED_AT = "createdAt"
75
+ MISSION = "mission"
76
+ TEMPLATE = "template"
77
+ UPDATED_AT = "updatedAt"
78
+ LOGS = "logs"
79
+
80
+
81
+ """
82
+ @dataclass(frozen=True)
83
+ class ActionTemplate:
84
+ uuid: UUID
85
+ access_rights: int
86
+ command: str
87
+ cpu_cores: int
88
+ cpu_memory_gb: int
89
+ entrypoint: str
90
+ gpu_memory_gb: int
91
+ image_name: str
92
+ max_runtime_minutes: int
93
+ created_at: datetime
94
+ name: str
95
+ version: str
96
+
97
+ """
98
+
99
+
100
+ class TemplateObjectKeys(str, Enum):
101
+ UUID = "uuid"
102
+ NAME = "name"
103
+ ACCESS_RIGHTS = "accessRights"
104
+ COMMAND = "command"
105
+ CPU_CORES = "cpuCores"
106
+ CPU_MEMORY_GB = "cpuMemory"
107
+ ENTRYPOINT = "entrypoint"
108
+ GPU_MEMORY_GB = "gpuMemory"
109
+ IMAGE_NAME = "imageName"
110
+ MAX_RUNTIME_MINUTES = "maxRuntime"
111
+ CREATED_AT = "createdAt"
112
+ VERSION = "version"
113
+
114
+
115
+ class LogEntryObjectKeys(str, Enum):
116
+ TIMESTAMP = "timestamp"
117
+ LEVEL = "type"
118
+ MESSAGE = "message"
119
+
120
+
121
+ def _get_nested_info(data, key: Literal["mission", "project"]) -> Tuple[UUID, str]:
122
+ nested_data = data[key]
123
+ return (
124
+ UUID(nested_data[ProjectObjectKeys.UUID], version=4),
125
+ nested_data[ProjectObjectKeys.NAME],
126
+ )
127
+
128
+
129
+ def _parse_datetime(date: str) -> datetime:
130
+ try:
131
+ return dateutil.parser.isoparse(date)
132
+ except ValueError as e:
133
+ raise ParsingError(f"error parsing date: {date}") from e
134
+
135
+
136
+ def _parse_file_state(state: str) -> FileState:
137
+ try:
138
+ return FileState(state)
139
+ except ValueError as e:
140
+ raise ParsingError(f"error parsing file state: {state}") from e
141
+
142
+
143
+ def _parse_metadata(tags: List[Dict]) -> Dict[str, MetadataValue]:
144
+ result = {}
145
+ try:
146
+ for tag in tags:
147
+ entry = {
148
+ tag.get("name"): MetadataValue(
149
+ tag.get("valueAsString"), tag.get("datatype")
150
+ )
151
+ }
152
+ result.update(entry)
153
+ return result
154
+ except ValueError as e:
155
+ raise ParsingError(f"error parsing metadata: {e}") from e
156
+
157
+
158
+ def _parse_required_tags(tags: List[Dict]) -> list[str]:
159
+ return list(_parse_metadata(tags).keys())
160
+
161
+
162
+ def _parse_project(project_object: ProjectObject) -> Project:
163
+ try:
164
+ id_ = UUID(project_object[ProjectObjectKeys.UUID], version=4)
165
+ name = project_object[ProjectObjectKeys.NAME]
166
+ description = project_object[ProjectObjectKeys.DESCRIPTION]
167
+ created_at = _parse_datetime(project_object[ProjectObjectKeys.CREATED_AT])
168
+ updated_at = _parse_datetime(project_object[ProjectObjectKeys.UPDATED_AT])
169
+ required_tags = _parse_required_tags(
170
+ project_object[ProjectObjectKeys.REQUIRED_TAGS]
171
+ )
172
+ except Exception as e:
173
+ raise ParsingError(f"error parsing project: {project_object}") from e
174
+ return Project(
175
+ id=id_,
176
+ name=name,
177
+ description=description,
178
+ created_at=created_at,
179
+ updated_at=updated_at,
180
+ required_tags=required_tags,
181
+ )
182
+
183
+
184
+ def _parse_mission(mission: MissionObject) -> Mission:
185
+ try:
186
+ id_ = UUID(mission[MissionObjectKeys.UUID], version=4)
187
+ name = mission[MissionObjectKeys.NAME]
188
+ created_at = _parse_datetime(mission[MissionObjectKeys.CREATED_AT])
189
+ updated_at = _parse_datetime(mission[MissionObjectKeys.UPDATED_AT])
190
+ metadata = _parse_metadata(mission[MissionObjectKeys.TAGS])
191
+ file_count = mission[MissionObjectKeys.FILECOUNT]
192
+ filesize = mission[MissionObjectKeys.FILESIZE]
193
+
194
+ project_id, project_name = _get_nested_info(mission, PROJECT)
195
+
196
+ parsed = Mission(
197
+ id=id_,
198
+ name=name,
199
+ created_at=created_at,
200
+ updated_at=updated_at,
201
+ metadata=metadata,
202
+ project_id=project_id,
203
+ project_name=project_name,
204
+ number_of_files=file_count,
205
+ size=filesize,
206
+ )
207
+ except Exception as e:
208
+ raise ParsingError(f"error parsing mission: {mission}") from e
209
+ return parsed
210
+
211
+
212
+ def _parse_file(file: FileObject) -> File:
213
+ try:
214
+ name = file[FileObjectKeys.FILENAME]
215
+ id_ = UUID(file[FileObjectKeys.UUID], version=4)
216
+ fsize = file[FileObjectKeys.SIZE]
217
+ fhash = file[FileObjectKeys.HASH]
218
+ ftype = file[FileObjectKeys.TYPE].split(".")[-1]
219
+ fdate = file[FileObjectKeys.DATE]
220
+ created_at = _parse_datetime(file[FileObjectKeys.CREATED_AT])
221
+ updated_at = _parse_datetime(file[FileObjectKeys.UPDATED_AT])
222
+ state = _parse_file_state(file[FileObjectKeys.STATE])
223
+ categories = file[FileObjectKeys.CATEGORIES]
224
+
225
+ mission_id, mission_name = _get_nested_info(file, MISSION)
226
+ project_id, project_name = _get_nested_info(file[MISSION], PROJECT)
227
+
228
+ parsed = File(
229
+ id=id_,
230
+ name=name,
231
+ hash=fhash,
232
+ size=fsize,
233
+ type_=ftype,
234
+ date=fdate,
235
+ categories=categories,
236
+ state=state,
237
+ created_at=created_at,
238
+ updated_at=updated_at,
239
+ mission_id=mission_id,
240
+ mission_name=mission_name,
241
+ project_id=project_id,
242
+ project_name=project_name,
243
+ )
244
+ except Exception as e:
245
+ raise ParsingError(f"error parsing file: {file}") from e
246
+ return parsed
247
+
248
+
249
+ """
250
+ @dataclass(frozen=True)
251
+ class ActionTemplate:
252
+ uuid: UUID
253
+ access_rights: int
254
+ command: str
255
+ cpu_cores: int
256
+ cpu_memory_gb: int
257
+ entrypoint: str
258
+ gpu_memory_gb: int
259
+ image_name: str
260
+ max_runtime_minutes: int
261
+ created_at: datetime
262
+ name: str
263
+ version: str
264
+
265
+ """
266
+
267
+
268
+ def _parse_action_template(run_object: RunObject) -> ActionTemplate:
269
+ try:
270
+ uuid_ = UUID(run_object[TemplateObjectKeys.UUID], version=4)
271
+ access_rights = run_object[TemplateObjectKeys.ACCESS_RIGHTS]
272
+ command = run_object[TemplateObjectKeys.COMMAND]
273
+ cpu_cores = run_object[TemplateObjectKeys.CPU_CORES]
274
+ cpu_memory_gb = run_object[TemplateObjectKeys.CPU_MEMORY_GB]
275
+ entrypoint = run_object[TemplateObjectKeys.ENTRYPOINT]
276
+ gpu_memory_gb = run_object[TemplateObjectKeys.GPU_MEMORY_GB]
277
+ image_name = run_object[TemplateObjectKeys.IMAGE_NAME]
278
+ max_runtime_minutes = run_object[TemplateObjectKeys.MAX_RUNTIME_MINUTES]
279
+ created_at = _parse_datetime(run_object[TemplateObjectKeys.CREATED_AT])
280
+ name = run_object[TemplateObjectKeys.NAME]
281
+ version = run_object[TemplateObjectKeys.VERSION]
282
+
283
+ except Exception as e:
284
+ raise ParsingError(f"error parsing action template: {run_object}") from e
285
+
286
+ return ActionTemplate(
287
+ uuid=uuid_,
288
+ access_rights=access_rights,
289
+ command=command,
290
+ cpu_cores=cpu_cores,
291
+ cpu_memory_gb=cpu_memory_gb,
292
+ entrypoint=entrypoint,
293
+ gpu_memory_gb=gpu_memory_gb,
294
+ image_name=image_name,
295
+ max_runtime_minutes=max_runtime_minutes,
296
+ created_at=created_at,
297
+ name=name,
298
+ version=version,
299
+ )
300
+
301
+
302
+ def _parse_run(run_object: RunObject) -> Run:
303
+ try:
304
+ uuid_ = UUID(run_object[RunObjectKeys.UUID], version=4)
305
+ state = run_object[RunObjectKeys.STATE]
306
+ created_at = _parse_datetime(run_object[RunObjectKeys.CREATED_AT])
307
+ updated_at = (
308
+ _parse_datetime(run_object[RunObjectKeys.UPDATED_AT])
309
+ if run_object.get(RunObjectKeys.UPDATED_AT)
310
+ else None
311
+ )
312
+
313
+ mission_dict = run_object[RunObjectKeys.MISSION]
314
+ mission_id = UUID(mission_dict[MissionObjectKeys.UUID], version=4)
315
+ mission_name = mission_dict[MissionObjectKeys.NAME]
316
+
317
+ project_dict = mission_dict[PROJECT]
318
+ project_name = project_dict[ProjectObjectKeys.NAME]
319
+
320
+ template_dict = run_object[RunObjectKeys.TEMPLATE]
321
+ template_id = UUID(template_dict[TemplateObjectKeys.UUID], version=4)
322
+ template_name = template_dict[TemplateObjectKeys.NAME]
323
+ logs = []
324
+ for log_entry in run_object.get(RunObjectKeys.LOGS, []):
325
+ log_timestamp = _parse_datetime(log_entry[LogEntryObjectKeys.TIMESTAMP])
326
+ log_level = log_entry[LogEntryObjectKeys.LEVEL]
327
+ log_message = log_entry[LogEntryObjectKeys.MESSAGE]
328
+ logs.append(
329
+ LogEntry(
330
+ timestamp=log_timestamp,
331
+ level=log_level,
332
+ message=log_message,
333
+ )
334
+ )
335
+
336
+ except Exception as e:
337
+ raise ParsingError(f"error parsing run: {run_object}") from e
338
+
339
+ return Run(
340
+ uuid=uuid_,
341
+ state=state,
342
+ created_at=created_at,
343
+ updated_at=updated_at,
344
+ mission_id=mission_id,
345
+ mission_name=mission_name,
346
+ project_name=project_name,
347
+ template_id=template_id,
348
+ template_name=template_name,
349
+ logs=logs,
350
+ )