kleinkram 0.0.111__tar.gz → 0.0.114__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kleinkram
3
- Version: 0.0.111
3
+ Version: 0.0.114
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.0.111"
7
+ version = "0.0.114"
8
8
  authors = [
9
9
  { name="Johann Schwabe", email="jschwab@ethz.ch" },
10
10
  ]
@@ -63,7 +63,6 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
63
63
  if self.path.startswith("/cli/callback"):
64
64
  query = urllib.parse.urlparse(self.path).query
65
65
  params = urllib.parse.parse_qs(query)
66
- print(params)
67
66
  self.server.tokens = {
68
67
  AUTH_TOKEN: params.get(AUTH_TOKEN)[0],
69
68
  REFRESH_TOKEN: params.get(REFRESH_TOKEN)[0],
@@ -108,10 +107,9 @@ class AuthenticatedClient(httpx.Client):
108
107
  if not refresh_token:
109
108
  print("No refresh token found. Please login again.")
110
109
  raise Exception("No refresh token found.")
111
-
110
+ self.cookies.set(REFRESH_TOKEN, refresh_token,)
112
111
  response = self.post(
113
112
  "/auth/refresh-token",
114
- json={REFRESH_TOKEN: refresh_token},
115
113
  )
116
114
  response.raise_for_status()
117
115
  new_access_token = response.cookies.get(AUTH_TOKEN)
@@ -132,7 +130,7 @@ class AuthenticatedClient(httpx.Client):
132
130
  if response.status_code == 401:
133
131
  print("Token expired, refreshing token...")
134
132
  self.refresh_token()
135
- response = super().request(method, url, *args, **kwargs)
133
+ response = super().request(method, self.tokenfile.endpoint + url, *args, **kwargs)
136
134
  return response
137
135
 
138
136
 
@@ -163,3 +161,8 @@ def endpoint(endpoint: Annotated[str, typer.Argument()]):
163
161
  tokenfile = TokenFile()
164
162
  tokenfile.endpoint = endpoint
165
163
  tokenfile.writeToFile()
164
+
165
+ def setCliKey(key: Annotated[str, typer.Argument()]):
166
+ tokenfile = TokenFile()
167
+ tokenfile.tokens[tokenfile.endpoint][CLI_KEY] = key
168
+ tokenfile.writeToFile()
@@ -1,5 +1,3 @@
1
- import fnmatch
2
- import math
3
1
  import os
4
2
  import threading
5
3
  import glob
@@ -45,7 +43,9 @@ def uploadFiles(files: Dict[str, str], paths: Dict[str, str], nrThreads: int):
45
43
  def uploadFile(_queue: queue.Queue, paths: Dict[str, str], pbar: tqdm):
46
44
  while True:
47
45
  try:
48
- filename, url = _queue.get(timeout=3)
46
+ filename, info = _queue.get(timeout=3)
47
+ url = info["url"]
48
+ uuid = info["uuid"]
49
49
  filepath = paths[filename]
50
50
  headers = {"Content-Type": "application/octet-stream"}
51
51
  with open(filepath, "rb") as f:
@@ -54,7 +54,7 @@ def uploadFile(_queue: queue.Queue, paths: Dict[str, str], pbar: tqdm):
54
54
  response = cli.put(url, content=f, headers=headers)
55
55
  if response.status_code == 200:
56
56
  pbar.update(100) # Update progress for each file
57
- client.post("/queue/confirmUpload", json={"filename": filename})
57
+ client.post("/queue/confirmUpload", json={"uuid": uuid})
58
58
  else:
59
59
  print(f"Failed to upload {filename}. HTTP status: {response.status_code}")
60
60
  _queue.task_done()
@@ -1,3 +1,4 @@
1
+ from datetime import datetime, timedelta
1
2
  import os
2
3
 
3
4
  import httpx
@@ -9,34 +10,40 @@ from rich.table import Table
9
10
 
10
11
  from .helper import uploadFiles, expand_and_match
11
12
 
12
- from .auth import login, client, endpoint
13
+ from .auth import login, client, endpoint, setCliKey
13
14
 
14
15
  app = typer.Typer()
15
16
  projects = typer.Typer(name="projects")
16
- runs = typer.Typer(name="runs")
17
+ missions = typer.Typer(name="missions")
17
18
  files = typer.Typer(name="files")
18
19
  topics = typer.Typer(name="topics")
19
20
  queue = typer.Typer(name="queue")
20
21
  user = typer.Typer(name="users")
22
+ tagtypes = typer.Typer(name="tagtypes")
23
+ tag = typer.Typer(name="tag")
24
+
21
25
 
22
26
  app.add_typer(projects)
23
- app.add_typer(runs)
27
+ app.add_typer(missions)
24
28
  app.add_typer(topics)
25
29
  app.add_typer(files)
26
30
  app.add_typer(queue)
27
31
  app.add_typer(user)
32
+ app.add_typer(tagtypes)
33
+ app.add_typer(tag)
28
34
  app.command()(login)
29
35
  app.command()(endpoint)
36
+ app.command()(setCliKey)
30
37
 
31
38
 
32
39
  @files.command("list")
33
40
  def list_files(
34
- project: Annotated[str, typer.Option()] = None,
35
- run: Annotated[str, typer.Option()] = None,
36
- topics: Annotated[List[str], typer.Option()] = None,
41
+ project: Annotated[str, typer.Option()] = None,
42
+ mission: Annotated[str, typer.Option()] = None,
43
+ topics: Annotated[List[str], typer.Option()] = None,
37
44
  ):
38
45
  """
39
- List all files with optional filters for project, run, or topics.
46
+ List all files with optional filters for project, mission, or topics.
40
47
  """
41
48
  try:
42
49
  url = f"/file/filteredByNames"
@@ -44,36 +51,36 @@ def list_files(
44
51
  url,
45
52
  params={
46
53
  "projectName": project,
47
- "runName": run,
54
+ "missionName": mission,
48
55
  "topics": topics,
49
56
  },
50
57
  )
51
58
  response.raise_for_status()
52
59
  data = response.json()
53
- runs_by_project_uuid = {}
54
- files_by_run_uuid = {}
60
+ missions_by_project_uuid = {}
61
+ files_by_mission_uuid = {}
55
62
  for file in data:
56
- run_uuid = file["run"]["uuid"]
57
- project_uuid = file["run"]["project"]["uuid"]
58
- if project_uuid not in runs_by_project_uuid:
59
- runs_by_project_uuid[project_uuid] = []
60
- if run_uuid not in runs_by_project_uuid[project_uuid]:
61
- runs_by_project_uuid[project_uuid].append(run_uuid)
62
- if run_uuid not in files_by_run_uuid:
63
- files_by_run_uuid[run_uuid] = []
64
- files_by_run_uuid[run_uuid].append(file)
65
-
66
- print("Files by Run & Project:")
67
- for project_uuid, runs in runs_by_project_uuid.items():
68
- first_file = files_by_run_uuid[runs[0]][0]
69
- print(f"* {first_file['run']['project']['name']}")
70
- for run in runs:
71
- print(f" - {files_by_run_uuid[run][0]['run']['name']}")
72
- for file in files_by_run_uuid[run]:
63
+ mission_uuid = file["mission"]["uuid"]
64
+ project_uuid = file["mission"]["project"]["uuid"]
65
+ if project_uuid not in missions_by_project_uuid:
66
+ missions_by_project_uuid[project_uuid] = []
67
+ if mission_uuid not in missions_by_project_uuid[project_uuid]:
68
+ missions_by_project_uuid[project_uuid].append(mission_uuid)
69
+ if mission_uuid not in files_by_mission_uuid:
70
+ files_by_mission_uuid[mission_uuid] = []
71
+ files_by_mission_uuid[mission_uuid].append(file)
72
+
73
+ print("Files by mission & Project:")
74
+ for project_uuid, missions in missions_by_project_uuid.items():
75
+ first_file = files_by_mission_uuid[missions[0]][0]
76
+ print(f"* {first_file['mission']['project']['name']}")
77
+ for mission in missions:
78
+ print(f" - {files_by_mission_uuid[mission][0]['mission']['name']}")
79
+ for file in files_by_mission_uuid[mission]:
73
80
  print(f" - '{file['filename']}'")
74
81
 
75
82
  except httpx.HTTPError as e:
76
- print(f"Failed to fetch runs: {e}")
83
+ print(f"Failed to fetch missions: {e}")
77
84
 
78
85
 
79
86
  @projects.command("list")
@@ -93,15 +100,16 @@ def list_projects():
93
100
  print(f"Failed to fetch projects: {e}")
94
101
 
95
102
 
96
- @runs.command("list")
97
- def list_runs(
98
- project: Annotated[str, typer.Option()] = None,
103
+ @missions.command("list")
104
+ def list_missions(
105
+ project: Annotated[str, typer.Option()] = None,
106
+ verbose: Annotated[bool, typer.Option()] = False,
99
107
  ):
100
108
  """
101
- List all runs with optional filter for project.
109
+ List all missions with optional filter for project.
102
110
  """
103
111
  try:
104
- url = "/run"
112
+ url = "/mission"
105
113
  if project:
106
114
  url += f"/filteredByProjectName/{project}"
107
115
  else:
@@ -109,45 +117,58 @@ def list_runs(
109
117
  response = client.get(url)
110
118
  response.raise_for_status()
111
119
  data = response.json()
112
- runs_by_project_uuid = {}
113
- for run in data:
114
- project_uuid = run["project"]["uuid"]
115
- if project_uuid not in runs_by_project_uuid:
116
- runs_by_project_uuid[project_uuid] = []
117
- runs_by_project_uuid[project_uuid].append(run)
118
-
119
- print("Runs by Project:")
120
- for project_uuid, runs in runs_by_project_uuid.items():
121
- print(f"* {runs_by_project_uuid[project_uuid][0]['project']['name']}")
122
- for run in runs:
123
- print(f" - {run['name']}")
120
+ missions_by_project_uuid = {}
121
+ for mission in data:
122
+ project_uuid = mission["project"]["uuid"]
123
+ if project_uuid not in missions_by_project_uuid:
124
+ missions_by_project_uuid[project_uuid] = []
125
+ missions_by_project_uuid[project_uuid].append(mission)
126
+
127
+ print("missions by Project:")
128
+ if not verbose:
129
+ for project_uuid, missions in missions_by_project_uuid.items():
130
+ print(f"* {missions_by_project_uuid[project_uuid][0]['project']['name']}")
131
+ for mission in missions:
132
+ print(f" - {mission['name']}")
133
+ else:
134
+ table = Table("UUID", "name", "project", "creator", "createdAt")
135
+ for project_uuid, missions in missions_by_project_uuid.items():
136
+ for mission in missions:
137
+ table.add_row(
138
+ mission["uuid"],
139
+ mission["name"],
140
+ mission["project"]["name"],
141
+ mission["creator"]["name"],
142
+ mission["createdAt"],
143
+ )
144
+ print(table)
124
145
 
125
146
  except httpx.HTTPError as e:
126
- print(f"Failed to fetch runs: {e}")
147
+ print(f"Failed to fetch missions: {e}")
127
148
 
128
149
 
129
- @runs.command("byUUID")
130
- def run_by_uuid(
131
- uuid: Annotated[str, typer.Argument()],
150
+ @missions.command("byUUID")
151
+ def mission_by_uuid(
152
+ uuid: Annotated[str, typer.Argument()],
132
153
  ):
133
154
  try:
134
- url = "/run/byUUID"
155
+ url = "/mission/byUUID"
135
156
  response = client.get(url, params={"uuid": uuid})
136
157
  response.raise_for_status()
137
158
  data = response.json()
138
- print(f"Run: {data['name']}")
159
+ print(f"mission: {data['name']}")
139
160
  print(f"Creator: {data['creator']['name']}")
140
161
  table = Table("Filename", "Size", "date")
141
162
  for file in data["files"]:
142
163
  table.add_row(file["filename"], f"{file['size']}", file["date"])
143
164
  except httpx.HTTPError as e:
144
- print(f"Failed to fetch runs: {e}")
165
+ print(f"Failed to fetch missions: {e}")
145
166
 
146
167
 
147
168
  @topics.command("list")
148
169
  def topics(
149
- file: Annotated[str, typer.Option()] = None,
150
- full: Annotated[bool, typer.Option()] = False,
170
+ file: Annotated[str, typer.Option()] = None,
171
+ full: Annotated[bool, typer.Option()] = False,
151
172
  ):
152
173
  try:
153
174
  url = "/file/byName"
@@ -187,12 +208,12 @@ def create_project(name: Annotated[str, typer.Option()]):
187
208
 
188
209
  @app.command("upload")
189
210
  def upload(
190
- path: Annotated[str, typer.Option(prompt=True)],
191
- project: Annotated[str, typer.Option(prompt=True)],
192
- run: Annotated[str, typer.Option(prompt=True)],
211
+ path: Annotated[str, typer.Option(prompt=True)],
212
+ project: Annotated[str, typer.Option(prompt=True)],
213
+ mission: Annotated[str, typer.Option(prompt=True)],
193
214
  ):
194
215
  files = expand_and_match(path)
195
- filenames = list(map(lambda x: x.split("/")[-1], files))
216
+ filenames = list(map(lambda x: x.split("/")[-1], filter(lambda x: not os.path.isdir(x),files)))
196
217
  filepaths = {}
197
218
  for path in files:
198
219
  if not os.path.isdir(path):
@@ -209,31 +230,32 @@ def upload(
209
230
  print(f"Project not found: {project}")
210
231
  return
211
232
 
212
- get_run_url = "/run/byName"
213
- run_response = client.get(get_run_url, params={"name": run})
214
- run_response.raise_for_status()
215
- if run_response.content:
216
- run_json = run_response.json()
217
- if run_json["uuid"]:
233
+ get_mission_url = "/mission/byName"
234
+ mission_response = client.get(get_mission_url, params={"name": mission})
235
+ mission_response.raise_for_status()
236
+ if mission_response.content:
237
+ mission_json = mission_response.json()
238
+ if mission_json["uuid"]:
218
239
  print(
219
- f"Run: {run_json['uuid']} already exists. Delete it or select another name."
240
+ f"mission: {mission_json['uuid']} already exists. Delete it or select another name."
220
241
  )
221
242
  return
222
243
  print(f"Something failed, should not happen")
223
244
  return
224
245
 
225
- create_run_url = "/run/create"
226
- new_run = client.post(
227
- create_run_url, json={"name": run, "projectUUID": project_json["uuid"]}
246
+ create_mission_url = "/mission/create"
247
+ new_mission = client.post(
248
+ create_mission_url, json={"name": mission, "projectUUID": project_json["uuid"]}
228
249
  )
229
- new_run.raise_for_status()
230
- new_run_data = new_run.json()
231
- print(f"Created run: {new_run_data['name']}")
250
+ new_mission.raise_for_status()
251
+ new_mission_data = new_mission.json()
252
+ print(f"Created mission: {new_mission_data['name']}")
232
253
 
233
254
  get_presigned_url = "/queue/createPreSignedURLS"
255
+
234
256
  response_2 = client.post(
235
257
  get_presigned_url,
236
- json={"filenames": filenames, "runUUID": new_run_data["uuid"]},
258
+ json={"filenames": filenames, "missionUUID": new_mission_data["uuid"]},
237
259
  )
238
260
  response_2.raise_for_status()
239
261
  presigned_urls = response_2.json()
@@ -260,6 +282,31 @@ def clear_queue():
260
282
  print("Operation cancelled.")
261
283
 
262
284
 
285
+ @queue.command("list")
286
+ def list_queue():
287
+ """List current Queue entities"""
288
+ try:
289
+ url = "/queue/active"
290
+ startDate = datetime.now().date() - timedelta(days=1)
291
+ response = client.get(url, params={"startDate": startDate})
292
+ response.raise_for_status()
293
+ data = response.json()
294
+ table = Table("UUID", "filename", "mission", "state", "origin", "createdAt")
295
+ for topic in data:
296
+ table.add_row(
297
+ topic["uuid"],
298
+ topic["filename"],
299
+ topic["mission"]["name"],
300
+ topic["state"],
301
+ topic["location"],
302
+ topic["createdAt"],
303
+ )
304
+ print(table)
305
+
306
+ except httpx.HTTPError as e:
307
+ print(e)
308
+
309
+
263
310
  @files.command("clear")
264
311
  def clear_queue():
265
312
  """Clear queue"""
@@ -289,7 +336,7 @@ def wipe():
289
336
  response_queue = client.delete("/queue/clear")
290
337
  response_file = client.delete("/file/clear")
291
338
  response_analysis = client.delete("/analysis/clear")
292
- response_run = client.delete("/run/clear")
339
+ response_mission = client.delete("/mission/clear")
293
340
  response_project = client.delete("/project/clear")
294
341
 
295
342
  if response_queue.status_code >= 400:
@@ -301,9 +348,9 @@ def wipe():
301
348
  elif response_analysis.status_code >= 400:
302
349
  print("Failed to clear analysis.")
303
350
  print(response_analysis.text)
304
- elif response_run.status_code >= 400:
305
- print("Failed to clear runs.")
306
- print(response_run.text)
351
+ elif response_mission.status_code >= 400:
352
+ print("Failed to clear missions.")
353
+ print(response_mission.text)
307
354
  elif response_project.status_code >= 400:
308
355
  print("Failed to clear projects.")
309
356
  print(response_project.text)
@@ -355,15 +402,67 @@ def demote(email: Annotated[str, typer.Option()]):
355
402
 
356
403
  @files.command("download")
357
404
  def download(
358
- runuuid: Annotated[str, typer.Argument()],
405
+ missionuuid: Annotated[str, typer.Argument()],
359
406
  ):
360
407
  try:
361
- response = client.get("/file/downloadWithToken", params={"uuid": runuuid})
408
+ response = client.get("/file/downloadWithToken", params={"uuid": missionuuid})
362
409
  response.raise_for_status()
363
410
  print(response.json())
364
411
  except:
365
412
  print("Failed to download file")
366
413
 
414
+ @missions.command('tag')
415
+ def addTag(
416
+ missionuuid: Annotated[str, typer.Argument()],
417
+ tagtypeuuid: Annotated[str, typer.Argument()],
418
+ value: Annotated[str, typer.Argument()],
419
+ ):
420
+ try:
421
+ response = client.post("/tag/addTag", json={"mission": missionuuid, "tagType": tagtypeuuid, "value": value})
422
+ if response.status_code < 400:
423
+ print("Tagged mission")
424
+ else:
425
+ print(response.json())
426
+ print("Failed to tag mission")
427
+ except:
428
+ print("Failed to tag mission"
429
+ )
430
+
431
+ @tagtypes.command('list')
432
+ def tagTypes(
433
+ verbose: Annotated[bool, typer.Option()] = False,
434
+ ):
435
+ try:
436
+ response = client.get("/tag/all")
437
+ response.raise_for_status()
438
+ data = response.json()
439
+ if verbose:
440
+ table = Table("UUID","Name", "Datatype")
441
+ for tagtype in data:
442
+ table.add_row(tagtype["uuid"], tagtype["name"], tagtype["datatype"])
443
+ else:
444
+ table = Table("Name", "Datatype")
445
+ for tagtype in data:
446
+ table.add_row(tagtype["name"], tagtype["datatype"])
447
+ print(table)
448
+ except:
449
+ print("Failed to fetch tagtypes")
450
+
451
+ @tag.command('delete')
452
+ def deleteTag(
453
+ taguuid: Annotated[str, typer.Argument()],
454
+ ):
455
+ try:
456
+ response = client.delete("/tag/deleteTag", params={"uuid": taguuid})
457
+ if response.status_code < 400:
458
+ print("Deleted tag")
459
+ else:
460
+ print(response)
461
+ print("Failed to delete tag")
462
+ except:
463
+ print("Failed to delete tag"
464
+ )
465
+
367
466
 
368
467
  if __name__ == "__main__":
369
468
  app()
File without changes
File without changes
File without changes
File without changes
File without changes