kleinkram 0.21.7__tar.gz → 0.21.8.dev20240920104547__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 (24) hide show
  1. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/PKG-INFO +1 -1
  2. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/pyproject.toml +1 -1
  3. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/helper.py +16 -1
  4. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/main.py +64 -23
  5. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/.gitignore +0 -0
  6. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/LICENSE +0 -0
  7. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/README.md +0 -0
  8. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/deploy.sh +0 -0
  9. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/dev.sh +0 -0
  10. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/requirements.txt +0 -0
  11. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/klein.py +0 -0
  12. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/__init__.py +0 -0
  13. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/api_client.py +0 -0
  14. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/auth/auth.py +0 -0
  15. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/consts.py +0 -0
  16. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/endpoint/endpoint.py +0 -0
  17. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/error_handling.py +0 -0
  18. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/file/file.py +0 -0
  19. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/mission/mission.py +0 -0
  20. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/project/project.py +0 -0
  21. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/queue/queue.py +0 -0
  22. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/tag/tag.py +0 -0
  23. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/topic/topic.py +0 -0
  24. {kleinkram-0.21.7 → kleinkram-0.21.8.dev20240920104547}/src/kleinkram/user/user.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kleinkram
3
- Version: 0.21.7
3
+ Version: 0.21.8.dev20240920104547
4
4
  Summary: A CLI for the ETH project kleinkram
5
5
  Project-URL: Homepage, https://github.com/leggedrobotics/kleinkram
6
6
  Project-URL: Issues, https://github.com/leggedrobotics/kleinkram/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "kleinkram"
7
- version = "0.21.7"
7
+ version = "0.21.8-dev20240920104547"
8
8
  description = "A CLI for the ETH project kleinkram"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
@@ -3,6 +3,8 @@ import os
3
3
  import queue
4
4
  import threading
5
5
  from functools import partial
6
+
7
+ from botocore.config import Config
6
8
  from typing_extensions import Dict
7
9
  import boto3
8
10
 
@@ -114,7 +116,12 @@ def uploadFiles(files: Dict[str, str], credentials: Dict[str, str], nrThreads: i
114
116
  minio_endpoint = "http://localhost:9000"
115
117
  else:
116
118
  minio_endpoint = api_endpoint.replace("api", "minio")
117
- s3 = session.resource("s3", endpoint_url=minio_endpoint)
119
+
120
+ config = Config(retries = {
121
+ 'max_attempts': 10,
122
+ 'mode': 'standard'
123
+ })
124
+ s3 = session.resource("s3", endpoint_url=minio_endpoint, config=config)
118
125
 
119
126
  _queue = queue.Queue()
120
127
  for file in files.items():
@@ -161,6 +168,14 @@ def uploadFile(_queue: queue.Queue, s3: BaseClient, transferCallback: TransferCa
161
168
  print(f"Error uploading {filename}: {e}")
162
169
  _queue.task_done()
163
170
 
171
+ def canUploadMission(client: AuthenticatedClient, project_uuid: str):
172
+ permissions = client.get("/user/permissions")
173
+ permissions.raise_for_status()
174
+ permissions_json = permissions.json()
175
+ for_project = filter(lambda x: x["uuid"] == project_uuid, permissions_json["projects"])
176
+ max_for_project = max(map(lambda x: x["access"], for_project))
177
+ return max_for_project >= 10
178
+
164
179
 
165
180
  if __name__ == "__main__":
166
181
  res = expand_and_match(
@@ -9,7 +9,7 @@ from rich import print
9
9
  from rich.table import Table
10
10
  from typer.core import TyperGroup
11
11
  from typer.models import Context
12
- from typing_extensions import Annotated, List
12
+ from typing_extensions import Annotated, List, Optional
13
13
 
14
14
  from kleinkram.api_client import AuthenticatedClient
15
15
  from kleinkram.auth.auth import login, setCliKey, logout
@@ -22,7 +22,7 @@ from kleinkram.queue.queue import queue
22
22
  from kleinkram.tag.tag import tag
23
23
  from kleinkram.topic.topic import topic
24
24
  from kleinkram.user.user import user
25
- from .helper import uploadFiles, expand_and_match
25
+ from .helper import uploadFiles, expand_and_match, canUploadMission
26
26
 
27
27
 
28
28
  class CommandPanel(str, Enum):
@@ -75,14 +75,14 @@ app = ErrorHandledTyper(
75
75
 
76
76
  @app.callback()
77
77
  def version(
78
- version: bool = typer.Option(
79
- None,
80
- "--version",
81
- "-v",
82
- callback=version_callback,
83
- is_eager=True,
84
- help="Print the version and exit",
85
- )
78
+ version: bool = typer.Option(
79
+ None,
80
+ "--version",
81
+ "-v",
82
+ callback=version_callback,
83
+ is_eager=True,
84
+ help="Print the version and exit",
85
+ )
86
86
  ):
87
87
  pass
88
88
 
@@ -109,20 +109,21 @@ def download():
109
109
 
110
110
  @app.command("upload", rich_help_panel=CommandPanel.CoreCommands)
111
111
  def upload(
112
- path: Annotated[
113
- str, typer.Option(prompt=True, help="Path to files to upload, Regex supported")
114
- ],
115
- project: Annotated[str, typer.Option(prompt=True, help="Name of Project")],
116
- mission: Annotated[
117
- str, typer.Option(prompt=True, help="Name of Mission to create")
118
- ],
112
+ path: Annotated[
113
+ str, typer.Option(prompt=True, help="Path to files to upload, Regex supported")
114
+ ],
115
+ project: Annotated[str, typer.Option(prompt=True, help="Name of Project")],
116
+ mission: Annotated[
117
+ str, typer.Option(prompt=True, help="Name of Mission to create")
118
+ ],
119
+ tags: Annotated[Optional[List[str]], typer.Option(prompt=False, help="Tags to add to the mission")] = None,
119
120
  ):
120
121
  """
121
122
  Upload files matching the path to a mission in a project.
122
123
 
123
124
  The mission name must be unique within the project and not yet created.\n
124
125
  Examples:\n
125
- - 'klein upload --path "~/data/**/*.bag" --project "Project 1" --mission "Mission 1"'\n
126
+ - 'klein upload --path "~/data/**/*.bag" --project "Project 1" --mission "Mission 1" --tags "0700946d-1d6a-4520-b263-0e177f49c35b:LEE-H" --tags "1565118d-593c-4517-8c2d-9658452d9319:Dodo"'\n
126
127
 
127
128
  """
128
129
  files = expand_and_match(path)
@@ -147,7 +148,6 @@ def upload(
147
148
  get_project_url = "/project/byName"
148
149
  project_response = client.get(get_project_url, params={"name": project})
149
150
  if project_response.status_code >= 400:
150
-
151
151
  raise AccessDeniedException(
152
152
  f"The project '{project}' does not exist or you do not have access to it.\n"
153
153
  f"Consider using the following command to create a project: 'klein project create'\n",
@@ -159,6 +159,42 @@ def upload(
159
159
  print(f"Project not found: '{project}'")
160
160
  return
161
161
 
162
+ can_upload = canUploadMission(client, project_json['uuid'])
163
+ if not can_upload:
164
+ raise AccessDeniedException(
165
+ f"You do not have the required permissions to upload to project '{project}'\n", "Access Denied")
166
+
167
+ if not tags:
168
+ tags = []
169
+ tags_dict = {item.split(":")[0]: item.split(":")[1] for item in tags}
170
+
171
+ for required_tag in project_json["requiredTags"]:
172
+ if required_tag["name"] not in tags_dict:
173
+ while True:
174
+ if required_tag["datatype"] in ["LOCATION", "STRING", "LINK"]:
175
+ tag_value = typer.prompt("Provide value for required tag " + required_tag["name"])
176
+ if tag_value != "":
177
+ break
178
+ elif required_tag["datatype"] == "BOOLEAN":
179
+ tag_value = typer.confirm("Provide (y/N) for required tag " + required_tag["name"])
180
+ break
181
+ elif required_tag["datatype"] == "NUMBER":
182
+ tag_value = typer.prompt("Provide number for required tag " + required_tag["name"])
183
+ try:
184
+ tag_value = float(tag_value)
185
+ break
186
+ except ValueError:
187
+ typer.echo("Invalid number format. Please provide a number.")
188
+ elif required_tag["datatype"] == "DATE":
189
+ tag_value = typer.prompt("Provide date for required tag " + required_tag["name"])
190
+ try:
191
+ tag_value = datetime.strptime(tag_value, "%Y-%m-%d %H:%M:%S")
192
+ break
193
+ except ValueError:
194
+ print("Invalid date format. Please use 'YYYY-MM-DD HH:MM:SS'")
195
+
196
+ tags_dict[required_tag["uuid"]] = tag_value
197
+
162
198
  get_mission_url = "/mission/byName"
163
199
  mission_response = client.get(get_mission_url, params={"name": mission})
164
200
  mission_response.raise_for_status()
@@ -174,18 +210,23 @@ def upload(
174
210
  create_mission_url = "/mission/create"
175
211
  new_mission = client.post(
176
212
  create_mission_url,
177
- json={"name": mission, "projectUUID": project_json["uuid"], "tags": []},
213
+ json={"name": mission, "projectUUID": project_json["uuid"], "tags": tags_dict},
178
214
  )
179
- new_mission.raise_for_status()
215
+ if new_mission.status_code >= 400:
216
+ raise ValueError(
217
+ "Failed to create mission. Status Code: " + str(new_mission.status_code) + "\n" + new_mission.json()[
218
+ "message"])
180
219
  new_mission_data = new_mission.json()
181
220
 
182
221
  get_temporary_credentials = "/file/temporaryAccess"
183
-
184
222
  response_2 = client.post(
185
223
  get_temporary_credentials,
186
224
  json={"filenames": filenames, "missionUUID": new_mission_data["uuid"]},
187
225
  )
188
- response_2.raise_for_status()
226
+ if response_2.status_code >= 400:
227
+ raise ValueError(
228
+ "Failed to get temporary credentials. Status Code: " + str(response_2.status_code) + "\n" + response_2.json()[
229
+ "message"])
189
230
  temp_credentials = response_2.json()
190
231
  credential = temp_credentials["credentials"]
191
232
  confirmed_files = temp_credentials["files"]