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.
Files changed (44) hide show
  1. kleinkram/api/client.py +6 -18
  2. kleinkram/api/deser.py +152 -1
  3. kleinkram/api/file_transfer.py +202 -101
  4. kleinkram/api/pagination.py +11 -2
  5. kleinkram/api/query.py +10 -10
  6. kleinkram/api/routes.py +192 -59
  7. kleinkram/auth.py +108 -7
  8. kleinkram/cli/_action.py +131 -0
  9. kleinkram/cli/_download.py +8 -19
  10. kleinkram/cli/_endpoint.py +2 -4
  11. kleinkram/cli/_file.py +6 -18
  12. kleinkram/cli/_file_validator.py +125 -0
  13. kleinkram/cli/_list.py +5 -15
  14. kleinkram/cli/_mission.py +24 -28
  15. kleinkram/cli/_project.py +10 -26
  16. kleinkram/cli/_run.py +220 -0
  17. kleinkram/cli/_upload.py +58 -26
  18. kleinkram/cli/_verify.py +59 -16
  19. kleinkram/cli/app.py +56 -17
  20. kleinkram/cli/error_handling.py +1 -3
  21. kleinkram/config.py +6 -21
  22. kleinkram/core.py +53 -43
  23. kleinkram/errors.py +12 -0
  24. kleinkram/models.py +51 -1
  25. kleinkram/printing.py +229 -18
  26. kleinkram/utils.py +10 -24
  27. kleinkram/wrappers.py +54 -30
  28. {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/METADATA +6 -4
  29. kleinkram-0.58.0.dev20260110152317.dist-info/RECORD +53 -0
  30. {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/WHEEL +1 -1
  31. {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/top_level.txt +0 -1
  32. {testing → tests}/backend_fixtures.py +27 -3
  33. tests/conftest.py +1 -1
  34. tests/generate_test_data.py +314 -0
  35. tests/test_config.py +2 -6
  36. tests/test_core.py +11 -31
  37. tests/test_end_to_end.py +3 -5
  38. tests/test_fixtures.py +3 -5
  39. tests/test_printing.py +9 -11
  40. tests/test_utils.py +1 -3
  41. tests/test_wrappers.py +9 -27
  42. kleinkram-0.43.2.dev20250331124109.dist-info/RECORD +0 -50
  43. testing/__init__.py +0 -0
  44. {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
- ".bag",
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, 1024))
218
+ power = math.floor(math.log(size_bytes, 1000))
233
219
  unit_index = min(power, len(units) - 1)
234
220
 
235
- value = size_bytes / (1024**unit_index)
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
- AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)])
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.43.2.dev20250331124109
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/usage/python/getting-started.html).
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/testing` directory.
121
- To see the exact files that are required, see `cli/testing/backend_fixtures.py`.
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,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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.parent / "data" / "testing"
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
- WAIT_BEOFORE_DELETION = 5
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(WAIT_BEOFORE_DELETION)
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
@@ -3,5 +3,5 @@ from __future__ import annotations
3
3
  import pytest
4
4
 
5
5
  pytest_plugins = [
6
- "testing.backend_fixtures",
6
+ "tests.backend_fixtures",
7
7
  ]