kleinkram 0.49.0.dev20250728101614__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 (62) hide show
  1. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/PKG-INFO +1 -1
  2. kleinkram-0.53.0/kleinkram/api/deser.py +350 -0
  3. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/file_transfer.py +14 -13
  4. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/pagination.py +3 -0
  5. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/query.py +8 -0
  6. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/routes.py +198 -25
  7. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/auth.py +5 -3
  8. kleinkram-0.53.0/kleinkram/cli/_action.py +140 -0
  9. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_endpoint.py +1 -1
  10. kleinkram-0.53.0/kleinkram/cli/_file_validator.py +130 -0
  11. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_mission.py +16 -4
  12. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_project.py +2 -2
  13. kleinkram-0.53.0/kleinkram/cli/_run.py +112 -0
  14. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_upload.py +63 -20
  15. kleinkram-0.53.0/kleinkram/cli/_verify.py +106 -0
  16. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/app.py +33 -4
  17. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/core.py +17 -5
  18. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/errors.py +12 -0
  19. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/models.py +46 -0
  20. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/printing.py +223 -2
  21. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/utils.py +2 -4
  22. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/PKG-INFO +1 -1
  23. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/SOURCES.txt +3 -0
  24. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/setup.cfg +1 -1
  25. kleinkram-0.49.0.dev20250728101614/kleinkram/api/deser.py +0 -162
  26. kleinkram-0.49.0.dev20250728101614/kleinkram/cli/_verify.py +0 -66
  27. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/README.md +0 -0
  28. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/__init__.py +0 -0
  29. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/__main__.py +0 -0
  30. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/_version.py +0 -0
  31. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/__init__.py +0 -0
  32. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/api/client.py +0 -0
  33. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/__init__.py +0 -0
  34. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_download.py +0 -0
  35. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_file.py +0 -0
  36. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/_list.py +0 -0
  37. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/cli/error_handling.py +0 -0
  38. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/config.py +0 -0
  39. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/main.py +0 -0
  40. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/py.typed +0 -0
  41. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/types.py +0 -0
  42. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram/wrappers.py +0 -0
  43. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/dependency_links.txt +0 -0
  44. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/entry_points.txt +0 -0
  45. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/requires.txt +0 -0
  46. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/kleinkram.egg-info/top_level.txt +0 -0
  47. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/pyproject.toml +0 -0
  48. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/requirements.txt +0 -0
  49. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/setup.py +0 -0
  50. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/testing/__init__.py +0 -0
  51. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/testing/backend_fixtures.py +0 -0
  52. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/__init__.py +0 -0
  53. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/conftest.py +0 -0
  54. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_config.py +0 -0
  55. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_core.py +0 -0
  56. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_end_to_end.py +0 -0
  57. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_error_handling.py +0 -0
  58. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_fixtures.py +0 -0
  59. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_printing.py +0 -0
  60. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_query.py +0 -0
  61. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_utils.py +0 -0
  62. {kleinkram-0.49.0.dev20250728101614 → kleinkram-0.53.0}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kleinkram
3
- Version: 0.49.0.dev20250728101614
3
+ Version: 0.53.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
@@ -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
+ )
@@ -572,19 +572,20 @@ def upload_files(
572
572
 
573
573
  avg_speed_bps = total_uploaded_bytes / elapsed_time if elapsed_time > 0 else 0
574
574
 
575
- console.print(f"Upload took {elapsed_time:.2f} seconds")
576
- console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}")
577
- console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}")
578
-
579
- if failed_files > 0:
580
- console.print(
581
- f"\nUploaded {len(files) - failed_files - skipped_files} files, {skipped_files} skipped, {failed_files} uploads failed",
582
- style="red",
583
- )
584
- else:
585
- console.print(
586
- f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped"
587
- )
575
+ if verbose:
576
+ console.print(f"Upload took {elapsed_time:.2f} seconds")
577
+ console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}")
578
+ console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}")
579
+
580
+ if failed_files > 0:
581
+ console.print(
582
+ f"\nUploaded {len(files) - failed_files - skipped_files} files, {skipped_files} skipped, {failed_files} uploads failed",
583
+ style="red",
584
+ )
585
+ else:
586
+ console.print(
587
+ f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped"
588
+ )
588
589
 
589
590
 
590
591
  def download_files(
@@ -17,6 +17,7 @@ DataPage = Dict[str, Any]
17
17
  PAGE_SIZE = 128
18
18
  SKIP = "skip"
19
19
  TAKE = "take"
20
+ EXACT_MATCH = "exactMatch"
20
21
 
21
22
 
22
23
  def paginated_request(
@@ -25,6 +26,7 @@ def paginated_request(
25
26
  params: Optional[Mapping[str, Any]] = None,
26
27
  max_entries: Optional[int] = None,
27
28
  page_size: int = PAGE_SIZE,
29
+ exact_match: bool = False,
28
30
  ) -> Generator[DataPage, None, None]:
29
31
  total_entries_count = 0
30
32
 
@@ -32,6 +34,7 @@ def paginated_request(
32
34
 
33
35
  params[TAKE] = page_size
34
36
  params[SKIP] = 0
37
+ params[EXACT_MATCH] = str(exact_match).lower() # pass string rather than bool
35
38
 
36
39
  while True:
37
40
  resp = client.get(endpoint, params=params)
@@ -41,6 +41,14 @@ class FileQuery:
41
41
  mission_query: MissionQuery = field(default_factory=MissionQuery)
42
42
 
43
43
 
44
+ @dataclass
45
+ class RunQuery:
46
+ mission_ids: List[UUID] = field(default_factory=list)
47
+ mission_patterns: List[str] = field(default_factory=list)
48
+ project_ids: List[UUID] = field(default_factory=list)
49
+ project_patterns: List[str] = field(default_factory=list)
50
+
51
+
44
52
  def check_mission_query_is_creatable(query: MissionQuery) -> str:
45
53
  """\
46
54
  check if a query is unique and can be used to create a mission