kleinkram 0.0.119__tar.gz → 0.0.121.dev20240731104925__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.dev20240731104925}/PKG-INFO +1 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/pyproject.toml +1 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/src/kleinkram/auth.py +23 -14
- kleinkram-0.0.121.dev20240731104925/src/kleinkram/consts.py +1 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/src/kleinkram/helper.py +3 -1
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/src/kleinkram/main.py +58 -36
- kleinkram-0.0.119/src/kleinkram/consts.py +0 -2
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/.gitignore +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/LICENSE +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/README.md +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/deploy.sh +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/dev.sh +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/requirements.txt +0 -0
- {kleinkram-0.0.119 → kleinkram-0.0.121.dev20240731104925}/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.dev20240731104925
|
|
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,18 +238,22 @@ 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"]
|
|
256
|
+
print(f"Failed to create project: {response_text}")
|
|
246
257
|
return
|
|
247
258
|
print("Project created")
|
|
248
259
|
|
|
@@ -252,9 +263,13 @@ def create_project(
|
|
|
252
263
|
|
|
253
264
|
@app.command("upload")
|
|
254
265
|
def upload(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
],
|
|
258
273
|
):
|
|
259
274
|
"""
|
|
260
275
|
Upload files matching the path to a mission in a project.
|
|
@@ -265,7 +280,9 @@ def upload(
|
|
|
265
280
|
|
|
266
281
|
"""
|
|
267
282
|
files = expand_and_match(path)
|
|
268
|
-
filenames = list(
|
|
283
|
+
filenames = list(
|
|
284
|
+
map(lambda x: x.split("/")[-1], filter(lambda x: not os.path.isdir(x), files))
|
|
285
|
+
)
|
|
269
286
|
if not filenames:
|
|
270
287
|
print("No files found")
|
|
271
288
|
return
|
|
@@ -300,7 +317,8 @@ def upload(
|
|
|
300
317
|
|
|
301
318
|
create_mission_url = "/mission/create"
|
|
302
319
|
new_mission = client.post(
|
|
303
|
-
create_mission_url,
|
|
320
|
+
create_mission_url,
|
|
321
|
+
json={"name": mission, "projectUUID": project_json["uuid"], "tags": []},
|
|
304
322
|
)
|
|
305
323
|
new_mission.raise_for_status()
|
|
306
324
|
new_mission_data = new_mission.json()
|
|
@@ -466,7 +484,7 @@ def demote(email: Annotated[str, typer.Option()]):
|
|
|
466
484
|
|
|
467
485
|
@files.command("download")
|
|
468
486
|
def download(
|
|
469
|
-
|
|
487
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
470
488
|
):
|
|
471
489
|
"""Download file"""
|
|
472
490
|
try:
|
|
@@ -476,15 +494,19 @@ def download(
|
|
|
476
494
|
except:
|
|
477
495
|
print("Failed to download file")
|
|
478
496
|
|
|
479
|
-
|
|
497
|
+
|
|
498
|
+
@missions.command("tag")
|
|
480
499
|
def addTag(
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
500
|
+
missionuuid: Annotated[str, typer.Argument()],
|
|
501
|
+
tagtypeuuid: Annotated[str, typer.Argument()],
|
|
502
|
+
value: Annotated[str, typer.Argument()],
|
|
484
503
|
):
|
|
485
504
|
"""Tag a mission"""
|
|
486
505
|
try:
|
|
487
|
-
response = client.post(
|
|
506
|
+
response = client.post(
|
|
507
|
+
"/tag/addTag",
|
|
508
|
+
json={"mission": missionuuid, "tagType": tagtypeuuid, "value": value},
|
|
509
|
+
)
|
|
488
510
|
if response.status_code < 400:
|
|
489
511
|
print("Tagged mission")
|
|
490
512
|
else:
|
|
@@ -496,9 +518,9 @@ def addTag(
|
|
|
496
518
|
sys.exit(1)
|
|
497
519
|
|
|
498
520
|
|
|
499
|
-
@tagtypes.command(
|
|
521
|
+
@tagtypes.command("list")
|
|
500
522
|
def tagTypes(
|
|
501
|
-
|
|
523
|
+
verbose: Annotated[bool, typer.Option()] = False,
|
|
502
524
|
):
|
|
503
525
|
"""List all tagtypes"""
|
|
504
526
|
try:
|
|
@@ -506,7 +528,7 @@ def tagTypes(
|
|
|
506
528
|
response.raise_for_status()
|
|
507
529
|
data = response.json()
|
|
508
530
|
if verbose:
|
|
509
|
-
table = Table("UUID","Name", "Datatype")
|
|
531
|
+
table = Table("UUID", "Name", "Datatype")
|
|
510
532
|
for tagtype in data:
|
|
511
533
|
table.add_row(tagtype["uuid"], tagtype["name"], tagtype["datatype"])
|
|
512
534
|
else:
|
|
@@ -517,9 +539,10 @@ def tagTypes(
|
|
|
517
539
|
except:
|
|
518
540
|
print("Failed to fetch tagtypes")
|
|
519
541
|
|
|
520
|
-
|
|
542
|
+
|
|
543
|
+
@tag.command("delete")
|
|
521
544
|
def deleteTag(
|
|
522
|
-
|
|
545
|
+
taguuid: Annotated[str, typer.Argument()],
|
|
523
546
|
):
|
|
524
547
|
"""Delete a tag"""
|
|
525
548
|
try:
|
|
@@ -530,8 +553,7 @@ def deleteTag(
|
|
|
530
553
|
print(response)
|
|
531
554
|
print("Failed to delete tag")
|
|
532
555
|
except:
|
|
533
|
-
print("Failed to delete tag"
|
|
534
|
-
)
|
|
556
|
+
print("Failed to delete tag")
|
|
535
557
|
|
|
536
558
|
|
|
537
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
|