kscale 0.0.13__cp311-cp311-macosx_11_0_arm64.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 +9 -0
- kscale/api.py +14 -0
- kscale/artifacts/__init__.py +8 -0
- kscale/artifacts/plane.obj +18 -0
- kscale/artifacts/plane.urdf +28 -0
- kscale/cli.py +25 -0
- kscale/conf.py +45 -0
- kscale/py.typed +0 -0
- kscale/requirements-dev.txt +11 -0
- kscale/requirements.txt +17 -0
- kscale/rust.cpython-311-darwin.so +0 -0
- kscale/utils/__init__.py +0 -0
- kscale/utils/api_base.py +6 -0
- kscale/utils/checksum.py +41 -0
- kscale/utils/cli.py +28 -0
- kscale/web/__init__.py +0 -0
- kscale/web/api.py +98 -0
- kscale/web/gen/__init__.py +0 -0
- kscale/web/gen/api.py +612 -0
- kscale/web/kernels.py +207 -0
- kscale/web/krec.py +175 -0
- kscale/web/pybullet.py +188 -0
- kscale/web/urdf.py +185 -0
- kscale/web/utils.py +48 -0
- kscale/web/www_client.py +134 -0
- kscale-0.0.13.dist-info/LICENSE +21 -0
- kscale-0.0.13.dist-info/METADATA +55 -0
- kscale-0.0.13.dist-info/RECORD +31 -0
- kscale-0.0.13.dist-info/WHEEL +5 -0
- kscale-0.0.13.dist-info/entry_points.txt +2 -0
- kscale-0.0.13.dist-info/top_level.txt +1 -0
kscale/__init__.py
ADDED
kscale/api.py
ADDED
@@ -0,0 +1,14 @@
|
|
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."""
|
12
|
+
|
13
|
+
def __init__(self, api_key: str | None = None) -> None:
|
14
|
+
self.api_key = api_key
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Blender v2.66 (sub 1) OBJ File: ''
|
2
|
+
# www.blender.org
|
3
|
+
mtllib plane.mtl
|
4
|
+
o Plane
|
5
|
+
v 15.000000 -15.000000 0.000000
|
6
|
+
v 15.000000 15.000000 0.000000
|
7
|
+
v -15.000000 15.000000 0.000000
|
8
|
+
v -15.000000 -15.000000 0.000000
|
9
|
+
|
10
|
+
vt 15.000000 0.000000
|
11
|
+
vt 15.000000 15.000000
|
12
|
+
vt 0.000000 15.000000
|
13
|
+
vt 0.000000 0.000000
|
14
|
+
|
15
|
+
usemtl Material
|
16
|
+
s off
|
17
|
+
f 1/1 2/2 3/3
|
18
|
+
f 1/1 3/3 4/4
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<?xml version="0.0" ?>
|
2
|
+
<robot name="plane">
|
3
|
+
<link name="planeLink">
|
4
|
+
<contact>
|
5
|
+
<lateral_friction value="1"/>
|
6
|
+
</contact>
|
7
|
+
<inertial>
|
8
|
+
<origin rpy="0 0 0" xyz="0 0 0"/>
|
9
|
+
<mass value=".0"/>
|
10
|
+
<inertia ixx="0" ixy="0" ixz="0" iyy="0" iyz="0" izz="0"/>
|
11
|
+
</inertial>
|
12
|
+
<visual>
|
13
|
+
<origin rpy="0 0 0" xyz="0 0 0"/>
|
14
|
+
<geometry>
|
15
|
+
<mesh filename="plane.obj" scale="1 1 1"/>
|
16
|
+
</geometry>
|
17
|
+
<material name="white">
|
18
|
+
<color rgba="1 1 1 1"/>
|
19
|
+
</material>
|
20
|
+
</visual>
|
21
|
+
<collision>
|
22
|
+
<origin rpy="0 0 0" xyz="0 0 -5"/>
|
23
|
+
<geometry>
|
24
|
+
<box size="30 30 10"/>
|
25
|
+
</geometry>
|
26
|
+
</collision>
|
27
|
+
</link>
|
28
|
+
</robot>
|
kscale/cli.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
"""Defines the top-level KOL CLI."""
|
2
|
+
|
3
|
+
import click
|
4
|
+
|
5
|
+
from kscale.utils.cli import recursive_help
|
6
|
+
from kscale.web.kernels import cli as kernel_images_cli
|
7
|
+
from kscale.web.krec import cli as krec_cli
|
8
|
+
from kscale.web.pybullet import cli as pybullet_cli
|
9
|
+
from kscale.web.urdf import cli as urdf_cli
|
10
|
+
|
11
|
+
|
12
|
+
@click.group()
|
13
|
+
def cli() -> None:
|
14
|
+
"""Command line interface for interacting with the K-Scale web API."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
cli.add_command(urdf_cli, "urdf")
|
19
|
+
cli.add_command(pybullet_cli, "pybullet")
|
20
|
+
cli.add_command(kernel_images_cli, "kernel")
|
21
|
+
cli.add_command(krec_cli, "krec")
|
22
|
+
|
23
|
+
if __name__ == "__main__":
|
24
|
+
# python -m kscale.cli
|
25
|
+
print(recursive_help(cli))
|
kscale/conf.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
"""Defines the bot environment settings."""
|
2
|
+
|
3
|
+
import functools
|
4
|
+
import os
|
5
|
+
import warnings
|
6
|
+
from dataclasses import dataclass, field
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
from omegaconf import II, OmegaConf
|
10
|
+
|
11
|
+
|
12
|
+
def get_path() -> Path:
|
13
|
+
if "KSCALE_CONFIG_DIR" in os.environ:
|
14
|
+
return Path(os.environ["KSCALE_CONFIG_DIR"]).expanduser().resolve()
|
15
|
+
return Path("~/.kscale/").expanduser().resolve()
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class WWWSettings:
|
20
|
+
api_key: str | None = field(default=None)
|
21
|
+
cache_dir: str = field(default=II("oc.env:KSCALE_CACHE_DIR,'~/.kscale/cache/'"))
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class Settings:
|
26
|
+
www: WWWSettings = field(default_factory=WWWSettings)
|
27
|
+
|
28
|
+
def save(self) -> None:
|
29
|
+
(dir_path := get_path()).mkdir(parents=True, exist_ok=True)
|
30
|
+
with open(dir_path / "settings.yaml", "w") as f:
|
31
|
+
OmegaConf.save(config=self, f=f)
|
32
|
+
|
33
|
+
@functools.lru_cache
|
34
|
+
@staticmethod
|
35
|
+
def load() -> "Settings":
|
36
|
+
config = OmegaConf.structured(Settings)
|
37
|
+
if not (dir_path := get_path()).exists():
|
38
|
+
warnings.warn(f"Settings directory does not exist: {dir_path}. Creating it now.")
|
39
|
+
dir_path.mkdir(parents=True)
|
40
|
+
OmegaConf.save(config, dir_path / "settings.yaml")
|
41
|
+
else:
|
42
|
+
with open(dir_path / "settings.yaml", "r") as f:
|
43
|
+
raw_settings = OmegaConf.load(f)
|
44
|
+
config = OmegaConf.merge(config, raw_settings)
|
45
|
+
return config
|
kscale/py.typed
ADDED
File without changes
|
kscale/requirements.txt
ADDED
Binary file
|
kscale/utils/__init__.py
ADDED
File without changes
|
kscale/utils/api_base.py
ADDED
kscale/utils/checksum.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
"""Utility functions for file checksums."""
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Tuple
|
6
|
+
|
7
|
+
CHUNK_SIZE = 8192
|
8
|
+
|
9
|
+
|
10
|
+
async def calculate_sha256(file_path: str | Path) -> Tuple[str, int]:
|
11
|
+
"""Calculate SHA256 checksum and size of a file.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
file_path: Path to the file
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
Tuple of (checksum hex string, file size in bytes)
|
18
|
+
"""
|
19
|
+
sha256_hash = hashlib.sha256()
|
20
|
+
file_size = 0
|
21
|
+
|
22
|
+
with open(file_path, "rb") as f:
|
23
|
+
for chunk in iter(lambda: f.read(CHUNK_SIZE), b""):
|
24
|
+
sha256_hash.update(chunk)
|
25
|
+
file_size += len(chunk)
|
26
|
+
|
27
|
+
return sha256_hash.hexdigest(), file_size
|
28
|
+
|
29
|
+
|
30
|
+
class FileChecksum:
|
31
|
+
"""Helper class for handling file checksums."""
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
async def calculate(file_path: str | Path) -> Tuple[str, int]:
|
35
|
+
"""Calculate SHA256 checksum and size of a file."""
|
36
|
+
return await calculate_sha256(file_path)
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def update_hash(hash_obj: "hashlib._Hash", chunk: bytes) -> None:
|
40
|
+
"""Update a hash object with new data."""
|
41
|
+
hash_obj.update(chunk)
|
kscale/utils/cli.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
"""Defines utilities for working with asyncio."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import textwrap
|
5
|
+
from functools import wraps
|
6
|
+
from typing import Any, Callable, Coroutine, ParamSpec, TypeVar
|
7
|
+
|
8
|
+
import click
|
9
|
+
|
10
|
+
T = TypeVar("T")
|
11
|
+
P = ParamSpec("P")
|
12
|
+
|
13
|
+
|
14
|
+
def coro(f: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
|
15
|
+
@wraps(f)
|
16
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
17
|
+
return asyncio.run(f(*args, **kwargs))
|
18
|
+
|
19
|
+
return wrapper
|
20
|
+
|
21
|
+
|
22
|
+
def recursive_help(cmd: click.Command, parent: click.Context | None = None, indent: int = 0) -> str:
|
23
|
+
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
|
24
|
+
help_text = cmd.get_help(ctx)
|
25
|
+
commands = getattr(cmd, "commands", {})
|
26
|
+
for sub in commands.values():
|
27
|
+
help_text += recursive_help(sub, ctx, indent + 2)
|
28
|
+
return textwrap.indent(help_text, " " * indent)
|
kscale/web/__init__.py
ADDED
File without changes
|
kscale/web/api.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
"""Defines a common interface for the K-Scale WWW API."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import overload
|
6
|
+
|
7
|
+
from kscale.utils.api_base import APIBase
|
8
|
+
from kscale.web.gen.api import UploadArtifactResponse
|
9
|
+
from kscale.web.urdf import download_urdf, upload_urdf
|
10
|
+
|
11
|
+
|
12
|
+
class WebAPI(APIBase):
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
*,
|
16
|
+
api_key: str | None = None,
|
17
|
+
) -> None:
|
18
|
+
super().__init__()
|
19
|
+
|
20
|
+
self.api_key = api_key
|
21
|
+
|
22
|
+
async def artifact_root(self, artifact_id: str) -> Path:
|
23
|
+
return await download_urdf(artifact_id)
|
24
|
+
|
25
|
+
@overload
|
26
|
+
async def urdf_path(self, artifact_id: str) -> Path: ...
|
27
|
+
|
28
|
+
@overload
|
29
|
+
async def urdf_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
30
|
+
|
31
|
+
async def urdf_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
32
|
+
root_dir = await self.artifact_root(artifact_id)
|
33
|
+
urdf_path = next(root_dir.glob("*.urdf"), None)
|
34
|
+
if urdf_path is None and throw_if_missing:
|
35
|
+
raise FileNotFoundError(f"No URDF found for artifact {artifact_id}")
|
36
|
+
return urdf_path
|
37
|
+
|
38
|
+
@overload
|
39
|
+
def urdf_path_sync(self, artifact_id: str) -> Path: ...
|
40
|
+
|
41
|
+
@overload
|
42
|
+
def urdf_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
43
|
+
|
44
|
+
def urdf_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
45
|
+
return asyncio.run(self.urdf_path(artifact_id, throw_if_missing=throw_if_missing))
|
46
|
+
|
47
|
+
@overload
|
48
|
+
async def mjcf_path(self, artifact_id: str) -> Path: ...
|
49
|
+
|
50
|
+
@overload
|
51
|
+
async def mjcf_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
52
|
+
|
53
|
+
async def mjcf_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
54
|
+
root_dir = await self.artifact_root(artifact_id)
|
55
|
+
mjcf_path = next(root_dir.glob("*.mjcf"), None)
|
56
|
+
if mjcf_path is None and throw_if_missing:
|
57
|
+
raise FileNotFoundError(f"No MJCF found for artifact {artifact_id}")
|
58
|
+
return mjcf_path
|
59
|
+
|
60
|
+
@overload
|
61
|
+
def mjcf_path_sync(self, artifact_id: str) -> Path: ...
|
62
|
+
|
63
|
+
@overload
|
64
|
+
def mjcf_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
65
|
+
|
66
|
+
def mjcf_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
67
|
+
return asyncio.run(self.mjcf_path(artifact_id, throw_if_missing=throw_if_missing))
|
68
|
+
|
69
|
+
@overload
|
70
|
+
async def xml_path(self, artifact_id: str) -> Path: ...
|
71
|
+
|
72
|
+
@overload
|
73
|
+
async def xml_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
74
|
+
|
75
|
+
async def xml_path(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
76
|
+
root_dir = await self.artifact_root(artifact_id)
|
77
|
+
xml_path = next(root_dir.glob("*.xml"), None)
|
78
|
+
if xml_path is None and throw_if_missing:
|
79
|
+
raise FileNotFoundError(f"No XML found for artifact {artifact_id}")
|
80
|
+
return xml_path
|
81
|
+
|
82
|
+
async def upload_urdf(self, listing_id: str, root_dir: Path) -> UploadArtifactResponse:
|
83
|
+
return await upload_urdf(listing_id, root_dir)
|
84
|
+
|
85
|
+
def artifact_root_sync(self, artifact_id: str) -> Path:
|
86
|
+
return asyncio.run(self.artifact_root(artifact_id))
|
87
|
+
|
88
|
+
@overload
|
89
|
+
def xml_path_sync(self, artifact_id: str) -> Path: ...
|
90
|
+
|
91
|
+
@overload
|
92
|
+
def xml_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None: ...
|
93
|
+
|
94
|
+
def xml_path_sync(self, artifact_id: str, *, throw_if_missing: bool = True) -> Path | None:
|
95
|
+
return asyncio.run(self.xml_path(artifact_id, throw_if_missing=throw_if_missing))
|
96
|
+
|
97
|
+
def upload_urdf_sync(self, listing_id: str, root_dir: Path) -> UploadArtifactResponse:
|
98
|
+
return asyncio.run(self.upload_urdf(listing_id, root_dir))
|
File without changes
|