kleinkram 0.38.1.dev20241212075157__py3-none-any.whl → 0.38.1.dev20250207122632__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.

Potentially problematic release.


This version of kleinkram might be problematic. Click here for more details.

Files changed (58) hide show
  1. kleinkram/__init__.py +33 -2
  2. kleinkram/api/client.py +21 -16
  3. kleinkram/api/deser.py +165 -0
  4. kleinkram/api/file_transfer.py +13 -24
  5. kleinkram/api/pagination.py +56 -0
  6. kleinkram/api/query.py +111 -0
  7. kleinkram/api/routes.py +266 -97
  8. kleinkram/auth.py +21 -20
  9. kleinkram/cli/__init__.py +0 -0
  10. kleinkram/{commands/download.py → cli/_download.py} +18 -44
  11. kleinkram/cli/_endpoint.py +58 -0
  12. kleinkram/{commands/list.py → cli/_list.py} +25 -38
  13. kleinkram/cli/_mission.py +153 -0
  14. kleinkram/cli/_project.py +99 -0
  15. kleinkram/cli/_upload.py +84 -0
  16. kleinkram/cli/_verify.py +56 -0
  17. kleinkram/{app.py → cli/app.py} +57 -25
  18. kleinkram/cli/error_handling.py +67 -0
  19. kleinkram/config.py +141 -107
  20. kleinkram/core.py +251 -3
  21. kleinkram/errors.py +13 -45
  22. kleinkram/main.py +1 -1
  23. kleinkram/models.py +48 -149
  24. kleinkram/printing.py +325 -0
  25. kleinkram/py.typed +0 -0
  26. kleinkram/types.py +9 -0
  27. kleinkram/utils.py +88 -29
  28. kleinkram/wrappers.py +401 -0
  29. {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/METADATA +3 -3
  30. kleinkram-0.38.1.dev20250207122632.dist-info/RECORD +49 -0
  31. {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/WHEEL +1 -1
  32. {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/top_level.txt +1 -0
  33. testing/__init__.py +0 -0
  34. testing/backend_fixtures.py +67 -0
  35. tests/conftest.py +7 -0
  36. tests/test_config.py +115 -0
  37. tests/test_core.py +165 -0
  38. tests/test_end_to_end.py +29 -39
  39. tests/test_error_handling.py +44 -0
  40. tests/test_fixtures.py +34 -0
  41. tests/test_printing.py +62 -0
  42. tests/test_query.py +138 -0
  43. tests/test_utils.py +46 -24
  44. tests/test_wrappers.py +71 -0
  45. kleinkram/api/parsing.py +0 -86
  46. kleinkram/commands/__init__.py +0 -1
  47. kleinkram/commands/endpoint.py +0 -62
  48. kleinkram/commands/mission.py +0 -69
  49. kleinkram/commands/project.py +0 -24
  50. kleinkram/commands/upload.py +0 -164
  51. kleinkram/commands/verify.py +0 -142
  52. kleinkram/consts.py +0 -8
  53. kleinkram/enums.py +0 -10
  54. kleinkram/resources.py +0 -158
  55. kleinkram-0.38.1.dev20241212075157.dist-info/LICENSE +0 -674
  56. kleinkram-0.38.1.dev20241212075157.dist-info/RECORD +0 -37
  57. tests/test_resources.py +0 -137
  58. {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/entry_points.txt +0 -0
kleinkram/utils.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import base64
4
- import fnmatch
5
4
  import hashlib
5
+ import re
6
6
  import string
7
7
  import traceback
8
8
  from hashlib import md5
@@ -10,20 +10,60 @@ from pathlib import Path
10
10
  from typing import Any
11
11
  from typing import Dict
12
12
  from typing import List
13
+ from typing import Optional
13
14
  from typing import Sequence
14
15
  from typing import Tuple
15
- from typing import Union
16
+ from typing import TypeVar
16
17
  from uuid import UUID
17
18
 
18
19
  import yaml
20
+ from rich.console import Console
21
+
19
22
  from kleinkram._version import __version__
23
+ from kleinkram.errors import FileNameNotSupported
20
24
  from kleinkram.errors import FileTypeNotSupported
21
- from rich.console import Console
25
+ from kleinkram.models import File
26
+ from kleinkram.types import IdLike
27
+ from kleinkram.types import PathLike
22
28
 
23
29
  INTERNAL_ALLOWED_CHARS = string.ascii_letters + string.digits + "_" + "-"
30
+ SUPPORT_FILE_TYPES = [
31
+ ".bag",
32
+ ".mcap",
33
+ ]
34
+
35
+
36
+ def file_paths_from_files(
37
+ files: Sequence[File], *, dest: Path, allow_nested: bool = False
38
+ ) -> Dict[Path, File]:
39
+ """\
40
+ determines the destinations for a sequence of `File` objects,
41
+ possibly nested by project and mission
42
+ """
43
+ if (
44
+ len(set([(file.project_id, file.mission_id) for file in files])) > 1
45
+ and not allow_nested
46
+ ):
47
+ raise ValueError("files from multiple missions were selected")
48
+ elif not allow_nested:
49
+ return {dest / file.name: file for file in files}
50
+ else:
51
+ return {
52
+ dest / file.project_name / file.mission_name / file.name: file
53
+ for file in files
54
+ }
24
55
 
25
56
 
26
- def split_args(args: List[str]) -> Tuple[List[UUID], List[str]]:
57
+ def upper_camel_case_to_words(s: str) -> List[str]:
58
+ """split `s` given upper camel case to words"""
59
+ return re.sub("([a-z])([A-Z])", r"\1 \2", s).split()
60
+
61
+
62
+ def split_args(args: Sequence[str]) -> Tuple[List[UUID], List[str]]:
63
+ """\
64
+ split a sequece of strings into a list of UUIDs and a list of names
65
+ depending on whether the string is a valid UUID or not
66
+ """
27
67
  uuids = []
28
68
  names = []
29
69
  for arg in args:
@@ -35,20 +75,30 @@ def split_args(args: List[str]) -> Tuple[List[UUID], List[str]]:
35
75
 
36
76
 
37
77
  def check_file_paths(files: Sequence[Path]) -> None:
78
+ """\
79
+ checks that files exist, are files and have a supported file suffix
80
+
81
+ NOTE: kleinkram treats filesuffixes as filetypes and limits
82
+ the supported suffixes
83
+ """
38
84
  for file in files:
39
- if file.is_dir():
40
- raise FileNotFoundError(f"{file} is a directory and not a file")
41
- if not file.exists():
42
- raise FileNotFoundError(f"{file} does not exist")
43
- if file.suffix not in (".bag", ".mcap"):
44
- raise FileTypeNotSupported(
45
- f"only `.bag` or `.mcap` files are supported: {file}"
46
- )
85
+ check_file_path(file)
47
86
 
48
87
 
49
- def noop(*args: Any, **kwargs: Any) -> None:
50
- _ = args, kwargs # suppress unused variable warning
51
- return
88
+ def check_file_path(file: Path) -> None:
89
+ if file.is_dir():
90
+ raise FileNotFoundError(f"{file} is a directory and not a file")
91
+ if not file.exists():
92
+ raise FileNotFoundError(f"{file} does not exist")
93
+ if file.suffix not in SUPPORT_FILE_TYPES:
94
+ raise FileTypeNotSupported(
95
+ f"only {', '.join(SUPPORT_FILE_TYPES)} files are supported: {file}"
96
+ )
97
+ if not check_filename_is_sanatized(file.stem):
98
+ raise FileNameNotSupported(
99
+ f"only `{''.join(INTERNAL_ALLOWED_CHARS)}` are "
100
+ f"allowed in filenames and at most 50chars: {file}"
101
+ )
52
102
 
53
103
 
54
104
  def format_error(msg: str, exc: Exception, *, verbose: bool = False) -> str:
@@ -65,14 +115,6 @@ def format_traceback(exc: Exception) -> str:
65
115
  )
66
116
 
67
117
 
68
- def filtered_by_patterns(names: Sequence[str], patterns: List[str]) -> List[str]:
69
- filtered = []
70
- for name in names:
71
- if any(fnmatch.fnmatch(name, p) for p in patterns):
72
- filtered.append(name)
73
- return filtered
74
-
75
-
76
118
  def styled_string(*objects: Any, **kwargs: Any) -> str:
77
119
  """\
78
120
  accepts any object that Console.print can print
@@ -92,6 +134,14 @@ def is_valid_uuid4(uuid: str) -> bool:
92
134
  return False
93
135
 
94
136
 
137
+ def check_filename_is_sanatized(filename: str) -> bool:
138
+ if len(filename) > 50:
139
+ return False
140
+ if not all(char in INTERNAL_ALLOWED_CHARS for char in filename):
141
+ return False
142
+ return True
143
+
144
+
95
145
  def get_filename(path: Path) -> str:
96
146
  """\
97
147
  takes a path and returns a sanitized filename
@@ -145,12 +195,6 @@ def b64_md5(file: Path) -> str:
145
195
  return base64.b64encode(binary_digest).decode("utf-8")
146
196
 
147
197
 
148
- def to_name_or_uuid(s: str) -> Union[UUID, str]:
149
- if is_valid_uuid4(s):
150
- return UUID(s)
151
- return s
152
-
153
-
154
198
  def load_metadata(path: Path) -> Dict[str, str]:
155
199
  if not path.exists():
156
200
  raise FileNotFoundError(f"metadata file not found: {path}")
@@ -164,3 +208,18 @@ def load_metadata(path: Path) -> Dict[str, str]:
164
208
  def get_supported_api_version() -> Tuple[int, int, int]:
165
209
  vers = __version__.split(".")
166
210
  return tuple(map(int, vers[:3])) # type: ignore
211
+
212
+
213
+ T = TypeVar("T")
214
+
215
+
216
+ def singleton_list(x: Optional[T]) -> List[T]:
217
+ return [] if x is None else [x]
218
+
219
+
220
+ def parse_uuid_like(s: IdLike) -> UUID:
221
+ return UUID(str(s))
222
+
223
+
224
+ def parse_path_like(s: PathLike) -> Path:
225
+ return Path(s)
kleinkram/wrappers.py ADDED
@@ -0,0 +1,401 @@
1
+ """\
2
+ this file contains wrappers around core functionality
3
+
4
+ these functions are meant to be exposed to the user, they
5
+ accept a more diverse set of arguments and handle the
6
+ conversion to the internal representation
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import Collection
13
+ from typing import Dict
14
+ from typing import List
15
+ from typing import Literal
16
+ from typing import Optional
17
+ from typing import Sequence
18
+ from typing import overload
19
+
20
+ import kleinkram.api.routes
21
+ import kleinkram.core
22
+ import kleinkram.utils
23
+ from kleinkram.api.client import AuthenticatedClient
24
+ from kleinkram.api.query import FileQuery
25
+ from kleinkram.api.query import MissionQuery
26
+ from kleinkram.api.query import ProjectQuery
27
+ from kleinkram.errors import FileNameNotSupported
28
+ from kleinkram.models import File
29
+ from kleinkram.models import Mission
30
+ from kleinkram.models import Project
31
+ from kleinkram.types import IdLike
32
+ from kleinkram.types import PathLike
33
+ from kleinkram.utils import parse_path_like
34
+ from kleinkram.utils import parse_uuid_like
35
+ from kleinkram.utils import singleton_list
36
+
37
+
38
+ def _args_to_project_query(
39
+ project_names: Optional[Sequence[str]] = None,
40
+ project_ids: Optional[Sequence[IdLike]] = None,
41
+ ) -> ProjectQuery:
42
+ return ProjectQuery(
43
+ ids=[parse_uuid_like(_id) for _id in project_ids or []],
44
+ patterns=list(project_names or []),
45
+ )
46
+
47
+
48
+ def _args_to_mission_query(
49
+ mission_names: Optional[Sequence[str]] = None,
50
+ mission_ids: Optional[Sequence[IdLike]] = None,
51
+ project_names: Optional[Sequence[str]] = None,
52
+ project_ids: Optional[Sequence[IdLike]] = None,
53
+ ) -> MissionQuery:
54
+ return MissionQuery(
55
+ ids=[parse_uuid_like(_id) for _id in mission_ids or []],
56
+ patterns=list(mission_names or []),
57
+ project_query=_args_to_project_query(
58
+ project_names=project_names, project_ids=project_ids
59
+ ),
60
+ )
61
+
62
+
63
+ def _args_to_file_query(
64
+ file_names: Optional[Sequence[str]] = None,
65
+ file_ids: Optional[Sequence[IdLike]] = None,
66
+ mission_names: Optional[Sequence[str]] = None,
67
+ mission_ids: Optional[Sequence[IdLike]] = None,
68
+ project_names: Optional[Sequence[str]] = None,
69
+ project_ids: Optional[Sequence[IdLike]] = None,
70
+ ) -> FileQuery:
71
+ return FileQuery(
72
+ ids=[parse_uuid_like(_id) for _id in file_ids or []],
73
+ patterns=list(file_names or []),
74
+ mission_query=_args_to_mission_query(
75
+ mission_names=mission_names,
76
+ mission_ids=mission_ids,
77
+ project_names=project_names,
78
+ project_ids=project_ids,
79
+ ),
80
+ )
81
+
82
+
83
+ def download(
84
+ *,
85
+ file_ids: Optional[Sequence[IdLike]] = None,
86
+ file_names: Optional[Sequence[str]] = None,
87
+ mission_ids: Optional[Sequence[IdLike]] = None,
88
+ mission_names: Optional[Sequence[str]] = None,
89
+ project_ids: Optional[Sequence[IdLike]] = None,
90
+ project_names: Optional[Sequence[str]] = None,
91
+ dest: PathLike,
92
+ nested: bool = False,
93
+ overwrite: bool = False,
94
+ verbose: bool = False,
95
+ ) -> None:
96
+ query = _args_to_file_query(
97
+ file_names=file_names,
98
+ file_ids=file_ids,
99
+ mission_names=mission_names,
100
+ mission_ids=mission_ids,
101
+ project_names=project_names,
102
+ project_ids=project_ids,
103
+ )
104
+ client = AuthenticatedClient()
105
+ kleinkram.core.download(
106
+ client=client,
107
+ query=query,
108
+ base_dir=parse_path_like(dest),
109
+ nested=nested,
110
+ overwrite=overwrite,
111
+ verbose=verbose,
112
+ )
113
+
114
+
115
+ def list_files(
116
+ *,
117
+ file_ids: Optional[Sequence[IdLike]] = None,
118
+ file_names: Optional[Sequence[str]] = None,
119
+ mission_ids: Optional[Sequence[IdLike]] = None,
120
+ mission_names: Optional[Sequence[str]] = None,
121
+ project_ids: Optional[Sequence[IdLike]] = None,
122
+ project_names: Optional[Sequence[str]] = None,
123
+ ) -> List[File]:
124
+ query = _args_to_file_query(
125
+ file_names=file_names,
126
+ file_ids=file_ids,
127
+ mission_names=mission_names,
128
+ mission_ids=mission_ids,
129
+ project_names=project_names,
130
+ project_ids=project_ids,
131
+ )
132
+ client = AuthenticatedClient()
133
+ return list(kleinkram.api.routes.get_files(client, query))
134
+
135
+
136
+ def list_missions(
137
+ *,
138
+ mission_ids: Optional[Sequence[IdLike]] = None,
139
+ mission_names: Optional[Sequence[str]] = None,
140
+ project_ids: Optional[Sequence[IdLike]] = None,
141
+ project_names: Optional[Sequence[str]] = None,
142
+ ) -> List[Mission]:
143
+ query = _args_to_mission_query(
144
+ mission_names=mission_names,
145
+ mission_ids=mission_ids,
146
+ project_names=project_names,
147
+ project_ids=project_ids,
148
+ )
149
+ client = AuthenticatedClient()
150
+ return list(kleinkram.api.routes.get_missions(client, query))
151
+
152
+
153
+ def list_projects(
154
+ *,
155
+ project_ids: Optional[Sequence[IdLike]] = None,
156
+ project_names: Optional[Sequence[str]] = None,
157
+ ) -> List[Project]:
158
+ query = _args_to_project_query(
159
+ project_names=project_names,
160
+ project_ids=project_ids,
161
+ )
162
+ client = AuthenticatedClient()
163
+ return list(kleinkram.api.routes.get_projects(client, query))
164
+
165
+
166
+ @overload
167
+ def upload(
168
+ *,
169
+ mission_name: str,
170
+ project_name: str,
171
+ files: Sequence[PathLike],
172
+ create: bool = False,
173
+ fix_filenames: bool = False,
174
+ metadata: Optional[Dict[str, str]] = None,
175
+ ignore_missing_metadata: bool = False,
176
+ verbose: bool = False,
177
+ ) -> None: ...
178
+
179
+
180
+ @overload
181
+ def upload(
182
+ *,
183
+ mission_id: IdLike,
184
+ files: Sequence[PathLike],
185
+ create: Literal[False] = False,
186
+ fix_filenames: bool = False,
187
+ verbose: bool = False,
188
+ ) -> None: ...
189
+
190
+
191
+ @overload
192
+ def upload(
193
+ *,
194
+ mission_name: str,
195
+ project_id: IdLike,
196
+ files: Sequence[PathLike],
197
+ create: bool = False,
198
+ fix_filenames: bool = False,
199
+ metadata: Optional[Dict[str, str]] = None,
200
+ ignore_missing_metadata: bool = False,
201
+ verbose: bool = False,
202
+ ) -> None: ...
203
+
204
+
205
+ def upload(
206
+ *,
207
+ mission_name: Optional[str] = None,
208
+ mission_id: Optional[IdLike] = None,
209
+ project_name: Optional[str] = None,
210
+ project_id: Optional[IdLike] = None,
211
+ files: Sequence[PathLike],
212
+ create: bool = False,
213
+ fix_filenames: bool = False,
214
+ metadata: Optional[Dict[str, str]] = None,
215
+ ignore_missing_metadata: bool = False,
216
+ verbose: bool = False,
217
+ ) -> None:
218
+ parsed_file_paths = [parse_path_like(f) for f in files]
219
+ if not fix_filenames:
220
+ for file in parsed_file_paths:
221
+ if not kleinkram.utils.check_filename_is_sanatized(file.stem):
222
+ print(file.name)
223
+ raise FileNameNotSupported(
224
+ f"only `{''.join(kleinkram.utils.INTERNAL_ALLOWED_CHARS)}` are "
225
+ f"allowed in filenames and at most 50 chars: {file}"
226
+ )
227
+
228
+ query = _args_to_mission_query(
229
+ mission_names=singleton_list(mission_name),
230
+ mission_ids=singleton_list(mission_id),
231
+ project_names=singleton_list(project_name),
232
+ project_ids=singleton_list(project_id),
233
+ )
234
+ client = AuthenticatedClient()
235
+ kleinkram.core.upload(
236
+ client=client,
237
+ query=query,
238
+ file_paths=parsed_file_paths,
239
+ create=create,
240
+ metadata=metadata,
241
+ ignore_missing_metadata=ignore_missing_metadata,
242
+ verbose=verbose,
243
+ )
244
+
245
+
246
+ @overload
247
+ def verify(
248
+ *,
249
+ mission_name: str,
250
+ project_name: str,
251
+ files: Sequence[PathLike],
252
+ verbose: bool = False,
253
+ ) -> Dict[Path, kleinkram.core.FileVerificationStatus]: ...
254
+
255
+
256
+ @overload
257
+ def verify(
258
+ *,
259
+ mission_name: str,
260
+ project_id: IdLike,
261
+ files: Sequence[PathLike],
262
+ verbose: bool = False,
263
+ ) -> Dict[Path, kleinkram.core.FileVerificationStatus]: ...
264
+
265
+
266
+ @overload
267
+ def verify(
268
+ *,
269
+ mission_id: IdLike,
270
+ files: Sequence[PathLike],
271
+ verbose: bool = False,
272
+ ) -> Dict[Path, kleinkram.core.FileVerificationStatus]: ...
273
+
274
+
275
+ def verify(
276
+ *,
277
+ mission_name: Optional[str] = None,
278
+ mission_id: Optional[IdLike] = None,
279
+ project_name: Optional[str] = None,
280
+ project_id: Optional[IdLike] = None,
281
+ files: Sequence[PathLike],
282
+ skip_hash: bool = False,
283
+ verbose: bool = False,
284
+ ) -> Dict[Path, kleinkram.core.FileVerificationStatus]:
285
+ query = _args_to_mission_query(
286
+ mission_names=singleton_list(mission_name),
287
+ mission_ids=singleton_list(mission_id),
288
+ project_names=singleton_list(project_name),
289
+ project_ids=singleton_list(project_id),
290
+ )
291
+ return kleinkram.core.verify(
292
+ client=AuthenticatedClient(),
293
+ query=query,
294
+ file_paths=[parse_path_like(f) for f in files],
295
+ skip_hash=skip_hash,
296
+ verbose=verbose,
297
+ )
298
+
299
+
300
+ def create_mission(
301
+ mission_name: str,
302
+ project_id: IdLike,
303
+ metadata: Dict[str, str],
304
+ ignore_missing_metadata: bool = False,
305
+ ) -> None:
306
+ kleinkram.api.routes._create_mission(
307
+ AuthenticatedClient(),
308
+ parse_uuid_like(project_id),
309
+ mission_name,
310
+ metadata=metadata,
311
+ ignore_missing_tags=ignore_missing_metadata,
312
+ )
313
+
314
+
315
+ def create_project(project_name: str, description: str) -> None:
316
+ kleinkram.api.routes._create_project(
317
+ AuthenticatedClient(), project_name, description
318
+ )
319
+
320
+
321
+ def update_file(file_id: IdLike) -> None:
322
+ kleinkram.core.update_file(
323
+ client=AuthenticatedClient(), file_id=parse_uuid_like(file_id)
324
+ )
325
+
326
+
327
+ def update_mission(mission_id: IdLike, metadata: Dict[str, str]) -> None:
328
+ kleinkram.core.update_mission(
329
+ client=AuthenticatedClient(),
330
+ mission_id=parse_uuid_like(mission_id),
331
+ metadata=metadata,
332
+ )
333
+
334
+
335
+ def update_project(project_id: IdLike, description: Optional[str] = None) -> None:
336
+ kleinkram.core.update_project(
337
+ client=AuthenticatedClient(),
338
+ project_id=parse_uuid_like(project_id),
339
+ description=description,
340
+ )
341
+
342
+
343
+ def delete_files(file_ids: Collection[IdLike]) -> None:
344
+ """\
345
+ delete multiple files by their ids
346
+ """
347
+ kleinkram.core.delete_files(
348
+ client=AuthenticatedClient(),
349
+ file_ids=[parse_uuid_like(_id) for _id in file_ids],
350
+ )
351
+
352
+
353
+ def delete_file(file_id: IdLike) -> None:
354
+ """\
355
+ delete a single file by id
356
+ """
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
+ )
363
+
364
+
365
+ def delete_mission(mission_id: IdLike) -> None:
366
+ kleinkram.core.delete_mission(
367
+ client=AuthenticatedClient(), mission_id=parse_uuid_like(mission_id)
368
+ )
369
+
370
+
371
+ def delete_project(project_id: IdLike) -> None:
372
+ kleinkram.core.delete_project(
373
+ client=AuthenticatedClient(), project_id=parse_uuid_like(project_id)
374
+ )
375
+
376
+
377
+ def get_file(file_id: IdLike) -> File:
378
+ """\
379
+ get a file by its id
380
+ """
381
+ return kleinkram.api.routes.get_file(
382
+ AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)])
383
+ )
384
+
385
+
386
+ def get_mission(mission_id: IdLike) -> Mission:
387
+ """\
388
+ get a mission by its id
389
+ """
390
+ return kleinkram.api.routes.get_mission(
391
+ AuthenticatedClient(), MissionQuery(ids=[parse_uuid_like(mission_id)])
392
+ )
393
+
394
+
395
+ def get_project(project_id: IdLike) -> Project:
396
+ """\
397
+ get a project by its id
398
+ """
399
+ return kleinkram.api.routes.get_project(
400
+ AuthenticatedClient(), ProjectQuery(ids=[parse_uuid_like(project_id)])
401
+ )
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: kleinkram
3
- Version: 0.38.1.dev20241212075157
3
+ Version: 0.38.1.dev20250207122632
4
4
  Summary: give me your bags
5
5
  Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
6
6
  Classifier: Programming Language :: Python :: 3
@@ -13,10 +13,10 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: Implementation :: CPython
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
16
  Requires-Dist: boto3
18
17
  Requires-Dist: botocore
19
18
  Requires-Dist: httpx
19
+ Requires-Dist: python-dateutil
20
20
  Requires-Dist: pyyaml
21
21
  Requires-Dist: rich
22
22
  Requires-Dist: tqdm
@@ -0,0 +1,49 @@
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=miNMmUu1XjT9DMNQu8BQoK2ygfUmXrnUV4D0zR156d4,2968
5
+ kleinkram/config.py,sha256=3FuIpq65SL0phetvOJftvG6VOAZ4RjnvLkcFX6pwuDY,5452
6
+ kleinkram/core.py,sha256=Q7OYIKPN9K6kxf9Eq7r5XRHPJ3RtT7SBZp_3_CS8yuY,8429
7
+ kleinkram/errors.py,sha256=4mygNxkf6IBgaiRWY95qu0v6z4TAXA3G6CUcXC9FU3s,772
8
+ kleinkram/main.py,sha256=BTE0mZN__xd46wBhFi6iBlK9eGGQvJ1LdUMsbnysLi0,172
9
+ kleinkram/models.py,sha256=8nJlPrKVLSmehspeuQSFV6nUo76JzehUn6KIZYH1xy4,1832
10
+ kleinkram/printing.py,sha256=fgSlfRaGqQ7dNiIZGMvEPxMatmUL3MrCvh2ibrz9b_s,9914
11
+ kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
13
+ kleinkram/utils.py,sha256=6HFqTw7-eqDEjNG_PsVEQNMNK-RWOqPsoiZI5SK8F7Q,6270
14
+ kleinkram/wrappers.py,sha256=4xXU43eNnvMG2sssU330MmTLSSRdurOpnZ-zNGOGmt0,11342
15
+ kleinkram/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ kleinkram/api/client.py,sha256=t6pWY29bQi36IhfIy98Ijc-w-3LzcBTJxXprfPIdwJo,3614
17
+ kleinkram/api/deser.py,sha256=-eP0haBAFr-dRWJ1v-P5o_rxA8vOBlZMtAGXW8ItIAk,4870
18
+ kleinkram/api/file_transfer.py,sha256=VbVQh6F7r81207OIx8zwnRGhA6SpXmzBhJQHQgR8tso,12982
19
+ kleinkram/api/pagination.py,sha256=P_zPsBKlMWkmAv-YfUNHaGW-XLB_4U8BDMrKyiDFIXk,1370
20
+ kleinkram/api/query.py,sha256=gn5yf-eRB_Bcw2diLjt66yQtorrZMKdj5_oNA_oOhvc,3281
21
+ kleinkram/api/routes.py,sha256=xtNZOwrcjcePSrrx7tY0bCB_i6m9z2Gtd8rvrMfxpnM,12111
22
+ kleinkram/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ kleinkram/cli/_download.py,sha256=H4YlXJkZE4Md02nzgrO_i8Hsm4ZIejPsxBEKkcn4KHs,2371
24
+ kleinkram/cli/_endpoint.py,sha256=oY0p4bnuHLEDJCXtTmir4AHswcKAygZ8I4IWC3RFcKc,1796
25
+ kleinkram/cli/_list.py,sha256=5gI3aIUeKC0_eWPQqdFXSBBFvpkTTJSm31TamHa197c,3090
26
+ kleinkram/cli/_mission.py,sha256=zDFnOozOFckpuREFgIPt1IzG5q3b1bsNxYlWQoHoz5A,5301
27
+ kleinkram/cli/_project.py,sha256=N0C96NC_onCEwTteYp2wgkkwkdJt-1q43LFdqNXfjC8,3398
28
+ kleinkram/cli/_upload.py,sha256=gOhbjbmqhmwW7p6bWlSvI53vLHvBFO9QqD1kdU92I2k,2813
29
+ kleinkram/cli/_verify.py,sha256=0ABVa4U_WzaV36ClR8NsOIG7KAMRlnFmsbtnHhbWVj4,1742
30
+ kleinkram/cli/app.py,sha256=pBPv_rDVer5aCelLLfBBqEofSm0yDB3GOBT6qVkd2vw,6539
31
+ kleinkram/cli/error_handling.py,sha256=wK3tzeKVSrZm-xmiyzGLnGT2E4TRpyxhaak6GWGP7P8,1921
32
+ testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ testing/backend_fixtures.py,sha256=79gB_s9cun3B1KGUA0YFcga5rN-rm_9zTRt1HajxRbI,1590
34
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ tests/conftest.py,sha256=5MLYQOtQoXWl0TRkYntYKNdqpd4hl9m0XTRi5OXanYI,104
36
+ tests/test_config.py,sha256=gfQUjwJb2rfvrNbi7cfkNwLr_DFn4mWMM29l3bunsBk,3269
37
+ tests/test_core.py,sha256=JbzB05LWmaaP77uXeTOQtCJD2AJT0zO9zhDfcZ3GNH8,5139
38
+ tests/test_end_to_end.py,sha256=kIY62viZk2_d5HGt4GVNTkDjR0f1IkAv9OJ8HSqcBG8,3172
39
+ tests/test_error_handling.py,sha256=qPSMKF1qsAHyUME0-krxbIrk38iGKkhAyAah-KwN4NE,1300
40
+ tests/test_fixtures.py,sha256=UlPmGbEsGvrDPsaStGMRjNvrVPGjCqOB0RMfLJq2VRA,1071
41
+ tests/test_printing.py,sha256=qCr04OJVl5ouht9FoeWGKOi8MZXevVV1EDghzV1JaMc,1903
42
+ tests/test_query.py,sha256=fExmCKXLA7-9j2S2sF_sbvRX_2s6Cp3a7OTcqE25q9g,3864
43
+ tests/test_utils.py,sha256=eUBYrn3xrcgcaxm1X4fqZaX4tRvkbI6rh6BUbNbu9T0,4784
44
+ tests/test_wrappers.py,sha256=TbcTyO2L7fslbzgfDdcVZkencxNQ8cGPZm_iB6c9d6Q,2673
45
+ kleinkram-0.38.1.dev20250207122632.dist-info/METADATA,sha256=iRYZsQuqJldM_lQrNcC3EBuUuut-8VX77uNt9Z-2rR0,2333
46
+ kleinkram-0.38.1.dev20250207122632.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
47
+ kleinkram-0.38.1.dev20250207122632.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
48
+ kleinkram-0.38.1.dev20250207122632.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
49
+ kleinkram-0.38.1.dev20250207122632.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
testing/__init__.py ADDED
File without changes