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/utils.py
CHANGED
|
@@ -28,15 +28,11 @@ from kleinkram.types import IdLike
|
|
|
28
28
|
from kleinkram.types import PathLike
|
|
29
29
|
|
|
30
30
|
INTERNAL_ALLOWED_CHARS = string.ascii_letters + string.digits + "_" + "-"
|
|
31
|
-
SUPPORT_FILE_TYPES = [
|
|
32
|
-
|
|
33
|
-
".mcap",
|
|
34
|
-
]
|
|
31
|
+
SUPPORT_FILE_TYPES = [".bag", ".mcap", ".db3", ".svo2", ".tum", ".yaml", ".yml"]
|
|
32
|
+
EXPERIMENTAL_FILE_TYPES = []
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
def file_paths_from_files(
|
|
38
|
-
files: Sequence[File], *, dest: Path, allow_nested: bool = False
|
|
39
|
-
) -> Dict[Path, File]:
|
|
35
|
+
def file_paths_from_files(files: Sequence[File], *, dest: Path, allow_nested: bool = False) -> Dict[Path, File]:
|
|
40
36
|
"""\
|
|
41
37
|
determines the destinations for a sequence of `File` objects,
|
|
42
38
|
possibly nested by project and mission
|
|
@@ -46,10 +42,7 @@ def file_paths_from_files(
|
|
|
46
42
|
elif not allow_nested:
|
|
47
43
|
return {dest / file.name: file for file in files}
|
|
48
44
|
else:
|
|
49
|
-
return {
|
|
50
|
-
dest / file.project_name / file.mission_name / file.name: file
|
|
51
|
-
for file in files
|
|
52
|
-
}
|
|
45
|
+
return {dest / file.project_name / file.mission_name / file.name: file for file in files}
|
|
53
46
|
|
|
54
47
|
|
|
55
48
|
def upper_camel_case_to_words(s: str) -> List[str]:
|
|
@@ -89,13 +82,10 @@ def check_file_path(file: Path) -> None:
|
|
|
89
82
|
if not file.exists():
|
|
90
83
|
raise FileNotFoundError(f"{file} does not exist")
|
|
91
84
|
if file.suffix not in SUPPORT_FILE_TYPES:
|
|
92
|
-
raise FileTypeNotSupported(
|
|
93
|
-
f"only {', '.join(SUPPORT_FILE_TYPES)} files are supported: {file}"
|
|
94
|
-
)
|
|
85
|
+
raise FileTypeNotSupported(f"only {', '.join(SUPPORT_FILE_TYPES)} files are supported: {file}")
|
|
95
86
|
if not check_filename_is_sanatized(file.stem):
|
|
96
87
|
raise FileNameNotSupported(
|
|
97
|
-
f"only `{''.join(INTERNAL_ALLOWED_CHARS)}` are "
|
|
98
|
-
f"allowed in filenames and at most 50chars: {file}"
|
|
88
|
+
f"only `{''.join(INTERNAL_ALLOWED_CHARS)}` are " f"allowed in filenames and at most 50chars: {file}"
|
|
99
89
|
)
|
|
100
90
|
|
|
101
91
|
|
|
@@ -108,9 +98,7 @@ def format_error(msg: str, exc: Exception, *, verbose: bool = False) -> str:
|
|
|
108
98
|
|
|
109
99
|
|
|
110
100
|
def format_traceback(exc: Exception) -> str:
|
|
111
|
-
return "".join(
|
|
112
|
-
traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
|
|
113
|
-
)
|
|
101
|
+
return "".join(traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__))
|
|
114
102
|
|
|
115
103
|
|
|
116
104
|
def styled_string(*objects: Any, **kwargs: Any) -> str:
|
|
@@ -150,9 +138,7 @@ def get_filename(path: Path) -> str:
|
|
|
150
138
|
- the 10 hashed chars are deterministic given the original filename
|
|
151
139
|
"""
|
|
152
140
|
|
|
153
|
-
stem = "".join(
|
|
154
|
-
char if char in INTERNAL_ALLOWED_CHARS else "_" for char in path.stem
|
|
155
|
-
)
|
|
141
|
+
stem = "".join(char if char in INTERNAL_ALLOWED_CHARS else "_" for char in path.stem)
|
|
156
142
|
|
|
157
143
|
if len(stem) > 50:
|
|
158
144
|
hash = md5(path.name.encode()).hexdigest()
|
|
@@ -229,10 +215,10 @@ def format_bytes(size_bytes: int | float, speed: bool = False) -> str:
|
|
|
229
215
|
return "0 B/s" if speed else "0 B"
|
|
230
216
|
|
|
231
217
|
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
232
|
-
power = math.floor(math.log(size_bytes,
|
|
218
|
+
power = math.floor(math.log(size_bytes, 1000))
|
|
233
219
|
unit_index = min(power, len(units) - 1)
|
|
234
220
|
|
|
235
|
-
value = size_bytes / (
|
|
221
|
+
value = size_bytes / (1000**unit_index)
|
|
236
222
|
|
|
237
223
|
unit_suffix = "/s" if speed else ""
|
|
238
224
|
return f"{value:.2f} {units[unit_index]}{unit_suffix}"
|
kleinkram/wrappers.py
CHANGED
|
@@ -9,6 +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 Any
|
|
12
13
|
from typing import Collection
|
|
13
14
|
from typing import Dict
|
|
14
15
|
from typing import List
|
|
@@ -39,6 +40,11 @@ def _args_to_project_query(
|
|
|
39
40
|
project_names: Optional[Sequence[str]] = None,
|
|
40
41
|
project_ids: Optional[Sequence[IdLike]] = None,
|
|
41
42
|
) -> ProjectQuery:
|
|
43
|
+
|
|
44
|
+
# verify types of passed arguments
|
|
45
|
+
_verify_string_sequence("project_names", project_names)
|
|
46
|
+
_verify_sequence("project_ids", project_ids)
|
|
47
|
+
|
|
42
48
|
return ProjectQuery(
|
|
43
49
|
ids=[parse_uuid_like(_id) for _id in project_ids or []],
|
|
44
50
|
patterns=list(project_names or []),
|
|
@@ -51,15 +57,39 @@ def _args_to_mission_query(
|
|
|
51
57
|
project_names: Optional[Sequence[str]] = None,
|
|
52
58
|
project_ids: Optional[Sequence[IdLike]] = None,
|
|
53
59
|
) -> MissionQuery:
|
|
60
|
+
|
|
61
|
+
# verify types of passed arguments
|
|
62
|
+
_verify_string_sequence("mission_names", mission_names)
|
|
63
|
+
_verify_sequence("mission_ids", mission_ids)
|
|
64
|
+
_verify_string_sequence("project_names", project_names)
|
|
65
|
+
_verify_sequence("project_ids", project_ids)
|
|
66
|
+
|
|
54
67
|
return MissionQuery(
|
|
55
68
|
ids=[parse_uuid_like(_id) for _id in mission_ids or []],
|
|
56
69
|
patterns=list(mission_names or []),
|
|
57
|
-
project_query=_args_to_project_query(
|
|
58
|
-
project_names=project_names, project_ids=project_ids
|
|
59
|
-
),
|
|
70
|
+
project_query=_args_to_project_query(project_names=project_names, project_ids=project_ids),
|
|
60
71
|
)
|
|
61
72
|
|
|
62
73
|
|
|
74
|
+
def _verify_sequence(arg_name: str, arg_value: Optional[Sequence[Any]]) -> None:
|
|
75
|
+
"""Verifies that an argument is either None, or a sequence."""
|
|
76
|
+
if arg_value is not None:
|
|
77
|
+
if not isinstance(arg_value, Sequence):
|
|
78
|
+
raise TypeError(f"{arg_name} must be a Sequence, None, or empty array.")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _verify_string_sequence(arg_name: str, arg_value: Optional[Sequence[Any]]) -> None:
|
|
82
|
+
"""Verifies that an argument is either None, an empty sequence, or a sequence of strings."""
|
|
83
|
+
if arg_value is not None:
|
|
84
|
+
if not isinstance(arg_value, Sequence):
|
|
85
|
+
raise TypeError(f"{arg_name} must be a Sequence, None, or empty array.")
|
|
86
|
+
if isinstance(arg_value, str):
|
|
87
|
+
raise TypeError(f"{arg_name} cannot be a string, but a sequence of strings.")
|
|
88
|
+
for item in arg_value:
|
|
89
|
+
if not isinstance(item, str):
|
|
90
|
+
raise TypeError(f"{arg_name} must contain strings only.")
|
|
91
|
+
|
|
92
|
+
|
|
63
93
|
def _args_to_file_query(
|
|
64
94
|
file_names: Optional[Sequence[str]] = None,
|
|
65
95
|
file_ids: Optional[Sequence[IdLike]] = None,
|
|
@@ -68,6 +98,15 @@ def _args_to_file_query(
|
|
|
68
98
|
project_names: Optional[Sequence[str]] = None,
|
|
69
99
|
project_ids: Optional[Sequence[IdLike]] = None,
|
|
70
100
|
) -> FileQuery:
|
|
101
|
+
|
|
102
|
+
# verify types of passed arguments
|
|
103
|
+
_verify_string_sequence("file_names", file_names)
|
|
104
|
+
_verify_sequence("file_ids", file_ids)
|
|
105
|
+
_verify_string_sequence("mission_names", mission_names)
|
|
106
|
+
_verify_sequence("mission_ids", mission_ids)
|
|
107
|
+
_verify_string_sequence("project_names", project_names)
|
|
108
|
+
_verify_sequence("project_ids", project_ids)
|
|
109
|
+
|
|
71
110
|
return FileQuery(
|
|
72
111
|
ids=[parse_uuid_like(_id) for _id in file_ids or []],
|
|
73
112
|
patterns=list(file_names or []),
|
|
@@ -288,6 +327,9 @@ def verify(
|
|
|
288
327
|
project_names=singleton_list(project_name),
|
|
289
328
|
project_ids=singleton_list(project_id),
|
|
290
329
|
)
|
|
330
|
+
|
|
331
|
+
_verify_string_sequence("files", files)
|
|
332
|
+
|
|
291
333
|
return kleinkram.core.verify(
|
|
292
334
|
client=AuthenticatedClient(),
|
|
293
335
|
query=query,
|
|
@@ -313,15 +355,11 @@ def create_mission(
|
|
|
313
355
|
|
|
314
356
|
|
|
315
357
|
def create_project(project_name: str, description: str) -> None:
|
|
316
|
-
kleinkram.api.routes._create_project(
|
|
317
|
-
AuthenticatedClient(), project_name, description
|
|
318
|
-
)
|
|
358
|
+
kleinkram.api.routes._create_project(AuthenticatedClient(), project_name, description)
|
|
319
359
|
|
|
320
360
|
|
|
321
361
|
def update_file(file_id: IdLike) -> None:
|
|
322
|
-
kleinkram.core.update_file(
|
|
323
|
-
client=AuthenticatedClient(), file_id=parse_uuid_like(file_id)
|
|
324
|
-
)
|
|
362
|
+
kleinkram.core.update_file(client=AuthenticatedClient(), file_id=parse_uuid_like(file_id))
|
|
325
363
|
|
|
326
364
|
|
|
327
365
|
def update_mission(mission_id: IdLike, metadata: Dict[str, str]) -> None:
|
|
@@ -354,48 +392,34 @@ def delete_file(file_id: IdLike) -> None:
|
|
|
354
392
|
"""\
|
|
355
393
|
delete a single file by id
|
|
356
394
|
"""
|
|
357
|
-
file = kleinkram.api.routes.get_file(
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
kleinkram.api.routes._delete_files(
|
|
361
|
-
AuthenticatedClient(), file_ids=[file.id], mission_id=file.mission_id
|
|
362
|
-
)
|
|
395
|
+
file = kleinkram.api.routes.get_file(AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)]))
|
|
396
|
+
kleinkram.api.routes._delete_files(AuthenticatedClient(), file_ids=[file.id], mission_id=file.mission_id)
|
|
363
397
|
|
|
364
398
|
|
|
365
399
|
def delete_mission(mission_id: IdLike) -> None:
|
|
366
|
-
kleinkram.core.delete_mission(
|
|
367
|
-
client=AuthenticatedClient(), mission_id=parse_uuid_like(mission_id)
|
|
368
|
-
)
|
|
400
|
+
kleinkram.core.delete_mission(client=AuthenticatedClient(), mission_id=parse_uuid_like(mission_id))
|
|
369
401
|
|
|
370
402
|
|
|
371
403
|
def delete_project(project_id: IdLike) -> None:
|
|
372
|
-
kleinkram.core.delete_project(
|
|
373
|
-
client=AuthenticatedClient(), project_id=parse_uuid_like(project_id)
|
|
374
|
-
)
|
|
404
|
+
kleinkram.core.delete_project(client=AuthenticatedClient(), project_id=parse_uuid_like(project_id))
|
|
375
405
|
|
|
376
406
|
|
|
377
407
|
def get_file(file_id: IdLike) -> File:
|
|
378
408
|
"""\
|
|
379
409
|
get a file by its id
|
|
380
410
|
"""
|
|
381
|
-
return kleinkram.api.routes.get_file(
|
|
382
|
-
AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)])
|
|
383
|
-
)
|
|
411
|
+
return kleinkram.api.routes.get_file(AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)]))
|
|
384
412
|
|
|
385
413
|
|
|
386
414
|
def get_mission(mission_id: IdLike) -> Mission:
|
|
387
415
|
"""\
|
|
388
416
|
get a mission by its id
|
|
389
417
|
"""
|
|
390
|
-
return kleinkram.api.routes.get_mission(
|
|
391
|
-
AuthenticatedClient(), MissionQuery(ids=[parse_uuid_like(mission_id)])
|
|
392
|
-
)
|
|
418
|
+
return kleinkram.api.routes.get_mission(AuthenticatedClient(), MissionQuery(ids=[parse_uuid_like(mission_id)]))
|
|
393
419
|
|
|
394
420
|
|
|
395
421
|
def get_project(project_id: IdLike) -> Project:
|
|
396
422
|
"""\
|
|
397
423
|
get a project by its id
|
|
398
424
|
"""
|
|
399
|
-
return kleinkram.api.routes.get_project(
|
|
400
|
-
AuthenticatedClient(), ProjectQuery(ids=[parse_uuid_like(project_id)])
|
|
401
|
-
)
|
|
425
|
+
return kleinkram.api.routes.get_project(AuthenticatedClient(), ProjectQuery(ids=[parse_uuid_like(project_id)]))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.58.0.dev20260110152317
|
|
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
|
|
@@ -16,9 +16,11 @@ Requires-Python: >=3.8
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
Requires-Dist: boto3
|
|
18
18
|
Requires-Dist: botocore
|
|
19
|
+
Requires-Dist: click
|
|
19
20
|
Requires-Dist: httpx
|
|
20
21
|
Requires-Dist: python-dateutil
|
|
21
22
|
Requires-Dist: pyyaml
|
|
23
|
+
Requires-Dist: requests
|
|
22
24
|
Requires-Dist: rich
|
|
23
25
|
Requires-Dist: tqdm
|
|
24
26
|
Requires-Dist: typer
|
|
@@ -75,7 +77,7 @@ Instead of downloading files from a specified mission you can download arbitrary
|
|
|
75
77
|
klein download --dest out *id1* *id2* *id3*
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
For more information consult the [documentation](https://docs.datasets.leggedrobotics.com
|
|
80
|
+
For more information consult the [documentation](https://docs.datasets.leggedrobotics.com//usage/python/setup).
|
|
79
81
|
|
|
80
82
|
## Development
|
|
81
83
|
|
|
@@ -117,7 +119,7 @@ pytest
|
|
|
117
119
|
```
|
|
118
120
|
For the latter you need to have an instance of the backend running locally.
|
|
119
121
|
See instructions in the root of the repository for this.
|
|
120
|
-
On top of that these tests require particular files to be present in the `cli/data
|
|
121
|
-
|
|
122
|
+
On top of that these tests require particular files to be present in the `cli/tests/data` directory.
|
|
123
|
+
These files are automatically generated by the `cli/tests/generate_test_data.py` script.
|
|
122
124
|
|
|
123
125
|
You also need to make sure to be logged in with the cli with `klein login`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
kleinkram/__init__.py,sha256=xIJqTJw2kbCGryGlCeAdpmtR1FTxmrW1MklUNQEaj74,1061
|
|
2
|
+
kleinkram/__main__.py,sha256=B9RiZxfO4jpCmWPUHyKJ7_EoZlEG4sPpH-nz7T_YhhQ,125
|
|
3
|
+
kleinkram/_version.py,sha256=QYJyRTcqFcJj4qWYpqs7WcoOP6jxDMqyvxLY-cD6KcE,129
|
|
4
|
+
kleinkram/auth.py,sha256=zbjSIPBS5xzLzo75Ou2dgUluXLwBRxKxBdSwtIQdA5g,6835
|
|
5
|
+
kleinkram/config.py,sha256=fCRyDdBKXbH7HQ_Kc3YGTamqGL9zkKLRIIQTQDql0Ec,7273
|
|
6
|
+
kleinkram/core.py,sha256=AhFIxm2jjzRUlGXrOJksebYHOyuioAcbpCx0WmbIpbs,9790
|
|
7
|
+
kleinkram/errors.py,sha256=Bevo8LNPlM46C9JY_uj1Z3FSvPJ051Vyd0M9IYENLtc,1140
|
|
8
|
+
kleinkram/main.py,sha256=BTE0mZN__xd46wBhFi6iBlK9eGGQvJ1LdUMsbnysLi0,172
|
|
9
|
+
kleinkram/models.py,sha256=YB3pcZGJ5nXeZ9gLTbpaaWsUCnvaDIcnvpXUnyWZAaY,2824
|
|
10
|
+
kleinkram/printing.py,sha256=U8I43Ju3orA_jKqF5f-vmgl3cC8Ffjo5m2fgChmY6ik,19163
|
|
11
|
+
kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
|
|
13
|
+
kleinkram/utils.py,sha256=iPZXUbiG4cYfkL3CG-Rs-dwVovNYkgybfAAvD0oamBY,6725
|
|
14
|
+
kleinkram/wrappers.py,sha256=TprQWvEw-8QsgcDNsQxjBkcEwVFQyfmclTpt8wcZ4tA,12967
|
|
15
|
+
kleinkram/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
kleinkram/api/client.py,sha256=j-ZWBsHI-PQOk86RtDfqunaSVyKqCnKdqPMy0tCbdNI,5840
|
|
17
|
+
kleinkram/api/deser.py,sha256=94O1ZeP0kW-LaKaZeP4o1PQLeHbxxZ_C7nU2FzunntY,10077
|
|
18
|
+
kleinkram/api/file_transfer.py,sha256=j1iINYFV4iXMjAz59eM6y8G35Uq4GpPgqf1T0z73Jzs,19710
|
|
19
|
+
kleinkram/api/pagination.py,sha256=NRVGorgNthKi4t_H9lOn87rDebx33WVOvTadA6-4QXw,1693
|
|
20
|
+
kleinkram/api/query.py,sha256=WR_j0WRt9LDW9uyqc8pEdjmgN5eOLVQi1y3LSOh2Y9A,3498
|
|
21
|
+
kleinkram/api/routes.py,sha256=zKgjEiuFP-v06pBnN87yLcdYyuAV4Jx-hbEf44qu_e4,17461
|
|
22
|
+
kleinkram/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
kleinkram/cli/_action.py,sha256=Gb3kIs4jcutINX4pmrnOjhetcEpeAGVs3llX0GR9gz0,4911
|
|
24
|
+
kleinkram/cli/_download.py,sha256=n4d9y8ViTLnzrfn8MLaM_6Jlsuh9_Y_F0I-dfAhZEqI,2318
|
|
25
|
+
kleinkram/cli/_endpoint.py,sha256=dFLTaARXP96ide4J4cOHx5qHc3_iFYGJmhz-fKLGbP8,1785
|
|
26
|
+
kleinkram/cli/_file.py,sha256=CiTmy3Di7lVBH7mfsNbWpiwyDl8WGw0xi8MV0-EmAV4,2897
|
|
27
|
+
kleinkram/cli/_file_validator.py,sha256=6SkQGNysq1ydZimfv7MmAqyRP7xiKwaWcoK7R-tL_M4,4552
|
|
28
|
+
kleinkram/cli/_list.py,sha256=jDZlG76b0KWr8imBQdA--V2DC6O3j6jrMd6qyJm6cTw,3028
|
|
29
|
+
kleinkram/cli/_mission.py,sha256=nZjk9gHsTCMkmwbtNMIb9fxfySyggc3nGJdTC0t45G4,5759
|
|
30
|
+
kleinkram/cli/_project.py,sha256=C6mnNJM4oloFto9wDY4zgwdmXNsqR9ErbgZeFdhCENk,3338
|
|
31
|
+
kleinkram/cli/_run.py,sha256=TRx08DPB4PRGIqZl2CjHEDODgQac3Jr7iNQqBN9A7nE,8008
|
|
32
|
+
kleinkram/cli/_upload.py,sha256=PkD9O4ApBo5VUxdt70pu0XdJqZ88FrY_vZWQzTtN0pg,4030
|
|
33
|
+
kleinkram/cli/_verify.py,sha256=j-bRmFqW2j88ZwVOSFEy0Dgok-JY0nAPkcTAOwgxQyY,3211
|
|
34
|
+
kleinkram/cli/app.py,sha256=fpme23Y6pd2LeTadcFYBNfyjDn4AEGltSJVg79GCDAI,8940
|
|
35
|
+
kleinkram/cli/error_handling.py,sha256=ZFcJhPXWITG1cGpnsiuDVVehjIqO5pbz47zWdL1QcQs,1907
|
|
36
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
tests/backend_fixtures.py,sha256=Kjr1W9NYQ1yCOqLheAIILYeTo9B59WW1M0FMr2MPKew,2308
|
|
38
|
+
tests/conftest.py,sha256=0kbRbC4AJhwjOlZYdwkzQHLBmcrU5pB3o76PImunP4c,102
|
|
39
|
+
tests/generate_test_data.py,sha256=TkyB208mHRs1wINq0aHOfWt5J0ad4umnATPSnHgkENY,11799
|
|
40
|
+
tests/test_config.py,sha256=RUIUEqTnAKg8EyqXVGohwfm9jtuaWo1hWqVxZMV6_bU,5843
|
|
41
|
+
tests/test_core.py,sha256=qIPYuMpI3yh2G6OEqqqjRCWlvaM9bd9VC0bB5kI7PJs,5473
|
|
42
|
+
tests/test_end_to_end.py,sha256=mySbCPZPmrszo2AjD18TDitrPAJJ73oz7DaBpTXIkiI,3337
|
|
43
|
+
tests/test_error_handling.py,sha256=qPSMKF1qsAHyUME0-krxbIrk38iGKkhAyAah-KwN4NE,1300
|
|
44
|
+
tests/test_fixtures.py,sha256=SaOe7naSqGH3nivdbwyNo1tcYOg_XU-NJy1cFv3__2o,1053
|
|
45
|
+
tests/test_printing.py,sha256=Kfg0XjGV7ymvI4RgHtNc8wiwpqB3VG0wc1XYWO1ld_k,2217
|
|
46
|
+
tests/test_query.py,sha256=fExmCKXLA7-9j2S2sF_sbvRX_2s6Cp3a7OTcqE25q9g,3864
|
|
47
|
+
tests/test_utils.py,sha256=_DGxsQ60YEKeLNmrwLdJbevSFhWgIK9f38fH02VA8MI,4762
|
|
48
|
+
tests/test_wrappers.py,sha256=Jr7qFbkEBbs21S-SkwF8A5cH-OcFsVz0Y0kBc6gGBuY,2547
|
|
49
|
+
kleinkram-0.58.0.dev20260110152317.dist-info/METADATA,sha256=bNsmokgdVcrAZPIx6CzVWcNI624Ayy1EWrDuKkdDfyQ,2862
|
|
50
|
+
kleinkram-0.58.0.dev20260110152317.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
51
|
+
kleinkram-0.58.0.dev20260110152317.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
52
|
+
kleinkram-0.58.0.dev20260110152317.dist-info/top_level.txt,sha256=G1Lj9vHAtZn402Ukkrfll-6BCmnDNy_HVtWeNvXzdDA,16
|
|
53
|
+
kleinkram-0.58.0.dev20260110152317.dist-info/RECORD,,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
3
5
|
import time
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from secrets import token_hex
|
|
@@ -14,7 +16,7 @@ from kleinkram import list_projects
|
|
|
14
16
|
from kleinkram import upload
|
|
15
17
|
|
|
16
18
|
# we expect the mission files to be in this folder that is not commited to the repo
|
|
17
|
-
DATA_PATH = Path(__file__).parent
|
|
19
|
+
DATA_PATH = Path(__file__).parent / "data"
|
|
18
20
|
DATA_FILES = [
|
|
19
21
|
DATA_PATH / "10_KB.bag",
|
|
20
22
|
DATA_PATH / "50_KB.bag",
|
|
@@ -26,7 +28,7 @@ DATA_FILES = [
|
|
|
26
28
|
PROJECT_DESCRIPTION = "This is a test project"
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
WAIT_BEFORE_DELETION = 5
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
@pytest.fixture(scope="session")
|
|
@@ -38,7 +40,7 @@ def project():
|
|
|
38
40
|
|
|
39
41
|
yield project
|
|
40
42
|
|
|
41
|
-
time.sleep(
|
|
43
|
+
time.sleep(WAIT_BEFORE_DELETION)
|
|
42
44
|
delete_project(project.id)
|
|
43
45
|
|
|
44
46
|
|
|
@@ -63,3 +65,25 @@ def empty_mission(project):
|
|
|
63
65
|
mission = list_missions(project_ids=[project.id], mission_names=[mission_name])[0]
|
|
64
66
|
|
|
65
67
|
yield mission
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
71
|
+
def auto_login():
|
|
72
|
+
"""
|
|
73
|
+
Automatically logs in using the CLI before running any tests.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# Run: python3 -m kleinkram login --user 1
|
|
77
|
+
result = subprocess.run(
|
|
78
|
+
[sys.executable, "-m", "kleinkram", "login", "--user", "1"],
|
|
79
|
+
cwd=str(Path(__file__).parent.parent), # Run from cli root
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
)
|
|
83
|
+
if result.returncode != 0:
|
|
84
|
+
pytest.fail(
|
|
85
|
+
f"Failed to auto-login. Return code: {result.returncode}\nStdout: {result.stdout}\nStderr: {result.stderr}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
pytest.fail(f"Failed to auto-login: {e}")
|
tests/conftest.py
CHANGED