kscale 0.3.11__py3-none-any.whl → 0.3.13__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.
- kscale/__init__.py +1 -1
- kscale/web/cli/robot_class.py +2 -1
- kscale/web/clients/base.py +21 -5
- kscale/web/clients/robot_class.py +17 -4
- kscale/web/gen/api.py +45 -4
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/METADATA +2 -2
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/RECORD +11 -11
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/WHEEL +1 -1
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/entry_points.txt +0 -0
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/licenses/LICENSE +0 -0
- {kscale-0.3.11.dist-info → kscale-0.3.13.dist-info}/top_level.txt +0 -0
kscale/__init__.py
CHANGED
kscale/web/cli/robot_class.py
CHANGED
@@ -41,10 +41,11 @@ async def list() -> None:
|
|
41
41
|
click.style(rc.id, fg="blue"),
|
42
42
|
click.style(rc.class_name, fg="green"),
|
43
43
|
rc.description or "N/A",
|
44
|
+
"N/A" if rc.num_downloads is None else f"{rc.num_downloads:,}",
|
44
45
|
]
|
45
46
|
for rc in robot_classes
|
46
47
|
]
|
47
|
-
click.echo(tabulate(table_data, headers=["ID", "Name", "Description"], tablefmt="simple"))
|
48
|
+
click.echo(tabulate(table_data, headers=["ID", "Name", "Description", "Downloads"], tablefmt="simple"))
|
48
49
|
else:
|
49
50
|
click.echo(click.style("No robot classes found", fg="red"))
|
50
51
|
|
kscale/web/clients/base.py
CHANGED
@@ -9,7 +9,7 @@ import sys
|
|
9
9
|
import time
|
10
10
|
import webbrowser
|
11
11
|
from types import TracebackType
|
12
|
-
from typing import Any, Self, Type
|
12
|
+
from typing import Any, Mapping, Self, Type
|
13
13
|
from urllib.parse import urljoin
|
14
14
|
|
15
15
|
import aiohttp
|
@@ -363,6 +363,7 @@ class BaseClient:
|
|
363
363
|
params: dict[str, Any] | None = None,
|
364
364
|
data: BaseModel | dict[str, Any] | None = None,
|
365
365
|
files: dict[str, Any] | None = None,
|
366
|
+
error_code_suggestions: dict[int, str] | None = None,
|
366
367
|
) -> dict[str, Any]:
|
367
368
|
url = urljoin(self.base_url, endpoint)
|
368
369
|
kwargs: dict[str, Any] = {}
|
@@ -380,12 +381,27 @@ class BaseClient:
|
|
380
381
|
response = await client.request(method, url, **kwargs)
|
381
382
|
|
382
383
|
if response.is_error:
|
383
|
-
|
384
|
-
|
384
|
+
error_code = response.status_code
|
385
|
+
error_json = response.json()
|
386
|
+
use_verbose_error = verbose_error()
|
387
|
+
|
388
|
+
if not use_verbose_error:
|
389
|
+
logger.info("Use KSCALE_VERBOSE_ERROR=1 to see the full error message")
|
390
|
+
logger.info("If this persists, please create an issue here: https://github.com/kscalelabs/kscale")
|
391
|
+
|
392
|
+
logger.error("Got error %d from the K-Scale API", error_code)
|
393
|
+
if isinstance(error_json, Mapping):
|
394
|
+
for key, value in error_json.items():
|
395
|
+
logger.error(" [%s] %s", key, value)
|
396
|
+
else:
|
397
|
+
logger.error(" %s", error_json)
|
398
|
+
|
399
|
+
if error_code_suggestions is not None and error_code in error_code_suggestions:
|
400
|
+
logger.error("Hint: %s", error_code_suggestions[error_code])
|
401
|
+
|
402
|
+
if use_verbose_error:
|
385
403
|
response.raise_for_status()
|
386
404
|
else:
|
387
|
-
logger.error("Use KSCALE_VERBOSE_ERROR=1 to see the full error message")
|
388
|
-
logger.error("If this persists, please create an issue here: https://github.com/kscalelabs/kscale")
|
389
405
|
sys.exit(1)
|
390
406
|
|
391
407
|
return response.json()
|
@@ -3,6 +3,7 @@
|
|
3
3
|
import hashlib
|
4
4
|
import json
|
5
5
|
import logging
|
6
|
+
import shutil
|
6
7
|
import tarfile
|
7
8
|
from pathlib import Path
|
8
9
|
from typing import Any
|
@@ -31,7 +32,7 @@ class RobotClassClient(BaseClient):
|
|
31
32
|
data = await self._request(
|
32
33
|
"GET",
|
33
34
|
"/robots/",
|
34
|
-
auth=
|
35
|
+
auth=False,
|
35
36
|
)
|
36
37
|
return [RobotClass.model_validate(item) for item in data]
|
37
38
|
|
@@ -39,7 +40,7 @@ class RobotClassClient(BaseClient):
|
|
39
40
|
data = await self._request(
|
40
41
|
"GET",
|
41
42
|
f"/robots/name/{class_name}",
|
42
|
-
auth=
|
43
|
+
auth=False,
|
43
44
|
)
|
44
45
|
return RobotClass.model_validate(data)
|
45
46
|
|
@@ -117,7 +118,12 @@ class RobotClassClient(BaseClient):
|
|
117
118
|
cache_path = get_robots_dir() / class_name / "robot.tgz"
|
118
119
|
if cache and cache_path.exists() and not should_refresh_file(cache_path):
|
119
120
|
return cache_path
|
120
|
-
data = await self._request(
|
121
|
+
data = await self._request(
|
122
|
+
"GET",
|
123
|
+
f"/robots/urdf/{class_name}",
|
124
|
+
auth=False,
|
125
|
+
error_code_suggestions={404: "Use `kscale robot list` to view classes."},
|
126
|
+
)
|
121
127
|
response = RobotDownloadURDFResponse.model_validate(data)
|
122
128
|
expected_hash = response.md5_hash
|
123
129
|
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
@@ -166,9 +172,16 @@ class RobotClassClient(BaseClient):
|
|
166
172
|
|
167
173
|
# Unpacks the file if requested.
|
168
174
|
unpack_path = cache_path.parent / "robot"
|
169
|
-
unpack_path.mkdir(parents=True, exist_ok=True)
|
170
175
|
unpacked_path_info = unpack_path / INFO_FILE_NAME
|
171
176
|
|
177
|
+
if not cache:
|
178
|
+
# If not using cache, remove the existing unpacked directory.
|
179
|
+
if unpack_path.exists():
|
180
|
+
logger.info("Removing existing unpacked directory")
|
181
|
+
shutil.rmtree(unpack_path)
|
182
|
+
|
183
|
+
unpack_path.mkdir(parents=True, exist_ok=True)
|
184
|
+
|
172
185
|
# If the file has already been unpacked, return the path.
|
173
186
|
if unpacked_path_info.exists():
|
174
187
|
with open(unpacked_path_info, "r") as f:
|
kscale/web/gen/api.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# generated by datamodel-codegen:
|
4
4
|
# filename: openapi.json
|
5
|
-
# timestamp: 2025-
|
5
|
+
# timestamp: 2025-05-04T22:45:26+00:00
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
@@ -19,6 +19,40 @@ class APIKeyResponse(BaseModel):
|
|
19
19
|
api_key: str = Field(..., title="Api Key")
|
20
20
|
|
21
21
|
|
22
|
+
class ActuatorMetadataInput(BaseModel):
|
23
|
+
actuator_type: Optional[str] = Field(None, title="Actuator Type")
|
24
|
+
sysid: Optional[str] = Field(None, title="Sysid")
|
25
|
+
max_torque: Optional[Union[float, str]] = Field(None, title="Max Torque")
|
26
|
+
armature: Optional[Union[float, str]] = Field(None, title="Armature")
|
27
|
+
damping: Optional[Union[float, str]] = Field(None, title="Damping")
|
28
|
+
frictionloss: Optional[Union[float, str]] = Field(None, title="Frictionloss")
|
29
|
+
vin: Optional[Union[float, str]] = Field(None, title="Vin")
|
30
|
+
kt: Optional[Union[float, str]] = Field(None, title="Kt")
|
31
|
+
R: Optional[Union[float, str]] = Field(None, title="R")
|
32
|
+
vmax: Optional[Union[float, str]] = Field(None, title="Vmax")
|
33
|
+
amax: Optional[Union[float, str]] = Field(None, title="Amax")
|
34
|
+
max_velocity: Optional[Union[float, str]] = Field(None, title="Max Velocity")
|
35
|
+
max_pwm: Optional[Union[float, str]] = Field(None, title="Max Pwm")
|
36
|
+
error_gain: Optional[Union[float, str]] = Field(None, title="Error Gain")
|
37
|
+
|
38
|
+
|
39
|
+
class ActuatorMetadataOutput(BaseModel):
|
40
|
+
actuator_type: Optional[str] = Field(None, title="Actuator Type")
|
41
|
+
sysid: Optional[str] = Field(None, title="Sysid")
|
42
|
+
max_torque: Optional[str] = Field(None, title="Max Torque")
|
43
|
+
armature: Optional[str] = Field(None, title="Armature")
|
44
|
+
damping: Optional[str] = Field(None, title="Damping")
|
45
|
+
frictionloss: Optional[str] = Field(None, title="Frictionloss")
|
46
|
+
vin: Optional[str] = Field(None, title="Vin")
|
47
|
+
kt: Optional[str] = Field(None, title="Kt")
|
48
|
+
R: Optional[str] = Field(None, title="R")
|
49
|
+
vmax: Optional[str] = Field(None, title="Vmax")
|
50
|
+
amax: Optional[str] = Field(None, title="Amax")
|
51
|
+
max_velocity: Optional[str] = Field(None, title="Max Velocity")
|
52
|
+
max_pwm: Optional[str] = Field(None, title="Max Pwm")
|
53
|
+
error_gain: Optional[str] = Field(None, title="Error Gain")
|
54
|
+
|
55
|
+
|
22
56
|
class AddRobotClassRequest(BaseModel):
|
23
57
|
description: Optional[str] = Field(None, title="Description")
|
24
58
|
|
@@ -37,8 +71,8 @@ class JointMetadataInput(BaseModel):
|
|
37
71
|
offset: Optional[Union[float, str]] = Field(None, title="Offset")
|
38
72
|
flipped: Optional[bool] = Field(None, title="Flipped")
|
39
73
|
actuator_type: Optional[str] = Field(None, title="Actuator Type")
|
40
|
-
nn_id: Optional[int] = Field(None, title="
|
41
|
-
soft_torque_limit: Optional[str] = Field(None, title="Soft Torque Limit")
|
74
|
+
nn_id: Optional[int] = Field(None, title="Nn Id")
|
75
|
+
soft_torque_limit: Optional[Union[float, str]] = Field(None, title="Soft Torque Limit")
|
42
76
|
|
43
77
|
|
44
78
|
class JointMetadataOutput(BaseModel):
|
@@ -50,7 +84,7 @@ class JointMetadataOutput(BaseModel):
|
|
50
84
|
offset: Optional[str] = Field(None, title="Offset")
|
51
85
|
flipped: Optional[bool] = Field(None, title="Flipped")
|
52
86
|
actuator_type: Optional[str] = Field(None, title="Actuator Type")
|
53
|
-
nn_id: Optional[int] = Field(None, title="
|
87
|
+
nn_id: Optional[int] = Field(None, title="Nn Id")
|
54
88
|
soft_torque_limit: Optional[str] = Field(None, title="Soft Torque Limit")
|
55
89
|
|
56
90
|
|
@@ -82,11 +116,17 @@ class RobotResponse(BaseModel):
|
|
82
116
|
|
83
117
|
class RobotURDFMetadataInput(BaseModel):
|
84
118
|
joint_name_to_metadata: Optional[Dict[str, JointMetadataInput]] = Field(None, title="Joint Name To Metadata")
|
119
|
+
actuator_type_to_metadata: Optional[Dict[str, ActuatorMetadataInput]] = Field(
|
120
|
+
None, title="Actuator Type To Metadata"
|
121
|
+
)
|
85
122
|
control_frequency: Optional[Union[float, str]] = Field(None, title="Control Frequency")
|
86
123
|
|
87
124
|
|
88
125
|
class RobotURDFMetadataOutput(BaseModel):
|
89
126
|
joint_name_to_metadata: Optional[Dict[str, JointMetadataOutput]] = Field(None, title="Joint Name To Metadata")
|
127
|
+
actuator_type_to_metadata: Optional[Dict[str, ActuatorMetadataOutput]] = Field(
|
128
|
+
None, title="Actuator Type To Metadata"
|
129
|
+
)
|
90
130
|
control_frequency: Optional[str] = Field(None, title="Control Frequency")
|
91
131
|
|
92
132
|
|
@@ -141,3 +181,4 @@ class RobotClass(BaseModel):
|
|
141
181
|
description: str = Field(..., title="Description")
|
142
182
|
user_id: str = Field(..., title="User Id")
|
143
183
|
metadata: Optional[RobotURDFMetadataOutput] = None
|
184
|
+
num_downloads: Optional[int] = Field(0, title="Num Downloads")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kscale
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.13
|
4
4
|
Summary: The kscale project
|
5
5
|
Home-page: https://github.com/kscalelabs/kscale
|
6
6
|
Author: Benjamin Bolte
|
@@ -55,7 +55,7 @@ Dynamic: summary
|
|
55
55
|
|
56
56
|
# K-Scale Command Line Interface
|
57
57
|
|
58
|
-
This is a command line tool for interacting with various services provided by K-Scale Labs. For more information, see the [documentation](https://docs.kscale.dev/
|
58
|
+
This is a command line tool for interacting with various services provided by K-Scale Labs. For more information, see the [documentation](https://docs.kscale.dev/docs/k-scale-api).
|
59
59
|
|
60
60
|
## Installation
|
61
61
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kscale/__init__.py,sha256=
|
1
|
+
kscale/__init__.py,sha256=blW9DBfEkhee_kJYnE9wcIWBY9iWvKHkO1_CoKimPU8,201
|
2
2
|
kscale/cli.py,sha256=JvaPtmWvF7s0D4I3K98eZAItf3oOi2ULsn5aPGxDcu4,795
|
3
3
|
kscale/conf.py,sha256=dm35XSnzJp93St-ixVtYN4Nvqvb5upPGBrWkSI6Yb-4,1743
|
4
4
|
kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -15,19 +15,19 @@ kscale/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
kscale/web/utils.py,sha256=Mme-FAQ0_zbjjOQeX8wyq8F4kL4i9fH7ytri16U6qOA,1046
|
16
16
|
kscale/web/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
kscale/web/cli/robot.py,sha256=rI-A4_0uvJPeA71Apl4Z3mV5fIfWkgmzT9JRmJYxz3A,3307
|
18
|
-
kscale/web/cli/robot_class.py,sha256=
|
18
|
+
kscale/web/cli/robot_class.py,sha256=Gzg1NTD1iDf04jPVRz_fCKVAjnQ3pmA7dB3KmUjxXwk,19080
|
19
19
|
kscale/web/cli/user.py,sha256=9IGsJBPyhjsmT04mZ2RGOs35ePfqB2RltYP0Ty5zF5o,1290
|
20
20
|
kscale/web/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
kscale/web/clients/base.py,sha256=
|
21
|
+
kscale/web/clients/base.py,sha256=P5AZdoawNpocgx_8j5v0eauowG5suUf8SnnD7q1_gx0,16213
|
22
22
|
kscale/web/clients/client.py,sha256=rzW2s8T7bKVuybOSQ65-ghl02rcXBoOxnx_nUDwgEPw,362
|
23
23
|
kscale/web/clients/robot.py,sha256=PI8HHkU-4Re9I5rLpp6dGbekRE-rBNVfXZxR_mO2MqE,1485
|
24
|
-
kscale/web/clients/robot_class.py,sha256=
|
24
|
+
kscale/web/clients/robot_class.py,sha256=LCKje6nNsDBeKDxZAGCO9vQXdyOwtx0woMmB5vDoUmE,7230
|
25
25
|
kscale/web/clients/user.py,sha256=jsa1_s6qXRM-AGBbHlPhd1NierUtynjY9tVAPNr6_Os,568
|
26
26
|
kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
-
kscale/web/gen/api.py,sha256=
|
28
|
-
kscale-0.3.
|
29
|
-
kscale-0.3.
|
30
|
-
kscale-0.3.
|
31
|
-
kscale-0.3.
|
32
|
-
kscale-0.3.
|
33
|
-
kscale-0.3.
|
27
|
+
kscale/web/gen/api.py,sha256=QwyayqHVz7FFgDQO7IJFMN9MxtKpJPemiTMAk6aCEQE,7299
|
28
|
+
kscale-0.3.13.dist-info/licenses/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
29
|
+
kscale-0.3.13.dist-info/METADATA,sha256=MBCGy62OjZbOcDo-culQK6K62vtR1kIDshurzWkp768,2350
|
30
|
+
kscale-0.3.13.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
31
|
+
kscale-0.3.13.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
|
32
|
+
kscale-0.3.13.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
33
|
+
kscale-0.3.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|