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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """Defines the common interface for the K-Scale Python API."""
2
2
 
3
- __version__ = "0.3.11"
3
+ __version__ = "0.3.13"
4
4
 
5
5
  from pathlib import Path
6
6
 
@@ -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
 
@@ -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
- logger.error("Got %d error K-Scale: %s", response.status_code, response.text)
384
- if verbose_error():
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=True,
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=True,
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("GET", f"/robots/urdf/{class_name}", auth=True)
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-01-22T23:12:27+00:00
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="Neural Network Id")
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="Neural Network Id")
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.11
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/pkg/intro).
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=6YWQz-S1hHb0tE-dX6V50gXseGHKNwAZwOgB9UiBRH0,201
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=DLB5ok69JAeUUeA7Yy80VmyZGFt9dKf8kD0vSBMmqP8,18987
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=ThQakuvGh7CquiGQ4wiCyCWAgqmKInsJcANo910X9Uw,15585
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=G8Nk6V7LGJE9Wpg9tyyCkIfz1fRTsxXQRgHtleiUVqo,6834
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=5ewJxWGGfVfaEdKyUbaHrkn36GnWV_IGcl5KSgP4pRg,5090
28
- kscale-0.3.11.dist-info/licenses/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
29
- kscale-0.3.11.dist-info/METADATA,sha256=WPRIutVSok3_QB4G78rCGNoSm-6i9eZoT0eql1d-Pv4,2343
30
- kscale-0.3.11.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
31
- kscale-0.3.11.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
32
- kscale-0.3.11.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
33
- kscale-0.3.11.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5