kleinkram 0.0.117__py3-none-any.whl → 0.0.118__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.

kleinkram/auth.py CHANGED
@@ -3,6 +3,8 @@ import os
3
3
  import urllib.parse
4
4
  import webbrowser
5
5
  from http.server import BaseHTTPRequestHandler, HTTPServer
6
+ from typing import Optional
7
+
6
8
  from typing_extensions import Annotated
7
9
 
8
10
  from pathlib import Path
@@ -139,9 +141,13 @@ client = AuthenticatedClient()
139
141
 
140
142
 
141
143
  def login(
142
- key: Annotated[str, typer.Option()] = None,
143
- open_browser: Annotated[bool, typer.Option()] = True,
144
+ key: Optional[str] = typer.Option(None, help="CLI Key", hidden=True),
145
+ open_browser: Optional[bool] = typer.Option(True, help="Open browser for authentication"),
144
146
  ):
147
+ """
148
+ Login into the currently set endpoint.\n
149
+ By default, it will open the browser for authentication. On machines without a browser, you can manually open the URL provided and paste the tokens back.
150
+ """
145
151
  tokenfile = TokenFile()
146
152
  if key:
147
153
  tokenfile.saveTokens(key)
@@ -181,7 +187,18 @@ def login(
181
187
  return
182
188
 
183
189
 
184
- def setEndpoint(endpoint: Annotated[str, typer.Argument()]):
190
+ def setEndpoint(
191
+ endpoint: Optional[str] = typer.Argument(None, help="API endpoint to use")
192
+ ):
193
+ """
194
+ Set the current endpoint
195
+
196
+ Use this command to switch between different API endpoints.\n
197
+ Standard endpoints are:\n
198
+ - http://localhost:3000\n
199
+ - https://api.datasets.leggedrobotics.com\n
200
+ - https://api.datasets.dev.leggedrobotics.com
201
+ """
185
202
  tokenfile = TokenFile()
186
203
  tokenfile.endpoint = endpoint
187
204
  tokenfile.writeToFile()
@@ -191,14 +208,27 @@ def setEndpoint(endpoint: Annotated[str, typer.Argument()]):
191
208
 
192
209
 
193
210
  def endpoint():
211
+ """
212
+ Get the current endpoint
213
+
214
+ Also displays all endpoints with saved tokens.
215
+ """
194
216
  tokenfile = TokenFile()
195
217
  print("Current: " + tokenfile.endpoint)
196
218
  print("Saved Tokens found for:")
197
219
  for _endpoint, _ in tokenfile.tokens.items():
198
- print(_endpoint)
220
+ print("- " + _endpoint)
199
221
 
200
222
 
201
- def setCliKey(key: Annotated[str, typer.Argument()]):
223
+ def setCliKey(
224
+ key: Annotated[str, typer.Argument(help="CLI Key")]
225
+ ):
226
+ """
227
+ Set the CLI key (Actions Only)
228
+
229
+ Same as login with the --key option.
230
+ Should never be used by the user, only in docker containers launched from within Kleinkram.
231
+ """
202
232
  tokenfile = TokenFile()
203
233
  if not tokenfile.endpoint in tokenfile.tokens:
204
234
  tokenfile.tokens[tokenfile.endpoint] = {}
kleinkram/consts.py CHANGED
@@ -1 +1,2 @@
1
- API_URL='https://api.datasets.leggedrobotics.com'
1
+ API_URL = "http://localhost:3000"
2
+ # API_URL = "https://api.datasets.leggedrobotics.com"
kleinkram/main.py CHANGED
@@ -14,14 +14,14 @@ from .helper import uploadFiles, expand_and_match
14
14
  from .auth import login, client, endpoint, setCliKey, setEndpoint
15
15
 
16
16
  app = typer.Typer()
17
- projects = typer.Typer(name="projects")
18
- missions = typer.Typer(name="missions")
19
- files = typer.Typer(name="files")
20
- topics = typer.Typer(name="topics")
21
- queue = typer.Typer(name="queue")
22
- user = typer.Typer(name="users")
23
- tagtypes = typer.Typer(name="tagtypes")
24
- tag = typer.Typer(name="tag")
17
+ projects = typer.Typer(name="projects", help="Project operations")
18
+ missions = typer.Typer(name="missions", help="Mission operations")
19
+ files = typer.Typer(name="files", help="File operations")
20
+ topics = typer.Typer(name="topics", help="Topic operations")
21
+ queue = typer.Typer(name="queue", help="Queue operations")
22
+ user = typer.Typer(name="users", help="User operations")
23
+ tagtypes = typer.Typer(name="tagtypes", help="TagType operations")
24
+ tag = typer.Typer(name="tag", help="Tag operations")
25
25
 
26
26
 
27
27
  app.add_typer(projects)
@@ -35,27 +35,38 @@ app.add_typer(tag)
35
35
  app.command()(login)
36
36
  app.command()(endpoint)
37
37
  app.command()(setEndpoint)
38
- app.command()(setCliKey)
38
+ app.command(hidden=True)(setCliKey)
39
39
 
40
40
 
41
41
  @files.command("list")
42
42
  def list_files(
43
- project: Annotated[str, typer.Option()] = None,
44
- mission: Annotated[str, typer.Option()] = None,
45
- topics: Annotated[List[str], typer.Option()] = None,
43
+ project: Optional[str] = typer.Option(None, help="Name of Project"),
44
+ mission: Optional[str] = typer.Option(None, help="Name of Mission"),
45
+ topics: Optional[List[str]] = typer.Option(None, help="Comma separated list of topics")
46
46
  ):
47
47
  """
48
48
  List all files with optional filters for project, mission, or topics.
49
+
50
+ Can list files of a project, mission, or with specific topics (Logical AND).
51
+ Examples:\n
52
+ - 'klein files list'\n
53
+ - 'klein files list --project "Project 1"'\n
54
+ - 'klein files list --mission "Mission 1"'\n
55
+ - 'klein files list --topics "/elevation_mapping/semantic_map,/elevation_mapping/elevation_map_raw"'\n
56
+ - 'klein files list --topics "/elevation_mapping/semantic_map,/elevation_mapping/elevation_map_raw" --mission "Mission A"'
49
57
  """
50
58
  try:
51
59
  url = f"/file/filteredByNames"
60
+ params = {}
61
+ if project:
62
+ params["projectName"] = project
63
+ if mission:
64
+ params["missionName"] = mission
65
+ if topics:
66
+ params["topics"] = topics
52
67
  response = client.get(
53
68
  url,
54
- params={
55
- "projectName": project,
56
- "missionName": mission,
57
- "topics": topics,
58
- },
69
+ params=params,
59
70
  )
60
71
  response.raise_for_status()
61
72
  data = response.json()
@@ -104,11 +115,12 @@ def list_projects():
104
115
 
105
116
  @missions.command("list")
106
117
  def list_missions(
107
- project: Annotated[str, typer.Option()] = None,
108
- verbose: Annotated[bool, typer.Option()] = False,
118
+ project: Optional[str] = typer.Option(None, help="Name of Project"),
119
+ verbose: Optional[bool] = typer.Option(False, help="Outputs a table with more information"),
109
120
  ):
110
121
  """
111
122
  List all missions with optional filter for project.
123
+
112
124
  """
113
125
  try:
114
126
  url = "/mission"
@@ -152,26 +164,47 @@ def list_missions(
152
164
  @missions.command("byUUID")
153
165
  def mission_by_uuid(
154
166
  uuid: Annotated[str, typer.Argument()],
167
+ json: Optional[bool] = typer.Option(False, help="Output as JSON"),
155
168
  ):
169
+ """
170
+ Get mission name, project name, creator and table of its files given a Mission UUID
171
+
172
+ Use the JSON flag to output the full JSON response instead.
173
+
174
+ Can be run with API Key or with login.
175
+ """
156
176
  try:
157
177
  url = "/mission/byUUID"
158
178
  response = client.get(url, params={"uuid": uuid})
159
179
  response.raise_for_status()
160
180
  data = response.json()
161
- print(f"mission: {data['name']}")
162
- print(f"Creator: {data['creator']['name']}")
163
- table = Table("Filename", "Size", "date")
164
- for file in data["files"]:
165
- table.add_row(file["filename"], f"{file['size']}", file["date"])
181
+ if json:
182
+ print(data)
183
+ else:
184
+ print(f"mission: {data['name']}")
185
+ print(f"Creator: {data['creator']['name']}")
186
+ print("Project: " + data["project"]["name"])
187
+ table = Table("Filename", "Size", "date")
188
+ for file in data["files"]:
189
+ table.add_row(file["filename"], f"{file['size']}", file["date"])
190
+ print(table)
166
191
  except httpx.HTTPError as e:
167
192
  print(f"Failed to fetch missions: {e}")
168
193
 
169
194
 
170
195
  @topics.command("list")
171
196
  def topics(
172
- file: Annotated[str, typer.Option()] = None,
173
- full: Annotated[bool, typer.Option()] = False,
197
+ file: Annotated[str, typer.Option(help="Name of File")],
198
+ full: Annotated[bool, typer.Option(help="As a table with additional parameters")] = False,
199
+ # Todo add mission / project as optional argument as filenames are not unique
174
200
  ):
201
+ """
202
+ List topics for a file
203
+
204
+ Only makes sense with MCAP files as we don't associate topics with BAGs.
205
+ """
206
+ if file.endswith(".bag"):
207
+ print("BAG files generally do not have topics")
175
208
  try:
176
209
  url = "/file/byName"
177
210
  response = client.get(url, params={"name": file})
@@ -197,11 +230,19 @@ def topics(
197
230
 
198
231
 
199
232
  @projects.command("create")
200
- def create_project(name: Annotated[str, typer.Option()]):
233
+ def create_project(
234
+ name: Annotated[str, typer.Option(help="Name of Project")],
235
+ description: Annotated[str, typer.Option(help="Description of Project")],
236
+ ):
237
+ """
238
+ Create a new project
239
+ """
201
240
  try:
202
241
  url = "/project/create"
203
- response = client.post(url, json={"name": name})
204
- response.raise_for_status()
242
+ response = client.post(url, json={"name": name, "description": description, "requiredTags": []}) # TODO: Add required tags as option
243
+ if response.status_code >= 400:
244
+ print(f"Failed to create project: {response.json()["message"]}")
245
+ return
205
246
  print("Project created")
206
247
 
207
248
  except httpx.HTTPError as e:
@@ -210,12 +251,23 @@ def create_project(name: Annotated[str, typer.Option()]):
210
251
 
211
252
  @app.command("upload")
212
253
  def upload(
213
- path: Annotated[str, typer.Option(prompt=True)],
214
- project: Annotated[str, typer.Option(prompt=True)],
215
- mission: Annotated[str, typer.Option(prompt=True)],
254
+ path: Annotated[str, typer.Option(prompt=True, help="Path to files to upload, Regex supported")],
255
+ project: Annotated[str, typer.Option(prompt=True, help="Name of Project")],
256
+ mission: Annotated[str, typer.Option(prompt=True, help="Name of Mission to create")],
216
257
  ):
258
+ """
259
+ Upload files matching the path to a mission in a project.
260
+
261
+ The mission name must be unique within the project and not yet created.\n
262
+ Examples:\n
263
+ - 'klein upload --path "~/data/**/*.bag" --project "Project 1" --mission "Mission 1"'\n
264
+
265
+ """
217
266
  files = expand_and_match(path)
218
267
  filenames = list(map(lambda x: x.split("/")[-1], filter(lambda x: not os.path.isdir(x),files)))
268
+ if not filenames:
269
+ print("No files found")
270
+ return
219
271
  filepaths = {}
220
272
  for path in files:
221
273
  if not os.path.isdir(path):
@@ -247,7 +299,7 @@ def upload(
247
299
 
248
300
  create_mission_url = "/mission/create"
249
301
  new_mission = client.post(
250
- create_mission_url, json={"name": mission, "projectUUID": project_json["uuid"]}
302
+ create_mission_url, json={"name": mission, "projectUUID": project_json["uuid"], "tags": []}
251
303
  )
252
304
  new_mission.raise_for_status()
253
305
  new_mission_data = new_mission.json()
@@ -322,7 +374,7 @@ def clear_queue():
322
374
  print("Operation cancelled.")
323
375
 
324
376
 
325
- @app.command("wipe")
377
+ @app.command("wipe", hidden=True)
326
378
  def wipe():
327
379
  """Wipe all data"""
328
380
  # Prompt the user for confirmation
@@ -362,8 +414,13 @@ def wipe():
362
414
  print("Operation cancelled.")
363
415
 
364
416
 
365
- @app.command("claim")
417
+ @app.command("claim", hidden=True)
366
418
  def claim():
419
+ """
420
+ Claim admin rights as the first user
421
+
422
+ Only works if no other user has claimed admin rights before.
423
+ """
367
424
  response = client.post("/user/claimAdmin")
368
425
  response.raise_for_status()
369
426
  print("Admin claimed.")
@@ -371,6 +428,7 @@ def claim():
371
428
 
372
429
  @user.command("list")
373
430
  def users():
431
+ """List all users"""
374
432
  response = client.get("/user/all")
375
433
  response.raise_for_status()
376
434
  data = response.json()
@@ -382,6 +440,7 @@ def users():
382
440
 
383
441
  @user.command("info")
384
442
  def user_info():
443
+ """Get logged in user info"""
385
444
  response = client.get("/user/me")
386
445
  response.raise_for_status()
387
446
  data = response.json()
@@ -390,6 +449,7 @@ def user_info():
390
449
 
391
450
  @user.command("promote")
392
451
  def promote(email: Annotated[str, typer.Option()]):
452
+ """Promote another user to admin"""
393
453
  response = client.post("/user/promote", json={"email": email})
394
454
  response.raise_for_status()
395
455
  print("User promoted.")
@@ -397,6 +457,7 @@ def promote(email: Annotated[str, typer.Option()]):
397
457
 
398
458
  @user.command("demote")
399
459
  def demote(email: Annotated[str, typer.Option()]):
460
+ """Demote another user from admin"""
400
461
  response = client.post("/user/demote", json={"email": email})
401
462
  response.raise_for_status()
402
463
  print("User demoted.")
@@ -406,6 +467,7 @@ def demote(email: Annotated[str, typer.Option()]):
406
467
  def download(
407
468
  missionuuid: Annotated[str, typer.Argument()],
408
469
  ):
470
+ """Download file"""
409
471
  try:
410
472
  response = client.get("/file/downloadWithToken", params={"uuid": missionuuid})
411
473
  response.raise_for_status()
@@ -419,6 +481,7 @@ def addTag(
419
481
  tagtypeuuid: Annotated[str, typer.Argument()],
420
482
  value: Annotated[str, typer.Argument()],
421
483
  ):
484
+ """Tag a mission"""
422
485
  try:
423
486
  response = client.post("/tag/addTag", json={"mission": missionuuid, "tagType": tagtypeuuid, "value": value})
424
487
  if response.status_code < 400:
@@ -436,6 +499,7 @@ def addTag(
436
499
  def tagTypes(
437
500
  verbose: Annotated[bool, typer.Option()] = False,
438
501
  ):
502
+ """List all tagtypes"""
439
503
  try:
440
504
  response = client.get("/tag/all")
441
505
  response.raise_for_status()
@@ -456,6 +520,7 @@ def tagTypes(
456
520
  def deleteTag(
457
521
  taguuid: Annotated[str, typer.Argument()],
458
522
  ):
523
+ """Delete a tag"""
459
524
  try:
460
525
  response = client.delete("/tag/deleteTag", params={"uuid": taguuid})
461
526
  if response.status_code < 400:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kleinkram
3
- Version: 0.0.117
3
+ Version: 0.0.118
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
@@ -0,0 +1,10 @@
1
+ kleinkram/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ kleinkram/auth.py,sha256=s6Lek1vLRs2cXSwfGmEWuuzmszMhaTAJbh9icxWPSkQ,7628
3
+ kleinkram/consts.py,sha256=8kCOeSdMqEyB89hT55w5yd1fksUwbj52a2nYkZTZI_0,88
4
+ kleinkram/helper.py,sha256=mP6AohHWU8kckAi1Oevgs0gfGPH1Qzkp0f7jiQF9u5I,2198
5
+ kleinkram/main.py,sha256=O1VOT8YFOc6YcyLhd723iOScvO1ILUG2LW1ggkUZVJY,18071
6
+ kleinkram-0.0.118.dist-info/METADATA,sha256=8eKAgwE0t4W6ecNaQ4r3eOnzgbCa-6ArE_aetX_otHk,733
7
+ kleinkram-0.0.118.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
8
+ kleinkram-0.0.118.dist-info/entry_points.txt,sha256=RHXtRzcreVHImatgjhQwZQ6GdJThElYjHEWcR1BPXUI,45
9
+ kleinkram-0.0.118.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
10
+ kleinkram-0.0.118.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- kleinkram/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- kleinkram/auth.py,sha256=HTLSHzSior2sgimmVUVhcA01w160QjQ4JkOw3uoIZ70,6688
3
- kleinkram/consts.py,sha256=pm_6OuQcO-tYcRhwauTtyRRsuYY0y0yb6EGuIl49LnI,50
4
- kleinkram/helper.py,sha256=mP6AohHWU8kckAi1Oevgs0gfGPH1Qzkp0f7jiQF9u5I,2198
5
- kleinkram/main.py,sha256=NaJ_u2_QLcS3nhMuoYPwoGOczo9c38lKXy5Phq_intk,15177
6
- kleinkram-0.0.117.dist-info/METADATA,sha256=vL4vRab6dfC-uPAIP_hXJW53AxhT46OaMsOLzhWjLu4,733
7
- kleinkram-0.0.117.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
8
- kleinkram-0.0.117.dist-info/entry_points.txt,sha256=RHXtRzcreVHImatgjhQwZQ6GdJThElYjHEWcR1BPXUI,45
9
- kleinkram-0.0.117.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
10
- kleinkram-0.0.117.dist-info/RECORD,,