kscale 0.0.2__tar.gz → 0.0.3__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. {kscale-0.0.2/kscale.egg-info → kscale-0.0.3}/PKG-INFO +1 -1
  2. kscale-0.0.3/kscale/__init__.py +1 -0
  3. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/cli.py +9 -4
  4. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/pybullet.py +11 -10
  5. kscale-0.0.3/kscale/store/urdf.py +213 -0
  6. {kscale-0.0.2 → kscale-0.0.3/kscale.egg-info}/PKG-INFO +1 -1
  7. kscale-0.0.3/kscale.egg-info/entry_points.txt +2 -0
  8. {kscale-0.0.2 → kscale-0.0.3}/setup.cfg +1 -1
  9. {kscale-0.0.2 → kscale-0.0.3}/setup.py +1 -0
  10. kscale-0.0.2/kscale/__init__.py +0 -1
  11. kscale-0.0.2/kscale/store/urdf.py +0 -174
  12. kscale-0.0.2/kscale.egg-info/entry_points.txt +0 -2
  13. {kscale-0.0.2 → kscale-0.0.3}/LICENSE +0 -0
  14. {kscale-0.0.2 → kscale-0.0.3}/MANIFEST.in +0 -0
  15. {kscale-0.0.2 → kscale-0.0.3}/README.md +0 -0
  16. {kscale-0.0.2 → kscale-0.0.3}/kscale/conf.py +0 -0
  17. {kscale-0.0.2 → kscale-0.0.3}/kscale/formats/mjcf.py +0 -0
  18. {kscale-0.0.2 → kscale-0.0.3}/kscale/py.typed +0 -0
  19. {kscale-0.0.2 → kscale-0.0.3}/kscale/requirements-dev.txt +0 -0
  20. {kscale-0.0.2 → kscale-0.0.3}/kscale/requirements.txt +0 -0
  21. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/__init__.py +0 -0
  22. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/bullet/MANIFEST.in +0 -0
  23. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/gen/__init__.py +0 -0
  24. {kscale-0.0.2 → kscale-0.0.3}/kscale/store/gen/api.py +0 -0
  25. {kscale-0.0.2 → kscale-0.0.3}/kscale.egg-info/SOURCES.txt +0 -0
  26. {kscale-0.0.2 → kscale-0.0.3}/kscale.egg-info/dependency_links.txt +0 -0
  27. {kscale-0.0.2 → kscale-0.0.3}/kscale.egg-info/requires.txt +0 -0
  28. {kscale-0.0.2 → kscale-0.0.3}/kscale.egg-info/top_level.txt +0 -0
  29. {kscale-0.0.2 → kscale-0.0.3}/pyproject.toml +0 -0
  30. {kscale-0.0.2 → kscale-0.0.3}/tests/test_dummy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kscale
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
@@ -0,0 +1 @@
1
+ __version__ = "0.0.3"
@@ -1,12 +1,13 @@
1
1
  """Defines the top-level KOL CLI."""
2
2
 
3
3
  import argparse
4
+ import asyncio
4
5
  from typing import Sequence
5
6
 
6
7
  from kscale.store import pybullet, urdf
7
8
 
8
9
 
9
- def main(args: Sequence[str] | None = None) -> None:
10
+ async def main(args: Sequence[str] | None = None) -> None:
10
11
  parser = argparse.ArgumentParser(description="K-Scale OnShape Library", add_help=False)
11
12
  parser.add_argument(
12
13
  "subcommand",
@@ -20,11 +21,15 @@ def main(args: Sequence[str] | None = None) -> None:
20
21
 
21
22
  match parsed_args.subcommand:
22
23
  case "urdf":
23
- urdf.main(remaining_args)
24
+ await urdf.main(remaining_args)
24
25
  case "pybullet":
25
- pybullet.main(remaining_args)
26
+ await pybullet.main(remaining_args)
27
+
28
+
29
+ def sync_main(args: Sequence[str] | None = None) -> None:
30
+ asyncio.run(main(args))
26
31
 
27
32
 
28
33
  if __name__ == "__main__":
29
34
  # python3 -m kscale.store.cli
30
- main()
35
+ sync_main()
@@ -1,6 +1,7 @@
1
1
  """Simple script to interact with a URDF in PyBullet."""
2
2
 
3
3
  import argparse
4
+ import asyncio
4
5
  import itertools
5
6
  import logging
6
7
  import math
@@ -8,12 +9,14 @@ import time
8
9
  from pathlib import Path
9
10
  from typing import Sequence
10
11
 
12
+ from kscale.store.urdf import download_urdf
13
+
11
14
  logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
- def main(args: Sequence[str] | None = None) -> None:
17
+ async def main(args: Sequence[str] | None = None) -> None:
15
18
  parser = argparse.ArgumentParser(description="Show a URDF in PyBullet")
16
- parser.add_argument("urdf", nargs="?", help="Path to the URDF file")
19
+ parser.add_argument("listing_id", help="Listing ID for the URDF")
17
20
  parser.add_argument("--dt", type=float, default=0.01, help="Time step")
18
21
  parser.add_argument("-n", "--hide-gui", action="store_true", help="Hide the GUI")
19
22
  parser.add_argument("--no-merge", action="store_true", help="Do not merge fixed links")
@@ -23,6 +26,11 @@ def main(args: Sequence[str] | None = None) -> None:
23
26
  parser.add_argument("--show-collision", action="store_true", help="Show collision meshes")
24
27
  parsed_args = parser.parse_args(args)
25
28
 
29
+ # Gets the URDF path.
30
+ urdf_path = await download_urdf(parsed_args.listing_id)
31
+
32
+ breakpoint()
33
+
26
34
  try:
27
35
  import pybullet as p # type: ignore[import-not-found]
28
36
  except ImportError:
@@ -46,13 +54,6 @@ def main(args: Sequence[str] | None = None) -> None:
46
54
  # Loads the floor plane.
47
55
  floor = p.loadURDF(str((Path(__file__).parent / "bullet" / "plane.urdf").resolve()))
48
56
 
49
- urdf_path = Path("robot" if parsed_args.urdf is None else parsed_args.urdf)
50
- if urdf_path.is_dir():
51
- try:
52
- urdf_path = next(urdf_path.glob("*.urdf"))
53
- except StopIteration:
54
- raise FileNotFoundError(f"No URDF files found in {urdf_path}")
55
-
56
57
  # Load the robot URDF.
57
58
  start_position = [0.0, 0.0, 1.0]
58
59
  start_orientation = p.getQuaternionFromEuler([0.0, 0.0, 0.0])
@@ -175,4 +176,4 @@ def main(args: Sequence[str] | None = None) -> None:
175
176
 
176
177
  if __name__ == "__main__":
177
178
  # python -m kscale.store.pybullet
178
- main()
179
+ asyncio.run(main())
@@ -0,0 +1,213 @@
1
+ """Utility functions for managing artifacts in the K-Scale store."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import logging
6
+ import os
7
+ import shutil
8
+ import sys
9
+ import tarfile
10
+ from pathlib import Path
11
+ from typing import Literal, Sequence, get_args
12
+
13
+ import httpx
14
+ import requests
15
+
16
+ from kscale.conf import Settings
17
+ from kscale.store.gen.api import UrdfResponse
18
+
19
+ # Set up logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def get_api_key() -> str:
25
+ api_key = Settings.load().store.api_key
26
+ if not api_key:
27
+ raise ValueError(
28
+ "API key not found! Get one here and set it as the `KSCALE_API_KEY` environment variable or in your "
29
+ "config file: https://kscale.store/keys"
30
+ )
31
+ return api_key
32
+
33
+
34
+ def get_cache_dir() -> Path:
35
+ return Path(Settings.load().store.cache_dir).expanduser().resolve()
36
+
37
+
38
+ def get_listing_dir(listing_id: str) -> Path:
39
+ (cache_dir := get_cache_dir() / listing_id).mkdir(parents=True, exist_ok=True)
40
+ return cache_dir
41
+
42
+
43
+ def fetch_urdf_info(listing_id: str) -> UrdfResponse:
44
+ url = f"https://api.kscale.store/urdf/info/{listing_id}"
45
+ headers = {
46
+ "Authorization": f"Bearer {get_api_key()}",
47
+ }
48
+ response = requests.get(url, headers=headers)
49
+ response.raise_for_status()
50
+ return UrdfResponse(**response.json())
51
+
52
+
53
+ async def download_artifact(artifact_url: str, cache_dir: Path) -> Path:
54
+ filename = os.path.join(cache_dir, artifact_url.split("/")[-1])
55
+ headers = {
56
+ "Authorization": f"Bearer {get_api_key()}",
57
+ }
58
+
59
+ if not os.path.exists(filename):
60
+ logger.info("Downloading artifact from %s", artifact_url)
61
+
62
+ async with httpx.AsyncClient() as client:
63
+ response = await client.get(artifact_url, headers=headers)
64
+ response.raise_for_status()
65
+ with open(filename, "wb") as f:
66
+ for chunk in response.iter_bytes(chunk_size=8192):
67
+ f.write(chunk)
68
+ logger.info("Artifact downloaded to %s", filename)
69
+ else:
70
+ logger.info("Artifact already cached at %s", filename)
71
+
72
+ # Extract the .tgz file
73
+ extract_dir = cache_dir / os.path.splitext(os.path.basename(filename))[0]
74
+ if not extract_dir.exists():
75
+ logger.info("Extracting %s to %s", filename, extract_dir)
76
+ with tarfile.open(filename, "r:gz") as tar:
77
+ tar.extractall(path=extract_dir)
78
+ else:
79
+ logger.info("Artifact already extracted at %s", extract_dir)
80
+
81
+ return extract_dir
82
+
83
+
84
+ def create_tarball(folder_path: str | Path, output_filename: str, cache_dir: Path) -> str:
85
+ tarball_path = os.path.join(cache_dir, output_filename)
86
+ with tarfile.open(tarball_path, "w:gz") as tar:
87
+ for root, _, files in os.walk(folder_path):
88
+ for file in files:
89
+ file_path = os.path.join(root, file)
90
+ arcname = os.path.relpath(file_path, start=folder_path)
91
+ tar.add(file_path, arcname=arcname)
92
+ logger.info("Added %s as %s", file_path, arcname)
93
+ logger.info("Created tarball %s", tarball_path)
94
+ return tarball_path
95
+
96
+
97
+ async def upload_artifact(tarball_path: str, listing_id: str) -> None:
98
+ url = f"https://api.kscale.store/urdf/upload/{listing_id}"
99
+ headers = {
100
+ "Authorization": f"Bearer {get_api_key()}",
101
+ }
102
+
103
+ async with httpx.AsyncClient() as client:
104
+ with open(tarball_path, "rb") as f:
105
+ files = {"file": (f.name, f, "application/gzip")}
106
+ response = await client.post(url, headers=headers, files=files)
107
+
108
+ response.raise_for_status()
109
+
110
+ logger.info("Uploaded artifact to %s", url)
111
+
112
+
113
+ async def download_urdf(listing_id: str) -> Path:
114
+ try:
115
+ urdf_info = fetch_urdf_info(listing_id)
116
+
117
+ if urdf_info.urdf is None:
118
+ breakpoint()
119
+ raise ValueError(f"No URDF found for listing {listing_id}")
120
+
121
+ artifact_url = urdf_info.urdf.url
122
+ return await download_artifact(artifact_url, get_listing_dir(listing_id))
123
+
124
+ except requests.RequestException:
125
+ logger.exception("Failed to fetch URDF info")
126
+ raise
127
+
128
+
129
+ async def show_urdf_info(listing_id: str) -> None:
130
+ try:
131
+ urdf_info = fetch_urdf_info(listing_id)
132
+
133
+ if urdf_info.urdf:
134
+ logger.info("URDF Artifact ID: %s", urdf_info.urdf.artifact_id)
135
+ logger.info("URDF URL: %s", urdf_info.urdf.url)
136
+ else:
137
+ logger.info("No URDF found for listing %s", listing_id)
138
+ except requests.RequestException:
139
+ logger.exception("Failed to fetch URDF info")
140
+ raise
141
+
142
+
143
+ async def upload_urdf(listing_id: str, args: Sequence[str] | None = None) -> None:
144
+ parser = argparse.ArgumentParser(description="Upload a URDF artifact to the K-Scale store")
145
+ parser.add_argument("folder_path", help="The path to the folder containing the URDF files")
146
+ parsed_args = parser.parse_args(args)
147
+ folder_path = Path(parsed_args.folder_path).expanduser().resolve()
148
+
149
+ output_filename = f"{listing_id}.tgz"
150
+ tarball_path = create_tarball(folder_path, output_filename, get_listing_dir(listing_id))
151
+
152
+ try:
153
+ fetch_urdf_info(listing_id)
154
+ await upload_artifact(tarball_path, listing_id)
155
+ except requests.RequestException:
156
+ logger.exception("Failed to upload artifact")
157
+ raise
158
+
159
+
160
+ async def remove_local_urdf(listing_id: str) -> None:
161
+ try:
162
+ if listing_id.lower() == "all":
163
+ cache_dir = get_cache_dir()
164
+ if cache_dir.exists():
165
+ logger.info("Removing all local caches at %s", cache_dir)
166
+ shutil.rmtree(cache_dir)
167
+ else:
168
+ logger.error("No local caches found")
169
+ else:
170
+ listing_dir = get_listing_dir(listing_id)
171
+ if listing_dir.exists():
172
+ logger.info("Removing local cache at %s", listing_dir)
173
+ shutil.rmtree(listing_dir)
174
+ else:
175
+ logger.error("No local cache found for listing %s", listing_id)
176
+
177
+ except Exception:
178
+ logger.error("Failed to remove local cache")
179
+ raise
180
+
181
+
182
+ Command = Literal["download", "info", "upload", "remove-local"]
183
+
184
+
185
+ async def main(args: Sequence[str] | None = None) -> None:
186
+ parser = argparse.ArgumentParser(description="K-Scale URDF Store", add_help=False)
187
+ parser.add_argument("command", choices=get_args(Command), help="The command to run")
188
+ parser.add_argument("listing_id", help="The listing ID to operate on")
189
+ parsed_args, remaining_args = parser.parse_known_args(args)
190
+
191
+ command: Command = parsed_args.command
192
+ listing_id: str = parsed_args.listing_id
193
+
194
+ match command:
195
+ case "download":
196
+ await download_urdf(listing_id)
197
+
198
+ case "info":
199
+ await show_urdf_info(listing_id)
200
+
201
+ case "upload":
202
+ await upload_urdf(listing_id, remaining_args)
203
+
204
+ case "remove-local":
205
+ await remove_local_urdf(listing_id)
206
+
207
+ case _:
208
+ logger.error("Invalid command")
209
+ sys.exit(1)
210
+
211
+
212
+ if __name__ == "__main__":
213
+ asyncio.run(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kscale
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kscale = kscale.store.cli:sync_main
@@ -7,7 +7,7 @@ exclude =
7
7
 
8
8
  [options.entry_points]
9
9
  console_scripts =
10
- kscale = kscale.store.cli:main
10
+ kscale = kscale.store.cli:sync_main
11
11
 
12
12
  [egg_info]
13
13
  tag_build =
@@ -29,6 +29,7 @@ setup(
29
29
  version=version,
30
30
  description="The kscale project",
31
31
  author="Benjamin Bolte",
32
+ license_files=("LICENSE",),
32
33
  url="https://github.com/kscalelabs/kscale",
33
34
  long_description=long_description,
34
35
  long_description_content_type="text/markdown",
@@ -1 +0,0 @@
1
- __version__ = "0.0.2"
@@ -1,174 +0,0 @@
1
- """Utility functions for managing artifacts in the K-Scale store."""
2
-
3
- import argparse
4
- import asyncio
5
- import logging
6
- import os
7
- import sys
8
- import tarfile
9
- from pathlib import Path
10
- from typing import Literal, Sequence
11
-
12
- import httpx
13
- import requests
14
-
15
- from kscale.conf import Settings
16
- from kscale.store.gen.api import UrdfResponse
17
-
18
- # Set up logging
19
- logging.basicConfig(level=logging.INFO)
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- def get_api_key() -> str:
24
- api_key = Settings.load().store.api_key
25
- if not api_key:
26
- raise ValueError(
27
- "API key not found! Get one here and set it as the `KSCALE_API_KEY` environment variable or in your"
28
- "config file: https://kscale.store/keys"
29
- )
30
- return api_key
31
-
32
-
33
- def get_cache_dir() -> Path:
34
- return Path(Settings.load().store.cache_dir).expanduser().resolve()
35
-
36
-
37
- def fetch_urdf_info(listing_id: str) -> UrdfResponse:
38
- url = f"https://api.kscale.store/urdf/info/{listing_id}"
39
- headers = {
40
- "Authorization": f"Bearer {get_api_key()}",
41
- }
42
- response = requests.get(url, headers=headers)
43
- response.raise_for_status()
44
- return UrdfResponse(**response.json())
45
-
46
-
47
- async def download_artifact(artifact_url: str, cache_dir: Path) -> str:
48
- filename = os.path.join(cache_dir, artifact_url.split("/")[-1])
49
- headers = {
50
- "Authorization": f"Bearer {get_api_key()}",
51
- }
52
-
53
- if not os.path.exists(filename):
54
- logger.info("Downloading artifact from %s" % artifact_url)
55
-
56
- async with httpx.AsyncClient() as client:
57
- response = await client.get(artifact_url, headers=headers)
58
- response.raise_for_status()
59
- with open(filename, "wb") as f:
60
- for chunk in response.iter_bytes(chunk_size=8192):
61
- f.write(chunk)
62
- logger.info("Artifact downloaded to %s" % filename)
63
- else:
64
- logger.info("Artifact already cached at %s" % filename)
65
-
66
- # Extract the .tgz file
67
- extract_dir = os.path.join(cache_dir, os.path.splitext(os.path.basename(filename))[0])
68
- if not os.path.exists(extract_dir):
69
- logger.info(f"Extracting {filename} to {extract_dir}")
70
- with tarfile.open(filename, "r:gz") as tar:
71
- tar.extractall(path=extract_dir)
72
- logger.info("Extraction complete")
73
- else:
74
- logger.info("Artifact already extracted at %s" % extract_dir)
75
-
76
- return extract_dir
77
-
78
-
79
- def create_tarball(folder_path: str | Path, output_filename: str, cache_dir: Path) -> str:
80
- tarball_path = os.path.join(cache_dir, output_filename)
81
- with tarfile.open(tarball_path, "w:gz") as tar:
82
- for root, _, files in os.walk(folder_path):
83
- for file in files:
84
- file_path = os.path.join(root, file)
85
- arcname = os.path.relpath(file_path, start=folder_path)
86
- tar.add(file_path, arcname=arcname)
87
- logger.info("Added %s as %s" % (file_path, arcname))
88
- logger.info("Created tarball %s" % tarball_path)
89
- return tarball_path
90
-
91
-
92
- async def upload_artifact(tarball_path: str, listing_id: str) -> None:
93
- url = f"https://api.kscale.store/urdf/upload/{listing_id}"
94
- headers = {
95
- "Authorization": f"Bearer {get_api_key()}",
96
- }
97
-
98
- async with httpx.AsyncClient() as client:
99
- with open(tarball_path, "rb") as f:
100
- files = {"file": (f.name, f, "application/gzip")}
101
- response = await client.post(url, headers=headers, files=files)
102
-
103
- response.raise_for_status()
104
-
105
- logger.info("Uploaded artifact to %s" % url)
106
-
107
-
108
- def main(args: Sequence[str] | None = None) -> None:
109
- parser = argparse.ArgumentParser(description="K-Scale URDF Store", add_help=False)
110
- parser.add_argument(
111
- "command",
112
- choices=["get", "info", "upload"],
113
- help="The command to run",
114
- )
115
- parser.add_argument("listing_id", help="The listing ID to operate on")
116
- parsed_args, remaining_args = parser.parse_known_args(args)
117
-
118
- command: Literal["get", "info", "upload"] = parsed_args.command
119
- listing_id: str = parsed_args.listing_id
120
-
121
- def get_listing_dir() -> Path:
122
- (cache_dir := get_cache_dir() / listing_id).mkdir(parents=True, exist_ok=True)
123
- return cache_dir
124
-
125
- match command:
126
- case "get":
127
- try:
128
- urdf_info = fetch_urdf_info(listing_id)
129
-
130
- if urdf_info.urdf:
131
- artifact_url = urdf_info.urdf.url
132
- asyncio.run(download_artifact(artifact_url, get_listing_dir()))
133
- else:
134
- logger.info("No URDF found for listing %s" % listing_id)
135
- except requests.RequestException as e:
136
- logger.error("Failed to fetch URDF info: %s" % e)
137
- sys.exit(1)
138
-
139
- case "info":
140
- try:
141
- urdf_info = fetch_urdf_info(listing_id)
142
-
143
- if urdf_info.urdf:
144
- logger.info("URDF Artifact ID: %s" % urdf_info.urdf.artifact_id)
145
- logger.info("URDF URL: %s" % urdf_info.urdf.url)
146
- else:
147
- logger.info("No URDF found for listing %s" % listing_id)
148
- except requests.RequestException as e:
149
- logger.error("Failed to fetch URDF info: %s" % e)
150
- sys.exit(1)
151
-
152
- case "upload":
153
- parser = argparse.ArgumentParser(description="Upload a URDF artifact to the K-Scale store")
154
- parser.add_argument("folder_path", help="The path to the folder containing the URDF files")
155
- parsed_args = parser.parse_args(remaining_args)
156
- folder_path = Path(parsed_args.folder_path).expanduser().resolve()
157
-
158
- output_filename = f"{listing_id}.tgz"
159
- tarball_path = create_tarball(folder_path, output_filename, get_listing_dir())
160
-
161
- try:
162
- urdf_info = fetch_urdf_info(listing_id)
163
- asyncio.run(upload_artifact(tarball_path, listing_id))
164
- except requests.RequestException as e:
165
- logger.error("Failed to upload artifact: %s" % e)
166
- sys.exit(1)
167
-
168
- case _:
169
- logger.error("Invalid command")
170
- sys.exit(1)
171
-
172
-
173
- if __name__ == "__main__":
174
- main()
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- kscale = kscale.store.cli:main
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes