kscale 0.2.0__py3-none-any.whl → 0.2.2__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,9 +1,9 @@
1
1
  """Defines the common interface for the K-Scale Python API."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
4
4
 
5
5
  from pathlib import Path
6
6
 
7
- from kscale.api import K
7
+ from kscale.web.clients.client import WWWClient as K
8
8
 
9
9
  ROOT_DIR = Path(__file__).parent
kscale/conf.py CHANGED
@@ -11,6 +11,8 @@ from omegaconf import II, OmegaConf
11
11
  # This is the public API endpoint for the K-Scale WWW API.
12
12
  DEFAULT_API_ROOT = "https://api.kscale.dev"
13
13
 
14
+ SETTINGS_FILE_NAME = "settings.yaml"
15
+
14
16
 
15
17
  def get_path() -> Path:
16
18
  if "KSCALE_CONFIG_DIR" in os.environ:
@@ -41,9 +43,12 @@ class Settings:
41
43
  if not (dir_path := get_path()).exists():
42
44
  warnings.warn(f"Settings directory does not exist: {dir_path}. Creating it now.")
43
45
  dir_path.mkdir(parents=True)
44
- OmegaConf.save(config, dir_path / "settings.yaml")
46
+ OmegaConf.save(config, dir_path / SETTINGS_FILE_NAME)
45
47
  else:
46
- with open(dir_path / "settings.yaml", "r") as f:
47
- raw_settings = OmegaConf.load(f)
48
- config = OmegaConf.merge(config, raw_settings)
48
+ try:
49
+ with open(dir_path / SETTINGS_FILE_NAME, "r") as f:
50
+ raw_settings = OmegaConf.load(f)
51
+ config = OmegaConf.merge(config, raw_settings)
52
+ except Exception as e:
53
+ warnings.warn(f"Failed to load settings: {e}")
49
54
  return config
@@ -105,7 +105,7 @@ async def upload(class_name: str, urdf_file: str) -> None:
105
105
  async def download(class_name: str, no_cache: bool) -> None:
106
106
  """Downloads a URDF file from a robot class."""
107
107
  async with RobotClassClient() as client:
108
- urdf_file = await client.download_robot_class_urdf(class_name, cache=not no_cache)
108
+ urdf_file = await client.download_compressed_urdf(class_name, cache=not no_cache)
109
109
  click.echo(f"URDF downloaded: {click.style(urdf_file, fg='green')}")
110
110
 
111
111
 
@@ -1,10 +1,14 @@
1
1
  """Defines a unified client for the K-Scale WWW API."""
2
2
 
3
3
  from kscale.web.clients.base import BaseClient
4
+ from kscale.web.clients.robot import RobotClient
5
+ from kscale.web.clients.robot_class import RobotClassClient
4
6
  from kscale.web.clients.user import UserClient
5
7
 
6
8
 
7
9
  class WWWClient(
10
+ RobotClient,
11
+ RobotClassClient,
8
12
  UserClient,
9
13
  BaseClient,
10
14
  ):
@@ -3,6 +3,7 @@
3
3
  import hashlib
4
4
  import json
5
5
  import logging
6
+ import tarfile
6
7
  from pathlib import Path
7
8
 
8
9
  import httpx
@@ -20,6 +21,8 @@ logger = logging.getLogger(__name__)
20
21
  UPLOAD_TIMEOUT = 300.0
21
22
  DOWNLOAD_TIMEOUT = 60.0
22
23
 
24
+ INFO_FILE_NAME = ".info.json"
25
+
23
26
 
24
27
  class RobotClassClient(BaseClient):
25
28
  async def get_robot_classes(self) -> list[RobotClass]:
@@ -97,7 +100,7 @@ class RobotClassClient(BaseClient):
97
100
  r.raise_for_status()
98
101
  return response
99
102
 
100
- async def download_robot_class_urdf(self, class_name: str, *, cache: bool = True) -> Path:
103
+ async def download_compressed_urdf(self, class_name: str, *, cache: bool = True) -> Path:
101
104
  cache_path = get_robots_dir() / class_name / "robot.tgz"
102
105
  if cache and cache_path.exists() and not should_refresh_file(cache_path):
103
106
  return cache_path
@@ -107,7 +110,7 @@ class RobotClassClient(BaseClient):
107
110
  cache_path.parent.mkdir(parents=True, exist_ok=True)
108
111
 
109
112
  # Checks the md5 hash of the file.
110
- cache_path_info = cache_path.parent / "info.json"
113
+ cache_path_info = cache_path.parent / INFO_FILE_NAME
111
114
  if cache_path_info.exists():
112
115
  with open(cache_path_info, "r") as f:
113
116
  info = json.load(f)
@@ -138,3 +141,36 @@ class RobotClassClient(BaseClient):
138
141
  json.dump(info, f)
139
142
 
140
143
  return cache_path
144
+
145
+ async def download_and_extract_urdf(self, class_name: str, *, cache: bool = True) -> Path:
146
+ cache_path = await self.download_compressed_urdf(class_name, cache=cache)
147
+
148
+ # Reads the MD5 hash from the info file.
149
+ cache_path_info = cache_path.parent / INFO_FILE_NAME
150
+ with open(cache_path_info, "r") as f:
151
+ info = json.load(f)
152
+ expected_hash = info["md5_hash"]
153
+
154
+ # Unpacks the file if requested.
155
+ unpack_path = cache_path.parent / "robot"
156
+ unpack_path.mkdir(parents=True, exist_ok=True)
157
+ unpacked_path_info = unpack_path / INFO_FILE_NAME
158
+
159
+ # If the file has already been unpacked, return the path.
160
+ if unpacked_path_info.exists():
161
+ with open(unpacked_path_info, "r") as f:
162
+ info = json.load(f)
163
+ if info["md5_hash"] == expected_hash:
164
+ unpack_path.touch()
165
+ return unpack_path
166
+
167
+ logger.info("Unpacking URDF file")
168
+ with tarfile.open(cache_path, "r:gz") as tar:
169
+ tar.extractall(path=unpack_path)
170
+
171
+ logger.info("Updating downloaded file information")
172
+ info = {"md5_hash": expected_hash}
173
+ with open(unpacked_path_info, "w") as f:
174
+ json.dump(info, f)
175
+
176
+ return unpack_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kscale
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
@@ -1,7 +1,6 @@
1
- kscale/__init__.py,sha256=90mHt8ZJkKMXl-CpnjHf369fws_cliOlAc0Y_SNrvS8,172
2
- kscale/api.py,sha256=jmiuFurTN_Gj_-k-6asqxw8wp-_bgJUXgMPFgJ4lqHA,230
1
+ kscale/__init__.py,sha256=A1hh6cfKSMUSIDxYuPny2OkMm4-WcRlZsRYow_TO9Vo,200
3
2
  kscale/cli.py,sha256=PMHLKR5UwdbbReVmqHXpJ-K9-mGHv_0I7KQkwxmFcUA,881
4
- kscale/conf.py,sha256=OLGz2J-9NAaICoWeN9-hCxrOsyf0vpJBNw-UzIsHdwE,1572
3
+ kscale/conf.py,sha256=dm35XSnzJp93St-ixVtYN4Nvqvb5upPGBrWkSI6Yb-4,1743
5
4
  kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
5
  kscale/requirements-dev.txt,sha256=WI7-ea4IRJakmqVMN8QKhOsDGrghwtvk03aIsFaNSIw,130
7
6
  kscale/requirements.txt,sha256=_BGbnKTQaXKx0bNEG0wguod9swsiCb2mF6rLm7sFJ2Q,214
@@ -13,24 +12,23 @@ kscale/utils/api_base.py,sha256=Kk_WtRDdJHmOg6NtHmVxVrcfARSUkhfr29ypLch_pO0,112
13
12
  kscale/utils/checksum.py,sha256=jt6QmmQND9zrOEnUtOfZpLYROhgto4Gh3OpdUWk4tZA,1093
14
13
  kscale/utils/cli.py,sha256=JoaY5x5SdUx97KmMM9j5AjRRUqqrTlJ9qVckZptEsYA,827
15
14
  kscale/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- kscale/web/api.py,sha256=oyW0XLfX96RPe1xNgdf8ejfATdLlNlP0CL1lP0FN1nM,593
17
15
  kscale/web/utils.py,sha256=Mme-FAQ0_zbjjOQeX8wyq8F4kL4i9fH7ytri16U6qOA,1046
18
16
  kscale/web/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
17
  kscale/web/cli/robot.py,sha256=rI-A4_0uvJPeA71Apl4Z3mV5fIfWkgmzT9JRmJYxz3A,3307
20
- kscale/web/cli/robot_class.py,sha256=ymC5phUqofvOXv5P6f51b9lMK5eRDaavvnzS0x9rDbU,3574
18
+ kscale/web/cli/robot_class.py,sha256=8igY6oWUDafb644QzmqC5cX6Az0rNJau_jvLr7YkCd0,3573
21
19
  kscale/web/cli/token.py,sha256=1rFC8MYKtqbNsQa2KIqwW1tqpaMtFaxuNsallwejXTU,787
22
20
  kscale/web/cli/user.py,sha256=aaJJCL1P5lfhK6ZC9OwOHXKA-I3MWqVZ_k7TYnx33CY,1303
23
21
  kscale/web/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
22
  kscale/web/clients/base.py,sha256=uovIxtkotRxrIFL0PhLYDIhhfXrIGjXxNY2FLVO3L18,15110
25
- kscale/web/clients/client.py,sha256=QjBicdHQYNoUG9XRjAYmGu3THae9DzWa_hQox3OO1Gw,214
23
+ kscale/web/clients/client.py,sha256=rzW2s8T7bKVuybOSQ65-ghl02rcXBoOxnx_nUDwgEPw,362
26
24
  kscale/web/clients/robot.py,sha256=HMfJnkDxaJ_o7X2vdYYS9iob1JRoBG2qiGmQpCQZpAk,1485
27
- kscale/web/clients/robot_class.py,sha256=KnfGUNyaXsnhDODp3KdMOkpydptKbOtmVn-W6Eta-Q0,5036
25
+ kscale/web/clients/robot_class.py,sha256=zCFL-Y_jIfO9_qOwzBAHzfgFxzpltMte4LM5haCC24U,6385
28
26
  kscale/web/clients/user.py,sha256=jsa1_s6qXRM-AGBbHlPhd1NierUtynjY9tVAPNr6_Os,568
29
27
  kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
28
  kscale/web/gen/api.py,sha256=SovcII36JFgK9jd2CXlLPMjiUROGB4vEnapOsYMUrkU,2188
31
- kscale-0.2.0.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
32
- kscale-0.2.0.dist-info/METADATA,sha256=6IsGyUFUz4LwuYhNeyaD86X6KlcoFkGq_zbrFiRXdLE,2340
33
- kscale-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
34
- kscale-0.2.0.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
35
- kscale-0.2.0.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
36
- kscale-0.2.0.dist-info/RECORD,,
29
+ kscale-0.2.2.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
30
+ kscale-0.2.2.dist-info/METADATA,sha256=c0duCLdAcxXv6SXhRpkoSO99-ahyAwV2HRhYiBZXK1k,2340
31
+ kscale-0.2.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
32
+ kscale-0.2.2.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
33
+ kscale-0.2.2.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
34
+ kscale-0.2.2.dist-info/RECORD,,
kscale/api.py DELETED
@@ -1,11 +0,0 @@
1
- """Defines common functionality for the K-Scale API."""
2
-
3
- from kscale.utils.api_base import APIBase
4
- from kscale.web.api import WebAPI
5
-
6
-
7
- class K(
8
- WebAPI,
9
- APIBase,
10
- ):
11
- """Defines a common interface for the K-Scale API."""
kscale/web/api.py DELETED
@@ -1,18 +0,0 @@
1
- """Defines a common interface for the K-Scale WWW API."""
2
-
3
- from kscale.utils.api_base import APIBase
4
- from kscale.web.clients.client import WWWClient
5
- from kscale.web.gen.api import ProfileResponse
6
-
7
-
8
- class WebAPI(APIBase):
9
- async def www_client(self) -> WWWClient:
10
- return WWWClient()
11
-
12
- async def get_profile_info(self) -> ProfileResponse:
13
- client = await self.www_client()
14
- return await client.get_profile_info()
15
-
16
- async def get_api_key(self, num_hours: int = 24) -> str:
17
- client = await self.www_client()
18
- return await client.get_api_key(num_hours)
File without changes