kscale 0.0.5__py3-none-any.whl → 0.0.7__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
kscale/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Defines the common interface for the K-Scale Python API."""
2
2
 
3
- __version__ = "0.0.5"
3
+ __version__ = "0.0.7"
4
4
 
5
5
  from kscale.api import KScale
kscale/store/api.py CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from kscale.store.urdf import download_urdf
5
+ from kscale.store.gen.api import UploadArtifactResponse
6
+ from kscale.store.urdf import download_urdf, upload_urdf
6
7
  from kscale.utils.api_base import APIBase
7
8
 
8
9
 
@@ -18,3 +19,6 @@ class StoreAPI(APIBase):
18
19
 
19
20
  async def urdf(self, artifact_id: str) -> Path:
20
21
  return await download_urdf(artifact_id)
22
+
23
+ async def upload_urdf(self, listing_id: str, root_dir: Path) -> UploadArtifactResponse:
24
+ return await upload_urdf(listing_id, root_dir)
kscale/store/client.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Defines a typed client for the K-Scale Store API."""
2
2
 
3
3
  import logging
4
+ from pathlib import Path
4
5
  from types import TracebackType
5
6
  from typing import Any, Dict, Type
6
7
  from urllib.parse import urljoin
@@ -14,17 +15,18 @@ from kscale.store.gen.api import (
14
15
  SingleArtifactResponse,
15
16
  UploadArtifactResponse,
16
17
  )
17
- from kscale.store.utils import API_ROOT, get_api_key
18
+ from kscale.store.utils import get_api_key, get_api_root
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
21
22
 
22
23
  class KScaleStoreClient:
23
- def __init__(self, base_url: str = API_ROOT) -> None:
24
+ def __init__(self, base_url: str = get_api_root()) -> None:
24
25
  self.base_url = base_url
25
26
  self.client = httpx.AsyncClient(
26
27
  base_url=self.base_url,
27
28
  headers={"Authorization": f"Bearer {get_api_key()}"},
29
+ timeout=httpx.Timeout(30.0),
28
30
  )
29
31
 
30
32
  async def _request(
@@ -55,8 +57,9 @@ class KScaleStoreClient:
55
57
  return SingleArtifactResponse(**data)
56
58
 
57
59
  async def upload_artifact(self, listing_id: str, file_path: str) -> UploadArtifactResponse:
60
+ file_name = Path(file_path).name
58
61
  with open(file_path, "rb") as f:
59
- files = {"files": (f.name, f, "application/gzip")}
62
+ files = {"files": (file_name, f, "application/gzip")}
60
63
  data = await self._request("POST", f"/artifacts/upload/{listing_id}", files=files)
61
64
  return UploadArtifactResponse(**data)
62
65
 
kscale/store/pybullet.py CHANGED
@@ -27,7 +27,10 @@ async def main(args: Sequence[str] | None = None) -> None:
27
27
  parsed_args = parser.parse_args(args)
28
28
 
29
29
  # Gets the URDF path.
30
- urdf_path = await download_urdf(parsed_args.listing_id)
30
+ urdf_dir = await download_urdf(parsed_args.listing_id)
31
+ urdf_path = next(urdf_dir.glob("*.urdf"), None)
32
+ if urdf_path is None:
33
+ raise ValueError(f"No URDF found in {urdf_dir}")
31
34
 
32
35
  try:
33
36
  import pybullet as p # type: ignore[import-not-found]
kscale/store/urdf.py CHANGED
@@ -14,13 +14,24 @@ import requests
14
14
 
15
15
  from kscale.conf import Settings
16
16
  from kscale.store.client import KScaleStoreClient
17
- from kscale.store.gen.api import SingleArtifactResponse
17
+ from kscale.store.gen.api import SingleArtifactResponse, UploadArtifactResponse
18
18
  from kscale.store.utils import get_api_key
19
19
 
20
20
  # Set up logging
21
21
  logging.basicConfig(level=logging.INFO)
22
22
  logger = logging.getLogger(__name__)
23
23
 
24
+ ALLOWED_SUFFIXES = {
25
+ ".urdf",
26
+ ".mjcf",
27
+ ".stl",
28
+ ".obj",
29
+ ".dae",
30
+ ".png",
31
+ ".jpg",
32
+ ".jpeg",
33
+ }
34
+
24
35
 
25
36
  def get_cache_dir() -> Path:
26
37
  return Path(Settings.load().store.cache_dir).expanduser().resolve()
@@ -75,7 +86,7 @@ def create_tarball(folder_path: Path, output_filename: str, cache_dir: Path) ->
75
86
  tarball_path = cache_dir / output_filename
76
87
  with tarfile.open(tarball_path, "w:gz") as tar:
77
88
  for file_path in folder_path.rglob("*"):
78
- if file_path.is_file() and file_path.suffix.lower() in (".urdf", ".mjcf", ".stl", ".obj", ".dae"):
89
+ if file_path.is_file() and file_path.suffix.lower() in ALLOWED_SUFFIXES:
79
90
  tar.add(file_path, arcname=file_path.relative_to(folder_path))
80
91
  logger.info("Added %s to tarball", file_path)
81
92
  else:
@@ -128,18 +139,24 @@ async def remove_local_urdf(artifact_id: str) -> None:
128
139
  raise
129
140
 
130
141
 
131
- async def upload_urdf(listing_id: str, args: Sequence[str]) -> None:
132
- parser = argparse.ArgumentParser(description="K-Scale URDF Store", add_help=False)
133
- parser.add_argument("root_dir", type=Path, help="The path to the root directory to upload")
134
- parsed_args = parser.parse_args(args)
135
-
136
- root_dir = parsed_args.root_dir
142
+ async def upload_urdf(listing_id: str, root_dir: Path) -> UploadArtifactResponse:
137
143
  tarball_path = create_tarball(root_dir, "robot.tgz", get_artifact_dir(listing_id))
138
144
 
139
145
  async with KScaleStoreClient() as client:
140
146
  response = await client.upload_artifact(listing_id, str(tarball_path))
141
147
 
142
148
  logger.info("Uploaded artifacts: %s", [artifact.artifact_id for artifact in response.artifacts])
149
+ return response
150
+
151
+
152
+ async def upload_urdf_cli(listing_id: str, args: Sequence[str]) -> UploadArtifactResponse:
153
+ parser = argparse.ArgumentParser(description="K-Scale URDF Store", add_help=False)
154
+ parser.add_argument("root_dir", type=Path, help="The path to the root directory to upload")
155
+ parsed_args = parser.parse_args(args)
156
+
157
+ root_dir = parsed_args.root_dir
158
+ response = await upload_urdf(listing_id, root_dir)
159
+ return response
143
160
 
144
161
 
145
162
  Command = Literal["download", "info", "upload", "remove-local"]
@@ -165,7 +182,7 @@ async def main(args: Sequence[str] | None = None) -> None:
165
182
  await remove_local_urdf(id)
166
183
 
167
184
  case "upload":
168
- await upload_urdf(id, remaining_args)
185
+ await upload_urdf_cli(id, remaining_args)
169
186
 
170
187
  case _:
171
188
  logger.error("Invalid command")
kscale/store/utils.py CHANGED
@@ -4,10 +4,24 @@ import os
4
4
 
5
5
  from kscale.conf import Settings
6
6
 
7
- API_ROOT = "https://api.kscale.store"
7
+
8
+ def get_api_root() -> str:
9
+ """Returns the base URL for the K-Scale Store API.
10
+
11
+ This can be overridden when targetting a different server.
12
+
13
+ Returns:
14
+ The base URL for the K-Scale Store API.
15
+ """
16
+ return os.getenv("KSCALE_API_ROOT", "https://api.kscale.store")
8
17
 
9
18
 
10
19
  def get_api_key() -> str:
20
+ """Returns the API key for the K-Scale Store API.
21
+
22
+ Returns:
23
+ The API key for the K-Scale Store API.
24
+ """
11
25
  api_key = Settings.load().store.api_key
12
26
  if api_key is None:
13
27
  api_key = os.getenv("KSCALE_API_KEY")
@@ -1,16 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kscale
3
- Version: 0.0.5
3
+ Version: 0.0.7
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: omegaconf ==2.3.0
11
- Requires-Dist: email-validator ==2.2.0
12
- Requires-Dist: httpx ==0.27.0
13
- Requires-Dist: requests ==2.32.2
10
+ Requires-Dist: omegaconf
11
+ Requires-Dist: email-validator
12
+ Requires-Dist: httpx
13
+ Requires-Dist: requests
14
+ Requires-Dist: pydantic
14
15
  Provides-Extra: dev
15
16
  Requires-Dist: black ; extra == 'dev'
16
17
  Requires-Dist: darglint ; extra == 'dev'
@@ -0,0 +1,21 @@
1
+ kscale/__init__.py,sha256=3DfIJ5zf1Jokj7wyBCtRldKZFvBMVODE8CoieJr1Zo4,117
2
+ kscale/api.py,sha256=xBtKj8rgZ400r1Xx9LRY0AzSgIIttoXdejmhHhdVGS0,333
3
+ kscale/conf.py,sha256=9fShFaYTbnrm_eiGjmy8ZtC4Q4m6PQkWPyoF3eNyov8,1424
4
+ kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ kscale/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ kscale/store/api.py,sha256=rydanYGg8zlc_cNuWo9YBG_WVtPKMxFgp6fpec-B8M0,671
7
+ kscale/store/cli.py,sha256=8ygg_1tZzOOHJotEIgSN9pfumcriPmA31sI_FCFQiTo,859
8
+ kscale/store/client.py,sha256=R1IDnf2J4ojAcP8nmUUHfXhcHUt4zP0-mxtVI7MIC5U,2664
9
+ kscale/store/pybullet.py,sha256=WzA1FVAc19JMY9nUar0go9kcy9BvU8W1lnT9eT1W1LI,7638
10
+ kscale/store/urdf.py,sha256=5x8tK2BYv901S_yYWYPWEnHv-3T0ALBQMdDwb70EZFw,6395
11
+ kscale/store/utils.py,sha256=rFXGkem2oAttAf3bhWmFEhxrqYnaVvlVJsC268IMw6Y,906
12
+ kscale/store/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ kscale/store/gen/api.py,sha256=82D41J6pg9KWdgD0lx7NggLcNS32SpnN8DqE3Md6ON0,9559
14
+ kscale/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ kscale/utils/api_base.py,sha256=Kk_WtRDdJHmOg6NtHmVxVrcfARSUkhfr29ypLch_pO0,112
16
+ kscale-0.0.7.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
17
+ kscale-0.0.7.dist-info/METADATA,sha256=JzwigJrhRpaJNCYKDzgam_allQTuY6G10nR4TU31an0,2504
18
+ kscale-0.0.7.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
19
+ kscale-0.0.7.dist-info/entry_points.txt,sha256=PaVs1ivqB0BBdGUsiFkxGUYjGLz05VqagxwRVwi4yV4,54
20
+ kscale-0.0.7.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
21
+ kscale-0.0.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.1)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,21 +0,0 @@
1
- kscale/__init__.py,sha256=e1NdSlxsBKi5qyOIjXC3vAPQXleZwh2hyVlLHcKQsq8,117
2
- kscale/api.py,sha256=xBtKj8rgZ400r1Xx9LRY0AzSgIIttoXdejmhHhdVGS0,333
3
- kscale/conf.py,sha256=9fShFaYTbnrm_eiGjmy8ZtC4Q4m6PQkWPyoF3eNyov8,1424
4
- kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- kscale/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- kscale/store/api.py,sha256=L-QZMDeOsNo8ovIRjwfBlidYKD510hdoKrmrue7C5Wk,454
7
- kscale/store/cli.py,sha256=8ygg_1tZzOOHJotEIgSN9pfumcriPmA31sI_FCFQiTo,859
8
- kscale/store/client.py,sha256=HEyTzgCOXdLCQKdFKwNglvTBtXVipul7cxbdjz53Pb4,2544
9
- kscale/store/pybullet.py,sha256=viTQCE2jT72miPKZpFKj4f4zGLLYtjhRFxGqzxd2Z8M,7504
10
- kscale/store/urdf.py,sha256=zMUfJNNpZkoLf-SnhMO28f8Qj39hmZrA0vqLL3sZSqE,6069
11
- kscale/store/utils.py,sha256=vQSFd9fByDUUSD0dAA65T_WI5R55vY-HOQjIJg_u2jw,536
12
- kscale/store/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- kscale/store/gen/api.py,sha256=82D41J6pg9KWdgD0lx7NggLcNS32SpnN8DqE3Md6ON0,9559
14
- kscale/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- kscale/utils/api_base.py,sha256=Kk_WtRDdJHmOg6NtHmVxVrcfARSUkhfr29ypLch_pO0,112
16
- kscale-0.0.5.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
17
- kscale-0.0.5.dist-info/METADATA,sha256=jAkpxXCrUa8b30BY32OaW6eIoR0VFHQzKgzO26EDFGM,2514
18
- kscale-0.0.5.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
19
- kscale-0.0.5.dist-info/entry_points.txt,sha256=PaVs1ivqB0BBdGUsiFkxGUYjGLz05VqagxwRVwi4yV4,54
20
- kscale-0.0.5.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
21
- kscale-0.0.5.dist-info/RECORD,,