kleinkram 0.0.119__tar.gz → 0.0.121__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.
- {kleinkram-0.0.119 → kleinkram-0.0.121}/.gitignore +1 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121}/PKG-INFO +1 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121}/pyproject.toml +1 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121}/src/kleinkram/auth.py +23 -14
- kleinkram-0.0.121/src/kleinkram/consts.py +1 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/src/kleinkram/helper.py +3 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121}/src/kleinkram/main.py +64 -36
- kleinkram-0.0.119/src/kleinkram/consts.py +0 -2
- {kleinkram-0.0.119 → kleinkram-0.0.121}/LICENSE +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/README.md +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/deploy.sh +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/dev.sh +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/requirements.txt +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121}/src/kleinkram/__init__.py +0 -0
|
@@ -94,7 +94,9 @@ class AuthenticatedClient(httpx.Client):
|
|
|
94
94
|
self.tokenfile = TokenFile()
|
|
95
95
|
self._load_cookies()
|
|
96
96
|
except Exception as e:
|
|
97
|
-
print(
|
|
97
|
+
print(
|
|
98
|
+
f"{self.tokenfile.endpoint} is not authenticated. Please run 'klein login'."
|
|
99
|
+
)
|
|
98
100
|
|
|
99
101
|
def _load_cookies(self):
|
|
100
102
|
if self.tokenfile.isCliToken():
|
|
@@ -110,7 +112,10 @@ class AuthenticatedClient(httpx.Client):
|
|
|
110
112
|
if not refresh_token:
|
|
111
113
|
print("No refresh token found. Please login again.")
|
|
112
114
|
raise Exception("No refresh token found.")
|
|
113
|
-
self.cookies.set(
|
|
115
|
+
self.cookies.set(
|
|
116
|
+
REFRESH_TOKEN,
|
|
117
|
+
refresh_token,
|
|
118
|
+
)
|
|
114
119
|
response = self.post(
|
|
115
120
|
"/auth/refresh-token",
|
|
116
121
|
)
|
|
@@ -124,16 +129,16 @@ class AuthenticatedClient(httpx.Client):
|
|
|
124
129
|
response = super().request(
|
|
125
130
|
method, self.tokenfile.endpoint + url, *args, **kwargs
|
|
126
131
|
)
|
|
127
|
-
if (
|
|
128
|
-
url == "/auth/refresh-token"
|
|
129
|
-
) and response.status_code == 401:
|
|
132
|
+
if (url == "/auth/refresh-token") and response.status_code == 401:
|
|
130
133
|
print("Refresh token expired. Please login again.")
|
|
131
134
|
response.status_code = 403
|
|
132
135
|
exit(1)
|
|
133
136
|
if response.status_code == 401:
|
|
134
137
|
print("Token expired, refreshing token...")
|
|
135
138
|
self.refresh_token()
|
|
136
|
-
response = super().request(
|
|
139
|
+
response = super().request(
|
|
140
|
+
method, self.tokenfile.endpoint + url, *args, **kwargs
|
|
141
|
+
)
|
|
137
142
|
return response
|
|
138
143
|
|
|
139
144
|
|
|
@@ -141,8 +146,10 @@ client = AuthenticatedClient()
|
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
def login(
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
key: Optional[str] = typer.Option(None, help="CLI Key", hidden=True),
|
|
150
|
+
open_browser: Optional[bool] = typer.Option(
|
|
151
|
+
True, help="Open browser for authentication"
|
|
152
|
+
),
|
|
146
153
|
):
|
|
147
154
|
"""
|
|
148
155
|
Login into the currently set endpoint.\n
|
|
@@ -175,12 +182,16 @@ def login(
|
|
|
175
182
|
|
|
176
183
|
return
|
|
177
184
|
|
|
178
|
-
print(
|
|
185
|
+
print(
|
|
186
|
+
f"Please open the following URL manually in your browser to authenticate: {url + '-no-redirect'}"
|
|
187
|
+
)
|
|
179
188
|
print("Enter the authentication token provided after logging in:")
|
|
180
189
|
manual_auth_token = input("Authentication Token: ")
|
|
181
190
|
manual_refresh_token = input("Refresh Token: ")
|
|
182
191
|
if manual_auth_token:
|
|
183
|
-
tokenfile.saveTokens(
|
|
192
|
+
tokenfile.saveTokens(
|
|
193
|
+
{AUTH_TOKEN: manual_auth_token, REFRESH_TOKEN: manual_refresh_token}
|
|
194
|
+
)
|
|
184
195
|
print("Authentication complete. Tokens saved to tokens.json.")
|
|
185
196
|
else:
|
|
186
197
|
print("No authentication token provided.")
|
|
@@ -188,7 +199,7 @@ def login(
|
|
|
188
199
|
|
|
189
200
|
|
|
190
201
|
def setEndpoint(
|
|
191
|
-
|
|
202
|
+
endpoint: Optional[str] = typer.Argument(None, help="API endpoint to use")
|
|
192
203
|
):
|
|
193
204
|
"""
|
|
194
205
|
Set the current endpoint
|
|
@@ -220,9 +231,7 @@ def endpoint():
|
|
|
220
231
|
print("- " + _endpoint)
|
|
221
232
|
|
|
222
233
|
|
|
223
|
-
def setCliKey(
|
|
224
|
-
key: Annotated[str, typer.Argument(help="CLI Key")]
|
|
225
|
-
):
|
|
234
|
+
def setCliKey(key: Annotated[str, typer.Argument(help="CLI Key")]):
|
|
226
235
|
"""
|
|
227
236
|
Set the CLI key (Actions Only)
|
|
228
237
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
API_URL='https://api.datasets.leggedrobotics.com'
|
|
@@ -56,7 +56,9 @@ def uploadFile(_queue: queue.Queue, paths: Dict[str, str], pbar: tqdm):
|
|
|
56
56
|
pbar.update(100) # Update progress for each file
|
|
57
57
|
client.post("/queue/confirmUpload", json={"uuid": uuid})
|
|
58
58
|
else:
|
|
59
|
-
print(
|
|
59
|
+
print(
|
|
60
|
+
f"Failed to upload {filename}. HTTP status: {response.status_code}"
|
|
61
|
+
)
|
|
60
62
|
_queue.task_done()
|
|
61
63
|
except queue.Empty:
|
|
62
64
|
break
|
|
@@ -18,7 +18,7 @@ projects = typer.Typer(name="projects", help="Project operations")
|
|
|
18
18
|
missions = typer.Typer(name="missions", help="Mission operations")
|
|
19
19
|
files = typer.Typer(name="files", help="File operations")
|
|
20
20
|
topics = typer.Typer(name="topics", help="Topic operations")
|
|
21
|
-
queue = typer.Typer(name="queue", help="
|
|
21
|
+
queue = typer.Typer(name="queue", help="Status of files uploading")
|
|
22
22
|
user = typer.Typer(name="users", help="User operations")
|
|
23
23
|
tagtypes = typer.Typer(name="tagtypes", help="TagType operations")
|
|
24
24
|
tag = typer.Typer(name="tag", help="Tag operations")
|
|
@@ -40,9 +40,12 @@ app.command(hidden=True)(setCliKey)
|
|
|
40
40
|
|
|
41
41
|
@files.command("list")
|
|
42
42
|
def list_files(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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[str] = typer.Option(
|
|
46
|
+
None, help="Comma separated list of topics"
|
|
47
|
+
),
|
|
48
|
+
tags: Optional[str] = typer.Option(None, help="Comma separated list of tagtype:tagvalue pairs")
|
|
46
49
|
):
|
|
47
50
|
"""
|
|
48
51
|
List all files with optional filters for project, mission, or topics.
|
|
@@ -64,6 +67,11 @@ def list_files(
|
|
|
64
67
|
params["missionName"] = mission
|
|
65
68
|
if topics:
|
|
66
69
|
params["topics"] = topics
|
|
70
|
+
if tags:
|
|
71
|
+
params["tags"] = {}
|
|
72
|
+
for tag in tags.split(","):
|
|
73
|
+
tagtype, tagvalue = tag.split("§")
|
|
74
|
+
params['tags'][tagtype] = tagvalue
|
|
67
75
|
response = client.get(
|
|
68
76
|
url,
|
|
69
77
|
params=params,
|
|
@@ -115,12 +123,13 @@ def list_projects():
|
|
|
115
123
|
|
|
116
124
|
@missions.command("list")
|
|
117
125
|
def list_missions(
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
project: Optional[str] = typer.Option(None, help="Name of Project"),
|
|
127
|
+
verbose: Optional[bool] = typer.Option(
|
|
128
|
+
False, help="Outputs a table with more information"
|
|
129
|
+
),
|
|
120
130
|
):
|
|
121
131
|
"""
|
|
122
132
|
List all missions with optional filter for project.
|
|
123
|
-
|
|
124
133
|
"""
|
|
125
134
|
try:
|
|
126
135
|
url = "/mission"
|
|
@@ -141,7 +150,9 @@ def list_missions(
|
|
|
141
150
|
print("missions by Project:")
|
|
142
151
|
if not verbose:
|
|
143
152
|
for project_uuid, missions in missions_by_project_uuid.items():
|
|
144
|
-
print(
|
|
153
|
+
print(
|
|
154
|
+
f"* {missions_by_project_uuid[project_uuid][0]['project']['name']}"
|
|
155
|
+
)
|
|
145
156
|
for mission in missions:
|
|
146
157
|
print(f" - {mission['name']}")
|
|
147
158
|
else:
|
|
@@ -163,8 +174,8 @@ def list_missions(
|
|
|
163
174
|
|
|
164
175
|
@missions.command("byUUID")
|
|
165
176
|
def mission_by_uuid(
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
uuid: Annotated[str, typer.Argument()],
|
|
178
|
+
json: Optional[bool] = typer.Option(False, help="Output as JSON"),
|
|
168
179
|
):
|
|
169
180
|
"""
|
|
170
181
|
Get mission name, project name, creator and table of its files given a Mission UUID
|
|
@@ -194,14 +205,16 @@ def mission_by_uuid(
|
|
|
194
205
|
|
|
195
206
|
@topics.command("list")
|
|
196
207
|
def topics(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
file: Annotated[str, typer.Option(help="Name of File")],
|
|
209
|
+
full: Annotated[
|
|
210
|
+
bool, typer.Option(help="As a table with additional parameters")
|
|
211
|
+
] = False,
|
|
212
|
+
# Todo add mission / project as optional argument as filenames are not unique or handle multiple files
|
|
200
213
|
):
|
|
201
214
|
"""
|
|
202
215
|
List topics for a file
|
|
203
216
|
|
|
204
|
-
Only makes sense with MCAP files as we don't associate topics with BAGs.
|
|
217
|
+
Only makes sense with MCAP files as we don't associate topics with BAGs as that would be redundant.
|
|
205
218
|
"""
|
|
206
219
|
if file.endswith(".bag"):
|
|
207
220
|
print("BAG files generally do not have topics")
|
|
@@ -231,18 +244,22 @@ def topics(
|
|
|
231
244
|
|
|
232
245
|
@projects.command("create")
|
|
233
246
|
def create_project(
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
name: Annotated[str, typer.Option(help="Name of Project")],
|
|
248
|
+
description: Annotated[str, typer.Option(help="Description of Project")],
|
|
236
249
|
):
|
|
237
250
|
"""
|
|
238
251
|
Create a new project
|
|
239
252
|
"""
|
|
253
|
+
# Todo add required tags as option.
|
|
240
254
|
try:
|
|
241
255
|
url = "/project/create"
|
|
242
|
-
response = client.post(
|
|
256
|
+
response = client.post(
|
|
257
|
+
url, json={"name": name, "description": description, "requiredTags": []}
|
|
258
|
+
) # TODO: Add required tags as option
|
|
243
259
|
if response.status_code >= 400:
|
|
244
260
|
response_json = response.json()
|
|
245
|
-
|
|
261
|
+
response_text = response_json["message"]
|
|
262
|
+
print(f"Failed to create project: {response_text}")
|
|
246
263
|
return
|
|
247
264
|
print("Project created")
|
|
248
265
|
|
|
@@ -252,9 +269,13 @@ def create_project(
|
|
|
252
269
|
|
|
253
270
|
@app.command("upload")
|
|
254
271
|
def upload(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
272
|
+
path: Annotated[
|
|
273
|
+
str, typer.Option(prompt=True, help="Path to files to upload, Regex supported")
|
|
274
|
+
],
|
|
275
|
+
project: Annotated[str, typer.Option(prompt=True, help="Name of Project")],
|
|
276
|
+
mission: Annotated[
|
|
277
|
+
str, typer.Option(prompt=True, help="Name of Mission to create")
|
|
278
|
+
],
|
|
258
279
|
):
|
|
259
280
|
"""
|
|
260
281
|
Upload files matching the path to a mission in a project.
|
|
@@ -265,7 +286,9 @@ def upload(
|
|
|
265
286
|
|
|
266
287
|
"""
|
|
267
288
|
files = expand_and_match(path)
|
|
268
|
-
filenames = list(
|
|
289
|
+
filenames = list(
|
|
290
|
+
map(lambda x: x.split("/")[-1], filter(lambda x: not os.path.isdir(x), files))
|
|
291
|
+
)
|
|
269
292
|
if not filenames:
|
|
270
293
|
print("No files found")
|
|
271
294
|
return
|
|
@@ -300,7 +323,8 @@ def upload(
|
|
|
300
323
|
|
|
301
324
|
create_mission_url = "/mission/create"
|
|
302
325
|
new_mission = client.post(
|
|
303
|
-
create_mission_url,
|
|
326
|
+
create_mission_url,
|
|
327
|
+
json={"name": mission, "projectUUID": project_json["uuid"], "tags": []},
|
|
304
328
|
)
|
|
305
329
|
new_mission.raise_for_status()
|
|
306
330
|
new_mission_data = new_mission.json()
|
|
@@ -466,7 +490,7 @@ def demote(email: Annotated[str, typer.Option()]):
|
|
|
466
490
|
|
|
467
491
|
@files.command("download")
|
|
468
492
|
def download(
|
|
469
|
-
|
|
493
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
470
494
|
):
|
|
471
495
|
"""Download file"""
|
|
472
496
|
try:
|
|
@@ -476,15 +500,19 @@ def download(
|
|
|
476
500
|
except:
|
|
477
501
|
print("Failed to download file")
|
|
478
502
|
|
|
479
|
-
|
|
503
|
+
|
|
504
|
+
@missions.command("tag")
|
|
480
505
|
def addTag(
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
506
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
507
|
+
tagtypeuuid: Annotated[str, typer.Argument()],
|
|
508
|
+
value: Annotated[str, typer.Argument()],
|
|
484
509
|
):
|
|
485
510
|
"""Tag a mission"""
|
|
486
511
|
try:
|
|
487
|
-
response = client.post(
|
|
512
|
+
response = client.post(
|
|
513
|
+
"/tag/addTag",
|
|
514
|
+
json={"mission": missionuuid, "tagType": tagtypeuuid, "value": value},
|
|
515
|
+
)
|
|
488
516
|
if response.status_code < 400:
|
|
489
517
|
print("Tagged mission")
|
|
490
518
|
else:
|
|
@@ -496,9 +524,9 @@ def addTag(
|
|
|
496
524
|
sys.exit(1)
|
|
497
525
|
|
|
498
526
|
|
|
499
|
-
@tagtypes.command(
|
|
527
|
+
@tagtypes.command("list")
|
|
500
528
|
def tagTypes(
|
|
501
|
-
|
|
529
|
+
verbose: Annotated[bool, typer.Option()] = False,
|
|
502
530
|
):
|
|
503
531
|
"""List all tagtypes"""
|
|
504
532
|
try:
|
|
@@ -506,7 +534,7 @@ def tagTypes(
|
|
|
506
534
|
response.raise_for_status()
|
|
507
535
|
data = response.json()
|
|
508
536
|
if verbose:
|
|
509
|
-
table = Table("UUID","Name", "Datatype")
|
|
537
|
+
table = Table("UUID", "Name", "Datatype")
|
|
510
538
|
for tagtype in data:
|
|
511
539
|
table.add_row(tagtype["uuid"], tagtype["name"], tagtype["datatype"])
|
|
512
540
|
else:
|
|
@@ -517,9 +545,10 @@ def tagTypes(
|
|
|
517
545
|
except:
|
|
518
546
|
print("Failed to fetch tagtypes")
|
|
519
547
|
|
|
520
|
-
|
|
548
|
+
|
|
549
|
+
@tag.command("delete")
|
|
521
550
|
def deleteTag(
|
|
522
|
-
|
|
551
|
+
taguuid: Annotated[str, typer.Argument()],
|
|
523
552
|
):
|
|
524
553
|
"""Delete a tag"""
|
|
525
554
|
try:
|
|
@@ -530,8 +559,7 @@ def deleteTag(
|
|
|
530
559
|
print(response)
|
|
531
560
|
print("Failed to delete tag")
|
|
532
561
|
except:
|
|
533
|
-
print("Failed to delete tag"
|
|
534
|
-
)
|
|
562
|
+
print("Failed to delete tag")
|
|
535
563
|
|
|
536
564
|
|
|
537
565
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|