kleinkram 0.43.2.dev20250331124109__py3-none-any.whl → 0.58.0.dev20260110152317__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.
- kleinkram/api/client.py +6 -18
- kleinkram/api/deser.py +152 -1
- kleinkram/api/file_transfer.py +202 -101
- kleinkram/api/pagination.py +11 -2
- kleinkram/api/query.py +10 -10
- kleinkram/api/routes.py +192 -59
- kleinkram/auth.py +108 -7
- kleinkram/cli/_action.py +131 -0
- kleinkram/cli/_download.py +8 -19
- kleinkram/cli/_endpoint.py +2 -4
- kleinkram/cli/_file.py +6 -18
- kleinkram/cli/_file_validator.py +125 -0
- kleinkram/cli/_list.py +5 -15
- kleinkram/cli/_mission.py +24 -28
- kleinkram/cli/_project.py +10 -26
- kleinkram/cli/_run.py +220 -0
- kleinkram/cli/_upload.py +58 -26
- kleinkram/cli/_verify.py +59 -16
- kleinkram/cli/app.py +56 -17
- kleinkram/cli/error_handling.py +1 -3
- kleinkram/config.py +6 -21
- kleinkram/core.py +53 -43
- kleinkram/errors.py +12 -0
- kleinkram/models.py +51 -1
- kleinkram/printing.py +229 -18
- kleinkram/utils.py +10 -24
- kleinkram/wrappers.py +54 -30
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/METADATA +6 -4
- kleinkram-0.58.0.dev20260110152317.dist-info/RECORD +53 -0
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/WHEEL +1 -1
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/top_level.txt +0 -1
- {testing → tests}/backend_fixtures.py +27 -3
- tests/conftest.py +1 -1
- tests/generate_test_data.py +314 -0
- tests/test_config.py +2 -6
- tests/test_core.py +11 -31
- tests/test_end_to_end.py +3 -5
- tests/test_fixtures.py +3 -5
- tests/test_printing.py +9 -11
- tests/test_utils.py +1 -3
- tests/test_wrappers.py +9 -27
- kleinkram-0.43.2.dev20250331124109.dist-info/RECORD +0 -50
- testing/__init__.py +0 -0
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/entry_points.txt +0 -0
kleinkram/api/client.py
CHANGED
|
@@ -39,15 +39,11 @@ ListData = Sequence[Data]
|
|
|
39
39
|
QueryParams = Mapping[str, Union[Data, NestedData, ListData]]
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _convert_nested_data_query_params_values(
|
|
43
|
-
key: str, values: NestedData
|
|
44
|
-
) -> List[Tuple[str, Data]]:
|
|
42
|
+
def _convert_nested_data_query_params_values(key: str, values: NestedData) -> List[Tuple[str, Data]]:
|
|
45
43
|
return [(f"{key}[{k}]", v) for k, v in values.items()]
|
|
46
44
|
|
|
47
45
|
|
|
48
|
-
def _convert_list_data_query_params_values(
|
|
49
|
-
key: str, values: ListData
|
|
50
|
-
) -> List[Tuple[str, Data]]:
|
|
46
|
+
def _convert_list_data_query_params_values(key: str, values: ListData) -> List[Tuple[str, Data]]:
|
|
51
47
|
return [(key, value) for value in values]
|
|
52
48
|
|
|
53
49
|
|
|
@@ -71,9 +67,7 @@ class AuthenticatedClient(httpx.Client):
|
|
|
71
67
|
_config: Config
|
|
72
68
|
_config_lock: Lock
|
|
73
69
|
|
|
74
|
-
def __init__(
|
|
75
|
-
self, config_path: Path = CONFIG_PATH, *args: Any, **kwargs: Any
|
|
76
|
-
) -> None:
|
|
70
|
+
def __init__(self, config_path: Path = CONFIG_PATH, *args: Any, **kwargs: Any) -> None:
|
|
77
71
|
super().__init__(*args, **kwargs)
|
|
78
72
|
|
|
79
73
|
self._config = get_config(path=config_path)
|
|
@@ -116,9 +110,7 @@ class AuthenticatedClient(httpx.Client):
|
|
|
116
110
|
|
|
117
111
|
self.cookies.set(COOKIE_AUTH_TOKEN, new_access_token)
|
|
118
112
|
|
|
119
|
-
def _send_request_with_kleinkram_headers(
|
|
120
|
-
self, *args: Any, **kwargs: Any
|
|
121
|
-
) -> httpx.Response:
|
|
113
|
+
def _send_request_with_kleinkram_headers(self, *args: Any, **kwargs: Any) -> httpx.Response:
|
|
122
114
|
# add the cli version to the headers
|
|
123
115
|
headers = kwargs.get("headers") or {}
|
|
124
116
|
headers.setdefault(CLI_VERSION_HEADER, __version__)
|
|
@@ -150,9 +142,7 @@ class AuthenticatedClient(httpx.Client):
|
|
|
150
142
|
logger.info(f"requesting {method} {full_url}")
|
|
151
143
|
|
|
152
144
|
httpx_params = _convert_query_params_to_httpx_format(params or {})
|
|
153
|
-
response = self._send_request_with_kleinkram_headers(
|
|
154
|
-
method, full_url, params=httpx_params, *args, **kwargs
|
|
155
|
-
)
|
|
145
|
+
response = self._send_request_with_kleinkram_headers(method, full_url, params=httpx_params, *args, **kwargs)
|
|
156
146
|
|
|
157
147
|
logger.info(f"got response {response}")
|
|
158
148
|
|
|
@@ -170,9 +160,7 @@ class AuthenticatedClient(httpx.Client):
|
|
|
170
160
|
raise NotAuthenticated
|
|
171
161
|
|
|
172
162
|
logger.info(f"retrying request {method} {full_url}")
|
|
173
|
-
response = self._send_request_with_kleinkram_headers(
|
|
174
|
-
method, full_url, params=httpx_params, *args, **kwargs
|
|
175
|
-
)
|
|
163
|
+
response = self._send_request_with_kleinkram_headers(method, full_url, params=httpx_params, *args, **kwargs)
|
|
176
164
|
logger.info(f"got response {response}")
|
|
177
165
|
return response
|
|
178
166
|
else:
|
kleinkram/api/deser.py
CHANGED
|
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Any
|
|
6
6
|
from typing import Dict
|
|
7
|
+
from typing import List
|
|
7
8
|
from typing import Literal
|
|
8
9
|
from typing import NewType
|
|
9
10
|
from typing import Tuple
|
|
@@ -12,10 +13,14 @@ from uuid import UUID
|
|
|
12
13
|
import dateutil.parser
|
|
13
14
|
|
|
14
15
|
from kleinkram.errors import ParsingError
|
|
16
|
+
from kleinkram.models import ActionTemplate
|
|
15
17
|
from kleinkram.models import File
|
|
16
18
|
from kleinkram.models import FileState
|
|
19
|
+
from kleinkram.models import LogEntry
|
|
20
|
+
from kleinkram.models import MetadataValue
|
|
17
21
|
from kleinkram.models import Mission
|
|
18
22
|
from kleinkram.models import Project
|
|
23
|
+
from kleinkram.models import Run
|
|
19
24
|
|
|
20
25
|
__all__ = [
|
|
21
26
|
"_parse_project",
|
|
@@ -27,6 +32,7 @@ __all__ = [
|
|
|
27
32
|
ProjectObject = NewType("ProjectObject", Dict[str, Any])
|
|
28
33
|
MissionObject = NewType("MissionObject", Dict[str, Any])
|
|
29
34
|
FileObject = NewType("FileObject", Dict[str, Any])
|
|
35
|
+
RunObject = NewType("RunObject", Dict[str, Any])
|
|
30
36
|
|
|
31
37
|
MISSION = "mission"
|
|
32
38
|
PROJECT = "project"
|
|
@@ -51,6 +57,9 @@ class MissionObjectKeys(str, Enum):
|
|
|
51
57
|
DESCRIPTION = "description"
|
|
52
58
|
CREATED_AT = "createdAt"
|
|
53
59
|
UPDATED_AT = "updatedAt"
|
|
60
|
+
TAGS = "tags"
|
|
61
|
+
FILESIZE = "size"
|
|
62
|
+
FILECOUNT = "filesCount"
|
|
54
63
|
|
|
55
64
|
|
|
56
65
|
class ProjectObjectKeys(str, Enum):
|
|
@@ -59,6 +68,40 @@ class ProjectObjectKeys(str, Enum):
|
|
|
59
68
|
DESCRIPTION = "description"
|
|
60
69
|
CREATED_AT = "createdAt"
|
|
61
70
|
UPDATED_AT = "updatedAt"
|
|
71
|
+
REQUIRED_TAGS = "requiredTags"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RunObjectKeys(str, Enum):
|
|
75
|
+
UUID = "uuid"
|
|
76
|
+
STATE = "state"
|
|
77
|
+
STATE_CAUSE = "stateCause"
|
|
78
|
+
CREATED_AT = "createdAt"
|
|
79
|
+
MISSION = "mission"
|
|
80
|
+
TEMPLATE = "template"
|
|
81
|
+
UPDATED_AT = "updatedAt"
|
|
82
|
+
LOGS = "logs"
|
|
83
|
+
ARTIFACT_URL = "artifactUrl"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TemplateObjectKeys(str, Enum):
|
|
87
|
+
UUID = "uuid"
|
|
88
|
+
NAME = "name"
|
|
89
|
+
ACCESS_RIGHTS = "accessRights"
|
|
90
|
+
COMMAND = "command"
|
|
91
|
+
CPU_CORES = "cpuCores"
|
|
92
|
+
CPU_MEMORY_GB = "cpuMemory"
|
|
93
|
+
ENTRYPOINT = "entrypoint"
|
|
94
|
+
GPU_MEMORY_GB = "gpuMemory"
|
|
95
|
+
IMAGE_NAME = "imageName"
|
|
96
|
+
MAX_RUNTIME_MINUTES = "maxRuntime"
|
|
97
|
+
CREATED_AT = "createdAt"
|
|
98
|
+
VERSION = "version"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LogEntryObjectKeys(str, Enum):
|
|
102
|
+
TIMESTAMP = "timestamp"
|
|
103
|
+
LEVEL = "type"
|
|
104
|
+
MESSAGE = "message"
|
|
62
105
|
|
|
63
106
|
|
|
64
107
|
def _get_nested_info(data, key: Literal["mission", "project"]) -> Tuple[UUID, str]:
|
|
@@ -83,6 +126,21 @@ def _parse_file_state(state: str) -> FileState:
|
|
|
83
126
|
raise ParsingError(f"error parsing file state: {state}") from e
|
|
84
127
|
|
|
85
128
|
|
|
129
|
+
def _parse_metadata(tags: List[Dict]) -> Dict[str, MetadataValue]:
|
|
130
|
+
result = {}
|
|
131
|
+
try:
|
|
132
|
+
for tag in tags:
|
|
133
|
+
entry = {tag.get("name"): MetadataValue(tag.get("valueAsString"), tag.get("datatype"))}
|
|
134
|
+
result.update(entry)
|
|
135
|
+
return result
|
|
136
|
+
except ValueError as e:
|
|
137
|
+
raise ParsingError(f"error parsing metadata: {e}") from e
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _parse_required_tags(tags: List[Dict]) -> list[str]:
|
|
141
|
+
return list(_parse_metadata(tags).keys())
|
|
142
|
+
|
|
143
|
+
|
|
86
144
|
def _parse_project(project_object: ProjectObject) -> Project:
|
|
87
145
|
try:
|
|
88
146
|
id_ = UUID(project_object[ProjectObjectKeys.UUID], version=4)
|
|
@@ -90,6 +148,7 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
90
148
|
description = project_object[ProjectObjectKeys.DESCRIPTION]
|
|
91
149
|
created_at = _parse_datetime(project_object[ProjectObjectKeys.CREATED_AT])
|
|
92
150
|
updated_at = _parse_datetime(project_object[ProjectObjectKeys.UPDATED_AT])
|
|
151
|
+
required_tags = _parse_required_tags(project_object[ProjectObjectKeys.REQUIRED_TAGS])
|
|
93
152
|
except Exception as e:
|
|
94
153
|
raise ParsingError(f"error parsing project: {project_object}") from e
|
|
95
154
|
return Project(
|
|
@@ -98,6 +157,7 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
98
157
|
description=description,
|
|
99
158
|
created_at=created_at,
|
|
100
159
|
updated_at=updated_at,
|
|
160
|
+
required_tags=required_tags,
|
|
101
161
|
)
|
|
102
162
|
|
|
103
163
|
|
|
@@ -107,7 +167,9 @@ def _parse_mission(mission: MissionObject) -> Mission:
|
|
|
107
167
|
name = mission[MissionObjectKeys.NAME]
|
|
108
168
|
created_at = _parse_datetime(mission[MissionObjectKeys.CREATED_AT])
|
|
109
169
|
updated_at = _parse_datetime(mission[MissionObjectKeys.UPDATED_AT])
|
|
110
|
-
metadata =
|
|
170
|
+
metadata = _parse_metadata(mission[MissionObjectKeys.TAGS])
|
|
171
|
+
file_count = mission[MissionObjectKeys.FILECOUNT]
|
|
172
|
+
filesize = mission[MissionObjectKeys.FILESIZE]
|
|
111
173
|
|
|
112
174
|
project_id, project_name = _get_nested_info(mission, PROJECT)
|
|
113
175
|
|
|
@@ -119,6 +181,8 @@ def _parse_mission(mission: MissionObject) -> Mission:
|
|
|
119
181
|
metadata=metadata,
|
|
120
182
|
project_id=project_id,
|
|
121
183
|
project_name=project_name,
|
|
184
|
+
number_of_files=file_count,
|
|
185
|
+
size=filesize,
|
|
122
186
|
)
|
|
123
187
|
except Exception as e:
|
|
124
188
|
raise ParsingError(f"error parsing mission: {mission}") from e
|
|
@@ -160,3 +224,90 @@ def _parse_file(file: FileObject) -> File:
|
|
|
160
224
|
except Exception as e:
|
|
161
225
|
raise ParsingError(f"error parsing file: {file}") from e
|
|
162
226
|
return parsed
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _parse_action_template(run_object: RunObject) -> ActionTemplate:
|
|
230
|
+
try:
|
|
231
|
+
uuid_ = UUID(run_object[TemplateObjectKeys.UUID], version=4)
|
|
232
|
+
access_rights = run_object[TemplateObjectKeys.ACCESS_RIGHTS]
|
|
233
|
+
command = run_object[TemplateObjectKeys.COMMAND]
|
|
234
|
+
cpu_cores = run_object[TemplateObjectKeys.CPU_CORES]
|
|
235
|
+
cpu_memory_gb = run_object[TemplateObjectKeys.CPU_MEMORY_GB]
|
|
236
|
+
entrypoint = run_object[TemplateObjectKeys.ENTRYPOINT]
|
|
237
|
+
gpu_memory_gb = run_object[TemplateObjectKeys.GPU_MEMORY_GB]
|
|
238
|
+
image_name = run_object[TemplateObjectKeys.IMAGE_NAME]
|
|
239
|
+
max_runtime_minutes = run_object[TemplateObjectKeys.MAX_RUNTIME_MINUTES]
|
|
240
|
+
created_at = _parse_datetime(run_object[TemplateObjectKeys.CREATED_AT])
|
|
241
|
+
name = run_object[TemplateObjectKeys.NAME]
|
|
242
|
+
version = run_object[TemplateObjectKeys.VERSION]
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
raise ParsingError(f"error parsing action template: {run_object}") from e
|
|
246
|
+
|
|
247
|
+
return ActionTemplate(
|
|
248
|
+
uuid=uuid_,
|
|
249
|
+
access_rights=access_rights,
|
|
250
|
+
command=command,
|
|
251
|
+
cpu_cores=cpu_cores,
|
|
252
|
+
cpu_memory_gb=cpu_memory_gb,
|
|
253
|
+
entrypoint=entrypoint,
|
|
254
|
+
gpu_memory_gb=gpu_memory_gb,
|
|
255
|
+
image_name=image_name,
|
|
256
|
+
max_runtime_minutes=max_runtime_minutes,
|
|
257
|
+
created_at=created_at,
|
|
258
|
+
name=name,
|
|
259
|
+
version=version,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _parse_run(run_object: RunObject) -> Run:
|
|
264
|
+
try:
|
|
265
|
+
uuid_ = UUID(run_object[RunObjectKeys.UUID], version=4)
|
|
266
|
+
state = run_object[RunObjectKeys.STATE]
|
|
267
|
+
state_cause = run_object[RunObjectKeys.STATE_CAUSE]
|
|
268
|
+
artifact_url = run_object.get(RunObjectKeys.ARTIFACT_URL)
|
|
269
|
+
created_at = _parse_datetime(run_object[RunObjectKeys.CREATED_AT])
|
|
270
|
+
updated_at = (
|
|
271
|
+
_parse_datetime(run_object[RunObjectKeys.UPDATED_AT]) if run_object.get(RunObjectKeys.UPDATED_AT) else None
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
mission_dict = run_object[RunObjectKeys.MISSION]
|
|
275
|
+
mission_id = UUID(mission_dict[MissionObjectKeys.UUID], version=4)
|
|
276
|
+
mission_name = mission_dict[MissionObjectKeys.NAME]
|
|
277
|
+
|
|
278
|
+
project_dict = mission_dict[PROJECT]
|
|
279
|
+
project_name = project_dict[ProjectObjectKeys.NAME]
|
|
280
|
+
|
|
281
|
+
template_dict = run_object[RunObjectKeys.TEMPLATE]
|
|
282
|
+
template_id = UUID(template_dict[TemplateObjectKeys.UUID], version=4)
|
|
283
|
+
template_name = template_dict[TemplateObjectKeys.NAME]
|
|
284
|
+
logs = []
|
|
285
|
+
for log_entry in run_object.get(RunObjectKeys.LOGS, []):
|
|
286
|
+
log_timestamp = _parse_datetime(log_entry[LogEntryObjectKeys.TIMESTAMP])
|
|
287
|
+
log_level = log_entry[LogEntryObjectKeys.LEVEL]
|
|
288
|
+
log_message = log_entry[LogEntryObjectKeys.MESSAGE]
|
|
289
|
+
logs.append(
|
|
290
|
+
LogEntry(
|
|
291
|
+
timestamp=log_timestamp,
|
|
292
|
+
level=log_level,
|
|
293
|
+
message=log_message,
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise ParsingError(f"error parsing run: {run_object}") from e
|
|
299
|
+
|
|
300
|
+
return Run(
|
|
301
|
+
uuid=uuid_,
|
|
302
|
+
state=state,
|
|
303
|
+
state_cause=state_cause,
|
|
304
|
+
artifact_url=artifact_url,
|
|
305
|
+
created_at=created_at,
|
|
306
|
+
updated_at=updated_at,
|
|
307
|
+
mission_id=mission_id,
|
|
308
|
+
mission_name=mission_name,
|
|
309
|
+
project_name=project_name,
|
|
310
|
+
template_id=template_id,
|
|
311
|
+
template_name=template_name,
|
|
312
|
+
logs=logs,
|
|
313
|
+
)
|