kleinkram 0.51.0.dev20251002115350__tar.gz → 0.51.0.dev20251003125418__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.
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/PKG-INFO +1 -1
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/deser.py +28 -1
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/file_transfer.py +14 -13
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/routes.py +100 -14
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_mission.py +16 -4
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/core.py +4 -3
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/errors.py +3 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/models.py +1 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/printing.py +2 -1
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/PKG-INFO +1 -1
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/setup.cfg +1 -1
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/README.md +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/__init__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/__main__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/_version.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/__init__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/client.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/pagination.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/query.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/auth.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/__init__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_download.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_endpoint.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_file.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_list.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_project.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_upload.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_verify.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/app.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/error_handling.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/config.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/main.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/py.typed +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/types.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/utils.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/wrappers.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/SOURCES.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/dependency_links.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/entry_points.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/requires.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram.egg-info/top_level.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/pyproject.toml +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/requirements.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/setup.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/testing/__init__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/testing/backend_fixtures.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/__init__.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/conftest.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_config.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_core.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_end_to_end.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_error_handling.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_fixtures.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_printing.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_query.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_utils.py +0 -0
- {kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.51.0.
|
|
3
|
+
Version: 0.51.0.dev20251003125418
|
|
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
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/deser.py
RENAMED
|
@@ -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
|
|
@@ -14,6 +15,7 @@ import dateutil.parser
|
|
|
14
15
|
from kleinkram.errors import ParsingError
|
|
15
16
|
from kleinkram.models import File
|
|
16
17
|
from kleinkram.models import FileState
|
|
18
|
+
from kleinkram.models import MetadataValue
|
|
17
19
|
from kleinkram.models import Mission
|
|
18
20
|
from kleinkram.models import Project
|
|
19
21
|
|
|
@@ -51,6 +53,7 @@ class MissionObjectKeys(str, Enum):
|
|
|
51
53
|
DESCRIPTION = "description"
|
|
52
54
|
CREATED_AT = "createdAt"
|
|
53
55
|
UPDATED_AT = "updatedAt"
|
|
56
|
+
TAGS = "tags"
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
class ProjectObjectKeys(str, Enum):
|
|
@@ -59,6 +62,7 @@ class ProjectObjectKeys(str, Enum):
|
|
|
59
62
|
DESCRIPTION = "description"
|
|
60
63
|
CREATED_AT = "createdAt"
|
|
61
64
|
UPDATED_AT = "updatedAt"
|
|
65
|
+
REQUIRED_TAGS = "requiredTags"
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
def _get_nested_info(data, key: Literal["mission", "project"]) -> Tuple[UUID, str]:
|
|
@@ -83,6 +87,25 @@ def _parse_file_state(state: str) -> FileState:
|
|
|
83
87
|
raise ParsingError(f"error parsing file state: {state}") from e
|
|
84
88
|
|
|
85
89
|
|
|
90
|
+
def _parse_metadata(tags: List[Dict]) -> Dict[str, MetadataValue]:
|
|
91
|
+
result = {}
|
|
92
|
+
try:
|
|
93
|
+
for tag in tags:
|
|
94
|
+
entry = {
|
|
95
|
+
tag.get("name"): MetadataValue(
|
|
96
|
+
tag.get("valueAsString"), tag.get("datatype")
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
result.update(entry)
|
|
100
|
+
return result
|
|
101
|
+
except ValueError as e:
|
|
102
|
+
raise ParsingError(f"error parsing metadata: {e}") from e
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _parse_required_tags(tags: List[Dict]) -> list[str]:
|
|
106
|
+
return list(_parse_metadata(tags).keys())
|
|
107
|
+
|
|
108
|
+
|
|
86
109
|
def _parse_project(project_object: ProjectObject) -> Project:
|
|
87
110
|
try:
|
|
88
111
|
id_ = UUID(project_object[ProjectObjectKeys.UUID], version=4)
|
|
@@ -90,6 +113,9 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
90
113
|
description = project_object[ProjectObjectKeys.DESCRIPTION]
|
|
91
114
|
created_at = _parse_datetime(project_object[ProjectObjectKeys.CREATED_AT])
|
|
92
115
|
updated_at = _parse_datetime(project_object[ProjectObjectKeys.UPDATED_AT])
|
|
116
|
+
required_tags = _parse_required_tags(
|
|
117
|
+
project_object[ProjectObjectKeys.REQUIRED_TAGS]
|
|
118
|
+
)
|
|
93
119
|
except Exception as e:
|
|
94
120
|
raise ParsingError(f"error parsing project: {project_object}") from e
|
|
95
121
|
return Project(
|
|
@@ -98,6 +124,7 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
98
124
|
description=description,
|
|
99
125
|
created_at=created_at,
|
|
100
126
|
updated_at=updated_at,
|
|
127
|
+
required_tags=required_tags,
|
|
101
128
|
)
|
|
102
129
|
|
|
103
130
|
|
|
@@ -107,7 +134,7 @@ def _parse_mission(mission: MissionObject) -> Mission:
|
|
|
107
134
|
name = mission[MissionObjectKeys.NAME]
|
|
108
135
|
created_at = _parse_datetime(mission[MissionObjectKeys.CREATED_AT])
|
|
109
136
|
updated_at = _parse_datetime(mission[MissionObjectKeys.UPDATED_AT])
|
|
110
|
-
metadata =
|
|
137
|
+
metadata = _parse_metadata(mission[MissionObjectKeys.TAGS])
|
|
111
138
|
|
|
112
139
|
project_id, project_name = _get_nested_info(mission, PROJECT)
|
|
113
140
|
|
|
@@ -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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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(
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/routes.py
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import tempfile
|
|
4
5
|
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import Any
|
|
6
8
|
from typing import Dict
|
|
7
9
|
from typing import Generator
|
|
@@ -38,12 +40,14 @@ from kleinkram.errors import InvalidMissionQuery
|
|
|
38
40
|
from kleinkram.errors import InvalidProjectQuery
|
|
39
41
|
from kleinkram.errors import MissionExists
|
|
40
42
|
from kleinkram.errors import MissionNotFound
|
|
43
|
+
from kleinkram.errors import MissionValidationError
|
|
41
44
|
from kleinkram.errors import ProjectExists
|
|
42
45
|
from kleinkram.errors import ProjectNotFound
|
|
43
46
|
from kleinkram.models import File
|
|
44
47
|
from kleinkram.models import Mission
|
|
45
48
|
from kleinkram.models import Project
|
|
46
49
|
from kleinkram.utils import is_valid_uuid4
|
|
50
|
+
from kleinkram.utils import split_args
|
|
47
51
|
|
|
48
52
|
__all__ = [
|
|
49
53
|
"_get_api_version",
|
|
@@ -220,6 +224,28 @@ def _mission_name_is_available(
|
|
|
220
224
|
return False
|
|
221
225
|
|
|
222
226
|
|
|
227
|
+
def _validate_mission_name(
|
|
228
|
+
client: AuthenticatedClient, project_id: UUID, mission_name: str
|
|
229
|
+
) -> None:
|
|
230
|
+
if not _mission_name_is_available(client, mission_name, project_id):
|
|
231
|
+
raise MissionExists(
|
|
232
|
+
f"Mission with name: `{mission_name}` already exists"
|
|
233
|
+
f" in project: {project_id}"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if is_valid_uuid4(mission_name):
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f"Mission name: `{mission_name}` is a valid UUIDv4, "
|
|
239
|
+
"mission names must not be valid UUIDv4's"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if mission_name.endswith(" "):
|
|
243
|
+
raise ValueError(
|
|
244
|
+
"A mission name cannot end with a whitespace. "
|
|
245
|
+
f"The given mission name was '{mission_name}'"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
223
249
|
def _project_name_is_available(client: AuthenticatedClient, project_name: str) -> bool:
|
|
224
250
|
project_query = ProjectQuery(patterns=[project_name])
|
|
225
251
|
try:
|
|
@@ -229,6 +255,50 @@ def _project_name_is_available(client: AuthenticatedClient, project_name: str) -
|
|
|
229
255
|
return False
|
|
230
256
|
|
|
231
257
|
|
|
258
|
+
def _validate_mission_created(
|
|
259
|
+
client: AuthenticatedClient, project_id: str, mission_name: str
|
|
260
|
+
) -> None:
|
|
261
|
+
"""
|
|
262
|
+
validate that a mission is successfully created
|
|
263
|
+
"""
|
|
264
|
+
mission_ids, mission_patterns = split_args([mission_name])
|
|
265
|
+
project_ids, project_patterns = split_args([project_id])
|
|
266
|
+
|
|
267
|
+
project_query = ProjectQuery(ids=project_ids, patterns=project_patterns)
|
|
268
|
+
mission_query = MissionQuery(
|
|
269
|
+
ids=mission_ids,
|
|
270
|
+
patterns=mission_patterns,
|
|
271
|
+
project_query=project_query,
|
|
272
|
+
)
|
|
273
|
+
try:
|
|
274
|
+
with tempfile.NamedTemporaryFile(suffix=".mcap", delete=False) as tmp:
|
|
275
|
+
tmp.write(b"dummy content")
|
|
276
|
+
tmp_path = Path(tmp.name)
|
|
277
|
+
|
|
278
|
+
kleinkram.core.upload(
|
|
279
|
+
client=client,
|
|
280
|
+
query=mission_query,
|
|
281
|
+
file_paths=[tmp_path],
|
|
282
|
+
verbose=False,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
file_query = FileQuery(
|
|
286
|
+
ids=[],
|
|
287
|
+
patterns=[tmp_path.name],
|
|
288
|
+
mission_query=mission_query,
|
|
289
|
+
)
|
|
290
|
+
file_parsed = get_file(client, file_query)
|
|
291
|
+
|
|
292
|
+
kleinkram.core.delete_files(client=client, file_ids=[file_parsed.id])
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
raise MissionValidationError(f"Mission validation failed: {e}")
|
|
296
|
+
|
|
297
|
+
finally:
|
|
298
|
+
if tmp_path.exists():
|
|
299
|
+
tmp_path.unlink()
|
|
300
|
+
|
|
301
|
+
|
|
232
302
|
def _create_mission(
|
|
233
303
|
client: AuthenticatedClient,
|
|
234
304
|
project_id: UUID,
|
|
@@ -236,6 +306,7 @@ def _create_mission(
|
|
|
236
306
|
*,
|
|
237
307
|
metadata: Optional[Dict[str, str]] = None,
|
|
238
308
|
ignore_missing_tags: bool = False,
|
|
309
|
+
required_tags: Optional[List[str]] = None,
|
|
239
310
|
) -> UUID:
|
|
240
311
|
"""\
|
|
241
312
|
creates a new mission with the given name and project_id
|
|
@@ -246,16 +317,11 @@ def _create_mission(
|
|
|
246
317
|
if metadata is None:
|
|
247
318
|
metadata = {}
|
|
248
319
|
|
|
249
|
-
|
|
250
|
-
raise MissionExists(
|
|
251
|
-
f"Mission with name: `{mission_name}` already exists"
|
|
252
|
-
f" in project: {project_id}"
|
|
253
|
-
)
|
|
320
|
+
_validate_mission_name(client, project_id, mission_name)
|
|
254
321
|
|
|
255
|
-
if
|
|
256
|
-
raise
|
|
257
|
-
f"Mission
|
|
258
|
-
"mission names must not be valid UUIDv4's"
|
|
322
|
+
if required_tags and not set(required_tags).issubset(metadata.keys()):
|
|
323
|
+
raise InvalidMissionMetadata(
|
|
324
|
+
f"Mission tags `{required_tags}` are required but missing from metadata: {metadata}"
|
|
259
325
|
)
|
|
260
326
|
|
|
261
327
|
# we need to translate tag keys to tag type ids
|
|
@@ -267,9 +333,9 @@ def _create_mission(
|
|
|
267
333
|
"tags": {str(k): v for k, v in tags.items()},
|
|
268
334
|
"ignoreTags": ignore_missing_tags,
|
|
269
335
|
}
|
|
270
|
-
|
|
271
336
|
resp = client.post(CREATE_MISSION, json=payload)
|
|
272
337
|
resp.raise_for_status()
|
|
338
|
+
_validate_mission_created(client, str(project_id), mission_name)
|
|
273
339
|
|
|
274
340
|
return UUID(resp.json()["uuid"], version=4)
|
|
275
341
|
|
|
@@ -288,18 +354,37 @@ def _create_project(
|
|
|
288
354
|
return UUID(resp.json()["uuid"], version=4)
|
|
289
355
|
|
|
290
356
|
|
|
357
|
+
def _validate_tag_value(tag_value, tag_datatype) -> None:
|
|
358
|
+
if tag_datatype == "NUMBER":
|
|
359
|
+
try:
|
|
360
|
+
float(tag_value)
|
|
361
|
+
except ValueError:
|
|
362
|
+
raise InvalidMissionMetadata(f"Value '{tag_value}' is not a valid NUMBER")
|
|
363
|
+
elif tag_datatype == "BOOLEAN":
|
|
364
|
+
if tag_value.lower() not in {"true", "false"}:
|
|
365
|
+
raise InvalidMissionMetadata(
|
|
366
|
+
f"Value '{tag_value}' is not a valid BOOLEAN (expected 'true' or 'false')"
|
|
367
|
+
)
|
|
368
|
+
else:
|
|
369
|
+
pass # any string is fine
|
|
370
|
+
# TODO: add check for LOCATION tag datatype
|
|
371
|
+
|
|
372
|
+
|
|
291
373
|
def _get_metadata_type_id_by_name(
|
|
292
374
|
client: AuthenticatedClient, tag_name: str
|
|
293
|
-
) -> Optional[UUID]:
|
|
375
|
+
) -> Tuple[Optional[UUID], str]:
|
|
294
376
|
resp = client.get(TAG_TYPE_BY_NAME, params={"name": tag_name, "take": 1})
|
|
295
377
|
|
|
296
378
|
if resp.status_code in (403, 404):
|
|
297
379
|
return None
|
|
298
380
|
|
|
299
381
|
resp.raise_for_status()
|
|
382
|
+
try:
|
|
383
|
+
data = resp.json()["data"][0]
|
|
384
|
+
except IndexError:
|
|
385
|
+
return None, None
|
|
300
386
|
|
|
301
|
-
data =
|
|
302
|
-
return UUID(data["uuid"], version=4)
|
|
387
|
+
return UUID(data["uuid"], version=4), data["datatype"]
|
|
303
388
|
|
|
304
389
|
|
|
305
390
|
def _get_tags_map(
|
|
@@ -309,9 +394,10 @@ def _get_tags_map(
|
|
|
309
394
|
# why are we using metadata type ids as keys???
|
|
310
395
|
ret = {}
|
|
311
396
|
for key, val in metadata.items():
|
|
312
|
-
metadata_type_id = _get_metadata_type_id_by_name(client, key)
|
|
397
|
+
metadata_type_id, tag_datatype = _get_metadata_type_id_by_name(client, key)
|
|
313
398
|
if metadata_type_id is None:
|
|
314
399
|
raise InvalidMissionMetadata(f"metadata field: {key} does not exist")
|
|
400
|
+
_validate_tag_value(val, tag_datatype)
|
|
315
401
|
ret[metadata_type_id] = val
|
|
316
402
|
return ret
|
|
317
403
|
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_mission.py
RENAMED
|
@@ -13,6 +13,7 @@ from kleinkram.api.query import ProjectQuery
|
|
|
13
13
|
from kleinkram.api.routes import get_mission
|
|
14
14
|
from kleinkram.api.routes import get_project
|
|
15
15
|
from kleinkram.config import get_shared_state
|
|
16
|
+
from kleinkram.errors import InvalidMissionQuery
|
|
16
17
|
from kleinkram.printing import print_mission_info
|
|
17
18
|
from kleinkram.utils import load_metadata
|
|
18
19
|
from kleinkram.utils import split_args
|
|
@@ -45,13 +46,16 @@ def create(
|
|
|
45
46
|
metadata_dct = load_metadata(Path(metadata)) if metadata else {} # noqa
|
|
46
47
|
|
|
47
48
|
client = AuthenticatedClient()
|
|
48
|
-
|
|
49
|
+
project = get_project(client, project_query)
|
|
50
|
+
project_id = project.id
|
|
51
|
+
project_required_tags = project.required_tags
|
|
49
52
|
mission_id = kleinkram.api.routes._create_mission(
|
|
50
53
|
client,
|
|
51
54
|
project_id,
|
|
52
55
|
mission_name,
|
|
53
56
|
metadata=metadata_dct,
|
|
54
57
|
ignore_missing_tags=ignore_missing_tags,
|
|
58
|
+
required_tags=project_required_tags,
|
|
55
59
|
)
|
|
56
60
|
|
|
57
61
|
mission_parsed = get_mission(client, MissionQuery(ids=[mission_id]))
|
|
@@ -120,9 +124,6 @@ def delete(
|
|
|
120
124
|
False, "--confirm", "-y", "--yes", help="confirm deletion"
|
|
121
125
|
),
|
|
122
126
|
) -> None:
|
|
123
|
-
if not confirm:
|
|
124
|
-
typer.confirm(f"delete {project} {mission}", abort=True)
|
|
125
|
-
|
|
126
127
|
project_ids, project_patterns = split_args([project] if project else [])
|
|
127
128
|
project_query = ProjectQuery(ids=project_ids, patterns=project_patterns)
|
|
128
129
|
|
|
@@ -132,9 +133,20 @@ def delete(
|
|
|
132
133
|
patterns=mission_patterns,
|
|
133
134
|
project_query=project_query,
|
|
134
135
|
)
|
|
136
|
+
if mission_patterns and not (project_patterns or project_ids):
|
|
137
|
+
raise InvalidMissionQuery(
|
|
138
|
+
"Mission query does not uniquely determine mission. "
|
|
139
|
+
"Project name or id must be specified when deleting by mission name"
|
|
140
|
+
)
|
|
135
141
|
|
|
136
142
|
client = AuthenticatedClient()
|
|
137
143
|
mission_parsed = get_mission(client, mission_query)
|
|
144
|
+
if not confirm:
|
|
145
|
+
if project:
|
|
146
|
+
typer.confirm(f"delete {project} {mission}", abort=True)
|
|
147
|
+
else:
|
|
148
|
+
typer.confirm(f"delete {mission_parsed.name} {mission}", abort=True)
|
|
149
|
+
|
|
138
150
|
kleinkram.core.delete_mission(client=client, mission_id=mission_parsed.id)
|
|
139
151
|
|
|
140
152
|
|
|
@@ -107,9 +107,9 @@ def upload(
|
|
|
107
107
|
|
|
108
108
|
if create and mission is None:
|
|
109
109
|
# check if project exists and get its id at the same time
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
project = kleinkram.api.routes.get_project(client, query=query.project_query)
|
|
111
|
+
project_id = project.id
|
|
112
|
+
project_required_tags = project.required_tags
|
|
113
113
|
mission_name = check_mission_query_is_creatable(query)
|
|
114
114
|
kleinkram.api.routes._create_mission(
|
|
115
115
|
client,
|
|
@@ -117,6 +117,7 @@ def upload(
|
|
|
117
117
|
mission_name,
|
|
118
118
|
metadata=metadata or {},
|
|
119
119
|
ignore_missing_tags=ignore_missing_metadata,
|
|
120
|
+
required_tags=project_required_tags,
|
|
120
121
|
)
|
|
121
122
|
mission = kleinkram.api.routes.get_mission(client, query)
|
|
122
123
|
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/printing.py
RENAMED
|
@@ -233,7 +233,7 @@ def file_info_table(file: File) -> Table:
|
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
def mission_info_table(
|
|
236
|
-
mission: Mission, print_metadata: bool =
|
|
236
|
+
mission: Mission, print_metadata: bool = True
|
|
237
237
|
) -> Tuple[Table, ...]:
|
|
238
238
|
table = Table("k", "v", title=f"mission info: {mission.name}", show_header=False)
|
|
239
239
|
|
|
@@ -269,6 +269,7 @@ def project_info_table(project: Project) -> Table:
|
|
|
269
269
|
table.add_row("description", project.description)
|
|
270
270
|
table.add_row("created", str(project.created_at))
|
|
271
271
|
table.add_row("updated", str(project.updated_at))
|
|
272
|
+
table.add_row("required tags", ", ".join(project.required_tags))
|
|
272
273
|
|
|
273
274
|
return table
|
|
274
275
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.51.0.
|
|
3
|
+
Version: 0.51.0.dev20251003125418
|
|
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
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/__init__.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/__main__.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/_version.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/__init__.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/client.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/api/query.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/__init__.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_download.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_endpoint.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_file.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_list.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_project.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_upload.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/_verify.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/cli/app.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/py.typed
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/types.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/utils.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/kleinkram/wrappers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/testing/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_config.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_core.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_end_to_end.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_fixtures.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_printing.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_query.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_utils.py
RENAMED
|
File without changes
|
{kleinkram-0.51.0.dev20251002115350 → kleinkram-0.51.0.dev20251003125418}/tests/test_wrappers.py
RENAMED
|
File without changes
|