kleinkram 0.0.120__tar.gz → 0.0.121.dev20240731111552__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.120 → kleinkram-0.0.121.dev20240731111552}/PKG-INFO +1 -1
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/pyproject.toml +1 -1
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/src/kleinkram/auth.py +23 -14
- kleinkram-0.0.121.dev20240731111552/src/kleinkram/consts.py +1 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/src/kleinkram/helper.py +3 -1
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/src/kleinkram/main.py +56 -35
- kleinkram-0.0.120/src/kleinkram/consts.py +0 -2
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/.gitignore +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/LICENSE +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/README.md +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/deploy.sh +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/dev.sh +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/requirements.txt +0 -0
- {kleinkram-0.0.120 → kleinkram-0.0.121.dev20240731111552}/src/kleinkram/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.121.dev20240731111552
|
|
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
|
|
@@ -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,11 @@ 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[List[str]] = typer.Option(
|
|
46
|
+
None, help="Comma separated list of topics"
|
|
47
|
+
),
|
|
46
48
|
):
|
|
47
49
|
"""
|
|
48
50
|
List all files with optional filters for project, mission, or topics.
|
|
@@ -115,12 +117,13 @@ def list_projects():
|
|
|
115
117
|
|
|
116
118
|
@missions.command("list")
|
|
117
119
|
def list_missions(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
project: Optional[str] = typer.Option(None, help="Name of Project"),
|
|
121
|
+
verbose: Optional[bool] = typer.Option(
|
|
122
|
+
False, help="Outputs a table with more information"
|
|
123
|
+
),
|
|
120
124
|
):
|
|
121
125
|
"""
|
|
122
126
|
List all missions with optional filter for project.
|
|
123
|
-
|
|
124
127
|
"""
|
|
125
128
|
try:
|
|
126
129
|
url = "/mission"
|
|
@@ -141,7 +144,9 @@ def list_missions(
|
|
|
141
144
|
print("missions by Project:")
|
|
142
145
|
if not verbose:
|
|
143
146
|
for project_uuid, missions in missions_by_project_uuid.items():
|
|
144
|
-
print(
|
|
147
|
+
print(
|
|
148
|
+
f"* {missions_by_project_uuid[project_uuid][0]['project']['name']}"
|
|
149
|
+
)
|
|
145
150
|
for mission in missions:
|
|
146
151
|
print(f" - {mission['name']}")
|
|
147
152
|
else:
|
|
@@ -163,8 +168,8 @@ def list_missions(
|
|
|
163
168
|
|
|
164
169
|
@missions.command("byUUID")
|
|
165
170
|
def mission_by_uuid(
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
uuid: Annotated[str, typer.Argument()],
|
|
172
|
+
json: Optional[bool] = typer.Option(False, help="Output as JSON"),
|
|
168
173
|
):
|
|
169
174
|
"""
|
|
170
175
|
Get mission name, project name, creator and table of its files given a Mission UUID
|
|
@@ -194,14 +199,16 @@ def mission_by_uuid(
|
|
|
194
199
|
|
|
195
200
|
@topics.command("list")
|
|
196
201
|
def topics(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
file: Annotated[str, typer.Option(help="Name of File")],
|
|
203
|
+
full: Annotated[
|
|
204
|
+
bool, typer.Option(help="As a table with additional parameters")
|
|
205
|
+
] = False,
|
|
206
|
+
# Todo add mission / project as optional argument as filenames are not unique or handle multiple files
|
|
200
207
|
):
|
|
201
208
|
"""
|
|
202
209
|
List topics for a file
|
|
203
210
|
|
|
204
|
-
Only makes sense with MCAP files as we don't associate topics with BAGs.
|
|
211
|
+
Only makes sense with MCAP files as we don't associate topics with BAGs as that would be redundant.
|
|
205
212
|
"""
|
|
206
213
|
if file.endswith(".bag"):
|
|
207
214
|
print("BAG files generally do not have topics")
|
|
@@ -231,15 +238,18 @@ def topics(
|
|
|
231
238
|
|
|
232
239
|
@projects.command("create")
|
|
233
240
|
def create_project(
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
name: Annotated[str, typer.Option(help="Name of Project")],
|
|
242
|
+
description: Annotated[str, typer.Option(help="Description of Project")],
|
|
236
243
|
):
|
|
237
244
|
"""
|
|
238
245
|
Create a new project
|
|
239
246
|
"""
|
|
247
|
+
# Todo add required tags as option.
|
|
240
248
|
try:
|
|
241
249
|
url = "/project/create"
|
|
242
|
-
response = client.post(
|
|
250
|
+
response = client.post(
|
|
251
|
+
url, json={"name": name, "description": description, "requiredTags": []}
|
|
252
|
+
) # TODO: Add required tags as option
|
|
243
253
|
if response.status_code >= 400:
|
|
244
254
|
response_json = response.json()
|
|
245
255
|
response_text = response_json["message"]
|
|
@@ -253,9 +263,13 @@ def create_project(
|
|
|
253
263
|
|
|
254
264
|
@app.command("upload")
|
|
255
265
|
def upload(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
266
|
+
path: Annotated[
|
|
267
|
+
str, typer.Option(prompt=True, help="Path to files to upload, Regex supported")
|
|
268
|
+
],
|
|
269
|
+
project: Annotated[str, typer.Option(prompt=True, help="Name of Project")],
|
|
270
|
+
mission: Annotated[
|
|
271
|
+
str, typer.Option(prompt=True, help="Name of Mission to create")
|
|
272
|
+
],
|
|
259
273
|
):
|
|
260
274
|
"""
|
|
261
275
|
Upload files matching the path to a mission in a project.
|
|
@@ -266,7 +280,9 @@ def upload(
|
|
|
266
280
|
|
|
267
281
|
"""
|
|
268
282
|
files = expand_and_match(path)
|
|
269
|
-
filenames = list(
|
|
283
|
+
filenames = list(
|
|
284
|
+
map(lambda x: x.split("/")[-1], filter(lambda x: not os.path.isdir(x), files))
|
|
285
|
+
)
|
|
270
286
|
if not filenames:
|
|
271
287
|
print("No files found")
|
|
272
288
|
return
|
|
@@ -301,7 +317,8 @@ def upload(
|
|
|
301
317
|
|
|
302
318
|
create_mission_url = "/mission/create"
|
|
303
319
|
new_mission = client.post(
|
|
304
|
-
create_mission_url,
|
|
320
|
+
create_mission_url,
|
|
321
|
+
json={"name": mission, "projectUUID": project_json["uuid"], "tags": []},
|
|
305
322
|
)
|
|
306
323
|
new_mission.raise_for_status()
|
|
307
324
|
new_mission_data = new_mission.json()
|
|
@@ -467,7 +484,7 @@ def demote(email: Annotated[str, typer.Option()]):
|
|
|
467
484
|
|
|
468
485
|
@files.command("download")
|
|
469
486
|
def download(
|
|
470
|
-
|
|
487
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
471
488
|
):
|
|
472
489
|
"""Download file"""
|
|
473
490
|
try:
|
|
@@ -477,15 +494,19 @@ def download(
|
|
|
477
494
|
except:
|
|
478
495
|
print("Failed to download file")
|
|
479
496
|
|
|
480
|
-
|
|
497
|
+
|
|
498
|
+
@missions.command("tag")
|
|
481
499
|
def addTag(
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
500
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
501
|
+
tagtypeuuid: Annotated[str, typer.Argument()],
|
|
502
|
+
value: Annotated[str, typer.Argument()],
|
|
485
503
|
):
|
|
486
504
|
"""Tag a mission"""
|
|
487
505
|
try:
|
|
488
|
-
response = client.post(
|
|
506
|
+
response = client.post(
|
|
507
|
+
"/tag/addTag",
|
|
508
|
+
json={"mission": missionuuid, "tagType": tagtypeuuid, "value": value},
|
|
509
|
+
)
|
|
489
510
|
if response.status_code < 400:
|
|
490
511
|
print("Tagged mission")
|
|
491
512
|
else:
|
|
@@ -497,9 +518,9 @@ def addTag(
|
|
|
497
518
|
sys.exit(1)
|
|
498
519
|
|
|
499
520
|
|
|
500
|
-
@tagtypes.command(
|
|
521
|
+
@tagtypes.command("list")
|
|
501
522
|
def tagTypes(
|
|
502
|
-
|
|
523
|
+
verbose: Annotated[bool, typer.Option()] = False,
|
|
503
524
|
):
|
|
504
525
|
"""List all tagtypes"""
|
|
505
526
|
try:
|
|
@@ -507,7 +528,7 @@ def tagTypes(
|
|
|
507
528
|
response.raise_for_status()
|
|
508
529
|
data = response.json()
|
|
509
530
|
if verbose:
|
|
510
|
-
table = Table("UUID","Name", "Datatype")
|
|
531
|
+
table = Table("UUID", "Name", "Datatype")
|
|
511
532
|
for tagtype in data:
|
|
512
533
|
table.add_row(tagtype["uuid"], tagtype["name"], tagtype["datatype"])
|
|
513
534
|
else:
|
|
@@ -518,9 +539,10 @@ def tagTypes(
|
|
|
518
539
|
except:
|
|
519
540
|
print("Failed to fetch tagtypes")
|
|
520
541
|
|
|
521
|
-
|
|
542
|
+
|
|
543
|
+
@tag.command("delete")
|
|
522
544
|
def deleteTag(
|
|
523
|
-
|
|
545
|
+
taguuid: Annotated[str, typer.Argument()],
|
|
524
546
|
):
|
|
525
547
|
"""Delete a tag"""
|
|
526
548
|
try:
|
|
@@ -531,8 +553,7 @@ def deleteTag(
|
|
|
531
553
|
print(response)
|
|
532
554
|
print("Failed to delete tag")
|
|
533
555
|
except:
|
|
534
|
-
print("Failed to delete tag"
|
|
535
|
-
)
|
|
556
|
+
print("Failed to delete tag")
|
|
536
557
|
|
|
537
558
|
|
|
538
559
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|