kscale 0.1.0__py3-none-any.whl → 0.1.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,6 +1,6 @@
1
1
  """Defines the common interface for the K-Scale Python API."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.2"
4
4
 
5
5
  from pathlib import Path
6
6
 
kscale/conf.py CHANGED
@@ -22,6 +22,7 @@ def get_path() -> Path:
22
22
  class WWWSettings:
23
23
  api_root: str = field(default=DEFAULT_API_ROOT)
24
24
  cache_dir: str = field(default=II("oc.env:KSCALE_CACHE_DIR,'~/.kscale/cache/'"))
25
+ refresh_interval_minutes: int = field(default=60 * 24)
25
26
 
26
27
 
27
28
  @dataclass
kscale/requirements.txt CHANGED
@@ -3,7 +3,6 @@
3
3
  # Configuration
4
4
  omegaconf
5
5
  email_validator
6
- colorlogging
7
6
 
8
7
  # HTTP requests
9
8
  aiohttp
@@ -17,6 +16,7 @@ yarl
17
16
  # CLI
18
17
  aiofiles
19
18
  click
19
+ colorlogging
20
20
  tabulate
21
21
 
22
22
  # Async
@@ -1,6 +1,7 @@
1
1
  """Defines the client for interacting with the K-Scale robot class endpoints."""
2
2
 
3
3
  import hashlib
4
+ import json
4
5
  import logging
5
6
  from pathlib import Path
6
7
 
@@ -8,10 +9,13 @@ import httpx
8
9
 
9
10
  from kscale.web.clients.base import BaseClient
10
11
  from kscale.web.gen.api import RobotClass, RobotDownloadURDFResponse, RobotUploadURDFResponse
11
- from kscale.web.utils import get_cache_dir
12
+ from kscale.web.utils import get_cache_dir, should_refresh_file
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
16
+ UPLOAD_TIMEOUT = 300.0
17
+ DOWNLOAD_TIMEOUT = 60.0
18
+
15
19
 
16
20
  class RobotClassClient(BaseClient):
17
21
  async def get_robot_classes(self) -> list[RobotClass]:
@@ -77,7 +81,9 @@ class RobotClassClient(BaseClient):
77
81
  auth=True,
78
82
  )
79
83
  response = RobotUploadURDFResponse.model_validate(data)
80
- async with httpx.AsyncClient() as client:
84
+ async with httpx.AsyncClient(
85
+ timeout=httpx.Timeout(UPLOAD_TIMEOUT),
86
+ ) as client:
81
87
  async with client.stream(
82
88
  "PUT",
83
89
  response.url,
@@ -89,15 +95,26 @@ class RobotClassClient(BaseClient):
89
95
 
90
96
  async def download_robot_class_urdf(self, class_name: str, *, cache: bool = True) -> Path:
91
97
  cache_path = get_cache_dir() / class_name / "robot.tgz"
92
- if cache and cache_path.exists():
98
+ if cache and cache_path.exists() and not should_refresh_file(cache_path):
93
99
  return cache_path
94
100
  data = await self._request("GET", f"/robots/urdf/{class_name}", auth=True)
95
101
  response = RobotDownloadURDFResponse.model_validate(data)
96
102
  expected_hash = response.md5_hash
97
103
  cache_path.parent.mkdir(parents=True, exist_ok=True)
98
104
 
105
+ # Checks the md5 hash of the file.
106
+ cache_path_info = cache_path.parent / "info.json"
107
+ if cache_path_info.exists():
108
+ with open(cache_path_info, "r") as f:
109
+ info = json.load(f)
110
+ if info["md5_hash"] == expected_hash:
111
+ cache_path.touch()
112
+ return cache_path
113
+
99
114
  logger.info("Downloading URDF file from %s", response.url)
100
- async with httpx.AsyncClient() as client:
115
+ async with httpx.AsyncClient(
116
+ timeout=httpx.Timeout(DOWNLOAD_TIMEOUT),
117
+ ) as client:
101
118
  with open(cache_path, "wb") as file:
102
119
  hash_value = hashlib.md5()
103
120
  async with client.stream("GET", response.url) as r:
@@ -111,4 +128,10 @@ class RobotClassClient(BaseClient):
111
128
  if hash_value_hex != expected_hash:
112
129
  raise ValueError(f"MD5 hash mismatch: {hash_value_hex} != {expected_hash}")
113
130
 
131
+ # Updates the info file.
132
+ logger.info("Updating downloaded file information")
133
+ info = {"md5_hash": hash_value_hex}
134
+ with open(cache_path_info, "w") as f:
135
+ json.dump(info, f)
136
+
114
137
  return cache_path
kscale/web/utils.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import functools
4
4
  import logging
5
+ import time
5
6
  from pathlib import Path
6
7
 
7
8
  from kscale.conf import Settings
@@ -17,6 +18,11 @@ def get_cache_dir() -> Path:
17
18
  return Path(Settings.load().www.cache_dir).expanduser().resolve()
18
19
 
19
20
 
21
+ def should_refresh_file(file: Path) -> bool:
22
+ """Returns whether the file should be refreshed."""
23
+ return file.exists() and file.stat().st_mtime < time.time() - Settings.load().www.refresh_interval_minutes * 60
24
+
25
+
20
26
  @functools.lru_cache
21
27
  def get_artifact_dir(artifact_id: str) -> Path:
22
28
  """Returns the directory for a specific artifact."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kscale
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
@@ -9,7 +9,6 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: omegaconf
11
11
  Requires-Dist: email_validator
12
- Requires-Dist: colorlogging
13
12
  Requires-Dist: aiohttp
14
13
  Requires-Dist: cryptography
15
14
  Requires-Dist: httpx
@@ -19,6 +18,7 @@ Requires-Dist: requests
19
18
  Requires-Dist: yarl
20
19
  Requires-Dist: aiofiles
21
20
  Requires-Dist: click
21
+ Requires-Dist: colorlogging
22
22
  Requires-Dist: tabulate
23
23
  Requires-Dist: async-lru
24
24
  Requires-Dist: krec
@@ -1,10 +1,10 @@
1
- kscale/__init__.py,sha256=2jGtLxds4EdX8FEGT3yA74b0VgrkOEBx_PxiWvoh924,172
1
+ kscale/__init__.py,sha256=Do_oyhXSMNLURbvLDeTx4nJqgYgPhuREBynKDoeJyX4,172
2
2
  kscale/api.py,sha256=jmiuFurTN_Gj_-k-6asqxw8wp-_bgJUXgMPFgJ4lqHA,230
3
3
  kscale/cli.py,sha256=PMHLKR5UwdbbReVmqHXpJ-K9-mGHv_0I7KQkwxmFcUA,881
4
- kscale/conf.py,sha256=i5gDTs8D73l2OvX4pOlPWtT2PC26MmoKw3PPwf8HM7U,1526
4
+ kscale/conf.py,sha256=x9jJ8pmSweUsLScN741B21SySCrnRZioV-1ZkhmERbI,1585
5
5
  kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  kscale/requirements-dev.txt,sha256=WI7-ea4IRJakmqVMN8QKhOsDGrghwtvk03aIsFaNSIw,130
7
- kscale/requirements.txt,sha256=lKDpyebp9nHZ2uHIuS6FQ6SAg7YO_0sDyb67MFmP4h4,214
7
+ kscale/requirements.txt,sha256=_BGbnKTQaXKx0bNEG0wguod9swsiCb2mF6rLm7sFJ2Q,214
8
8
  kscale/artifacts/__init__.py,sha256=RK8wdybtCJPgdLLJ8R8-YMi1Ph5ojqAKVJZowHONtgo,232
9
9
  kscale/artifacts/plane.obj,sha256=x59-IIrWpLjhotChiqT2Ul6U8s0RcHkaEeUZb4KXL1c,348
10
10
  kscale/artifacts/plane.urdf,sha256=LCiTk14AyTHjkZ1jvsb0hNaEaJUxDb8Z1JjsgpXu3YM,819
@@ -14,7 +14,7 @@ kscale/utils/checksum.py,sha256=jt6QmmQND9zrOEnUtOfZpLYROhgto4Gh3OpdUWk4tZA,1093
14
14
  kscale/utils/cli.py,sha256=JoaY5x5SdUx97KmMM9j5AjRRUqqrTlJ9qVckZptEsYA,827
15
15
  kscale/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  kscale/web/api.py,sha256=YioIdruq7LCKSBf9SvOGkv914W36_zBmpTzsJqKc0wE,439
17
- kscale/web/utils.py,sha256=KFB9lrgn_2BRY38Sfbb_QOKZ8fWyINqIqLPwN0rjbyk,806
17
+ kscale/web/utils.py,sha256=l_m8GETCThhnXbHfXq53j1b-E__uZ5GuUbsQ8HR0WA0,1037
18
18
  kscale/web/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  kscale/web/cli/robot.py,sha256=rI-A4_0uvJPeA71Apl4Z3mV5fIfWkgmzT9JRmJYxz3A,3307
20
20
  kscale/web/cli/robot_class.py,sha256=ymC5phUqofvOXv5P6f51b9lMK5eRDaavvnzS0x9rDbU,3574
@@ -24,13 +24,13 @@ kscale/web/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
24
24
  kscale/web/clients/base.py,sha256=NRqRH2JTXjJLUMDM93txKgwFu9bQRYhCYLYy7vE76Ik,11462
25
25
  kscale/web/clients/client.py,sha256=QjBicdHQYNoUG9XRjAYmGu3THae9DzWa_hQox3OO1Gw,214
26
26
  kscale/web/clients/robot.py,sha256=HMfJnkDxaJ_o7X2vdYYS9iob1JRoBG2qiGmQpCQZpAk,1485
27
- kscale/web/clients/robot_class.py,sha256=yC0G4pVPDoEskf0QfQLxejyDgHLT8gR1RLJ8ioCGoOM,4236
27
+ kscale/web/clients/robot_class.py,sha256=vFXlvwQBmt7zEOixAI-mByBEkXCMZIW5OZjiESMavps,5050
28
28
  kscale/web/clients/user.py,sha256=9iv8J-ROm_yBIwi-0oqldReLkNBFktdHRv3UCOxBzjY,377
29
29
  kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  kscale/web/gen/api.py,sha256=SovcII36JFgK9jd2CXlLPMjiUROGB4vEnapOsYMUrkU,2188
31
- kscale-0.1.0.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
32
- kscale-0.1.0.dist-info/METADATA,sha256=EBGeAHNhtgjRQ38M_3n_mQ1LFlaogmA3qgUsVa45zEA,2340
33
- kscale-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
34
- kscale-0.1.0.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
35
- kscale-0.1.0.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
36
- kscale-0.1.0.dist-info/RECORD,,
31
+ kscale-0.1.2.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
32
+ kscale-0.1.2.dist-info/METADATA,sha256=YEcA0zqZwXvW9BnfZn51PrTXcwemCxMuolC1jB7NG-g,2340
33
+ kscale-0.1.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
34
+ kscale-0.1.2.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
35
+ kscale-0.1.2.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
36
+ kscale-0.1.2.dist-info/RECORD,,
File without changes