kleinkram 0.41.1.dev20250303132129__tar.gz → 0.41.2__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.

Files changed (57) hide show
  1. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/PKG-INFO +1 -1
  2. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/client.py +31 -9
  3. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/core.py +7 -0
  4. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/errors.py +11 -5
  5. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/printing.py +1 -1
  6. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/PKG-INFO +1 -1
  7. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/setup.cfg +1 -1
  8. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_core.py +13 -0
  9. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/README.md +0 -0
  10. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/__init__.py +0 -0
  11. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/__main__.py +0 -0
  12. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/_version.py +0 -0
  13. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/__init__.py +0 -0
  14. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/deser.py +0 -0
  15. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/file_transfer.py +0 -0
  16. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/pagination.py +0 -0
  17. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/query.py +0 -0
  18. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/api/routes.py +0 -0
  19. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/auth.py +0 -0
  20. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/__init__.py +0 -0
  21. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_download.py +0 -0
  22. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_endpoint.py +0 -0
  23. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_file.py +0 -0
  24. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_list.py +0 -0
  25. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_mission.py +0 -0
  26. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_project.py +0 -0
  27. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_upload.py +0 -0
  28. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/_verify.py +0 -0
  29. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/app.py +0 -0
  30. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/cli/error_handling.py +0 -0
  31. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/config.py +0 -0
  32. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/main.py +0 -0
  33. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/models.py +0 -0
  34. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/py.typed +0 -0
  35. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/types.py +0 -0
  36. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/utils.py +0 -0
  37. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram/wrappers.py +0 -0
  38. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/SOURCES.txt +0 -0
  39. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/dependency_links.txt +0 -0
  40. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/entry_points.txt +0 -0
  41. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/requires.txt +0 -0
  42. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/kleinkram.egg-info/top_level.txt +0 -0
  43. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/pyproject.toml +0 -0
  44. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/requirements.txt +0 -0
  45. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/setup.py +0 -0
  46. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/testing/__init__.py +0 -0
  47. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/testing/backend_fixtures.py +0 -0
  48. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/__init__.py +0 -0
  49. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/conftest.py +0 -0
  50. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_config.py +0 -0
  51. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_end_to_end.py +0 -0
  52. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_error_handling.py +0 -0
  53. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_fixtures.py +0 -0
  54. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_printing.py +0 -0
  55. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_query.py +0 -0
  56. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_utils.py +0 -0
  57. {kleinkram-0.41.1.dev20250303132129 → kleinkram-0.41.2}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kleinkram
3
- Version: 0.41.1.dev20250303132129
3
+ Version: 0.41.2
4
4
  Summary: give me your bags
5
5
  Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
6
6
  Classifier: Programming Language :: Python :: 3
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  from collections import abc
5
+ from pathlib import Path
5
6
  from threading import Lock
6
7
  from typing import Any
7
8
  from typing import List
@@ -13,6 +14,9 @@ from typing import Union
13
14
  import httpx
14
15
  from httpx._types import PrimitiveData
15
16
 
17
+ import kleinkram.errors
18
+ from kleinkram._version import __version__
19
+ from kleinkram.config import CONFIG_PATH
16
20
  from kleinkram.config import Config
17
21
  from kleinkram.config import Credentials
18
22
  from kleinkram.config import get_config
@@ -26,6 +30,8 @@ COOKIE_AUTH_TOKEN = "authtoken"
26
30
  COOKIE_REFRESH_TOKEN = "refreshtoken"
27
31
  COOKIE_API_KEY = "clikey"
28
32
 
33
+ CLI_VERSION_HEADER = "Kleinkram-Client-Version"
34
+
29
35
 
30
36
  Data = Union[PrimitiveData, Any]
31
37
  NestedData = Mapping[str, Data]
@@ -65,10 +71,12 @@ class AuthenticatedClient(httpx.Client):
65
71
  _config: Config
66
72
  _config_lock: Lock
67
73
 
68
- def __init__(self, *args: Any, **kwargs: Any) -> None:
74
+ def __init__(
75
+ self, config_path: Path = CONFIG_PATH, *args: Any, **kwargs: Any
76
+ ) -> None:
69
77
  super().__init__(*args, **kwargs)
70
78
 
71
- self._config = get_config()
79
+ self._config = get_config(path=config_path)
72
80
  self._config_lock = Lock()
73
81
 
74
82
  if self._config.credentials is None:
@@ -95,9 +103,7 @@ class AuthenticatedClient(httpx.Client):
95
103
  self.cookies.set(COOKIE_REFRESH_TOKEN, refresh_token)
96
104
 
97
105
  logger.info("refreshing token...")
98
- response = self.post(
99
- "/auth/refresh-token",
100
- )
106
+ response = self.post("/auth/refresh-token")
101
107
  response.raise_for_status()
102
108
  new_access_token = response.cookies[COOKIE_AUTH_TOKEN]
103
109
  creds = Credentials(auth_token=new_access_token, refresh_token=refresh_token)
@@ -110,6 +116,22 @@ class AuthenticatedClient(httpx.Client):
110
116
 
111
117
  self.cookies.set(COOKIE_AUTH_TOKEN, new_access_token)
112
118
 
119
+ def _send_request_with_kleinkram_headers(
120
+ self, *args: Any, **kwargs: Any
121
+ ) -> httpx.Response:
122
+ # add the cli version to the headers
123
+ headers = kwargs.get("headers") or {}
124
+ headers.setdefault(CLI_VERSION_HEADER, __version__)
125
+ kwargs["headers"] = headers
126
+
127
+ # send the request
128
+ response = super().request(*args, **kwargs)
129
+
130
+ # check version compatibility
131
+ if response.status_code == 426:
132
+ raise kleinkram.errors.UpdateCLIVersion
133
+ return response
134
+
113
135
  def request(
114
136
  self,
115
137
  method: str,
@@ -128,7 +150,7 @@ class AuthenticatedClient(httpx.Client):
128
150
  logger.info(f"requesting {method} {full_url}")
129
151
 
130
152
  httpx_params = _convert_query_params_to_httpx_format(params or {})
131
- response = super().request(
153
+ response = self._send_request_with_kleinkram_headers(
132
154
  method, full_url, params=httpx_params, *args, **kwargs
133
155
  )
134
156
 
@@ -148,10 +170,10 @@ class AuthenticatedClient(httpx.Client):
148
170
  raise NotAuthenticated
149
171
 
150
172
  logger.info(f"retrying request {method} {full_url}")
151
- resp = super().request(
173
+ response = self._send_request_with_kleinkram_headers(
152
174
  method, full_url, params=httpx_params, *args, **kwargs
153
175
  )
154
- logger.info(f"got response {resp}")
155
- return resp
176
+ logger.info(f"got response {response}")
177
+ return response
156
178
  else:
157
179
  return response
@@ -210,6 +210,10 @@ def delete_files(*, client: AuthenticatedClient, file_ids: Collection[UUID]) ->
210
210
  """\
211
211
  deletes multiple files accross multiple missions
212
212
  """
213
+ if not file_ids:
214
+ return
215
+
216
+ # we need to check that file_ids is not empty, otherwise this is bad
213
217
  files = list(kleinkram.api.routes.get_files(client, FileQuery(ids=list(file_ids))))
214
218
 
215
219
  # check if all file_ids were actually found
@@ -220,6 +224,9 @@ def delete_files(*, client: AuthenticatedClient, file_ids: Collection[UUID]) ->
220
224
  f"file {file_id} not found, did not delete any files"
221
225
  )
222
226
 
227
+ # to prevent catastrophic mistakes from happening *again*
228
+ assert set(file_ids) == set([file.id for file in files]), "unreachable"
229
+
223
230
  # we can only batch delete files within the same mission
224
231
  missions_to_files: Dict[UUID, List[UUID]] = {}
225
232
  for file in files:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  LOGIN_MESSAGE = "Please login using `klein login`."
4
+ UPDATE_MESSAGE = "Please update your CLI using `pip install --upgrade kleinkram`."
4
5
 
5
6
 
6
7
  class ParsingError(Exception): ...
@@ -33,11 +34,6 @@ class FileNotFound(Exception): ...
33
34
  class AccessDenied(Exception): ...
34
35
 
35
36
 
36
- class NotAuthenticated(Exception):
37
- def __init__(self) -> None:
38
- super().__init__(LOGIN_MESSAGE)
39
-
40
-
41
37
  class InvalidCLIVersion(Exception): ...
42
38
 
43
39
 
@@ -48,3 +44,13 @@ class FileNameNotSupported(Exception): ...
48
44
 
49
45
 
50
46
  class InvalidMissionMetadata(Exception): ...
47
+
48
+
49
+ class NotAuthenticated(Exception):
50
+ def __init__(self) -> None:
51
+ super().__init__(LOGIN_MESSAGE)
52
+
53
+
54
+ class UpdateCLIVersion(Exception):
55
+ def __init__(self) -> None:
56
+ super().__init__(UPDATE_MESSAGE)
@@ -273,7 +273,7 @@ def project_info_table(project: Project) -> Table:
273
273
 
274
274
 
275
275
  def file_verification_status_table(
276
- file_status: Mapping[Path, FileVerificationStatus]
276
+ file_status: Mapping[Path, FileVerificationStatus],
277
277
  ) -> Table:
278
278
  table = Table(title="file status")
279
279
  table.add_column("filename", style="cyan")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kleinkram
3
- Version: 0.41.1.dev20250303132129
3
+ Version: 0.41.2
4
4
  Summary: give me your bags
5
5
  Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = kleinkram
3
- version = 0.41.1-dev20250303132129
3
+ version = 0.41.2
4
4
  description = give me your bags
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -77,6 +77,19 @@ def test_delete_existing_files(mission):
77
77
  assert not list_files(mission_ids=[mission.id], file_names=["*.bag"])
78
78
 
79
79
 
80
+ @pytest.mark.slow
81
+ def test_delete_working_as_expected_when_passing_empty_list(mission):
82
+ client = AuthenticatedClient()
83
+
84
+ # we need to filter by *.bag to not get flakyness due to conversion
85
+ n_files = len(list_files(mission_ids=[mission.id], file_names=["*.bag"]))
86
+ kleinkram.core.delete_files(client=client, file_ids=[])
87
+ n_files_after_delete = len(
88
+ list_files(mission_ids=[mission.id], file_names=["*.bag"])
89
+ )
90
+ assert n_files == n_files_after_delete
91
+
92
+
80
93
  @pytest.mark.slow
81
94
  def test_delete_non_existing_files():
82
95
  client = AuthenticatedClient()