Skip to content

Commit d9cd5b6

Browse files
authored
Merge pull request #311 from RobotControlStack/juelg/issue-284-assets-download
feat: download missing assets for packaged installs
2 parents e3b946b + 26b89f1 commit d9cd5b6

1 file changed

Lines changed: 67 additions & 6 deletions

File tree

python/rcs/__init__.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,87 @@
11
"""Robot control stack python bindings."""
22

3+
import io
34
import os
5+
import zipfile
6+
from collections.abc import Callable
47
from dataclasses import dataclass
8+
from pathlib import Path, PurePosixPath
59
from typing import Any
610

711
import numpy as np
12+
import requests
813
from rcs._core import __version__, common
914

1015
from rcs import camera, envs, hand, sim
1116

17+
GITHUB_ASSET_ARCHIVE_URL = "https://github.com/RobotControlStack/robot-control-stack/archive/refs/tags/{tag}.zip"
1218

13-
def _rcs_prefix() -> str:
14-
env_prefix = os.environ.get("RCS_PREFIX")
19+
20+
def download_assets(
21+
version: str,
22+
prefix: Path,
23+
github_asset_archive_url: str,
24+
assets_dirname: str = "assets",
25+
) -> None:
26+
tag = f"v{version.split('+', maxsplit=1)[0]}"
27+
response = requests.get(github_asset_archive_url.format(tag=tag), timeout=60)
28+
response.raise_for_status()
29+
30+
found_assets = False
31+
with zipfile.ZipFile(io.BytesIO(response.content)) as archive:
32+
for member in archive.infolist():
33+
parts = PurePosixPath(member.filename).parts
34+
if len(parts) < 2 or parts[1] != assets_dirname:
35+
continue
36+
37+
found_assets = True
38+
target = prefix.joinpath(*parts[1:])
39+
if member.is_dir():
40+
target.mkdir(parents=True, exist_ok=True)
41+
else:
42+
target.parent.mkdir(parents=True, exist_ok=True)
43+
target.write_bytes(archive.read(member))
44+
45+
if not found_assets:
46+
msg = f"Could not find {assets_dirname} in the downloaded GitHub archive."
47+
raise FileNotFoundError(msg)
48+
49+
50+
def get_prefix(
51+
env_prefix: str | None,
52+
editable_prefix: Path,
53+
default_prefix: Path,
54+
version: str,
55+
github_asset_archive_url: str,
56+
assets_dirname: str = "assets",
57+
download_fn: Callable[[str, Path, str, str], None] = download_assets,
58+
) -> str:
1559
if env_prefix:
16-
return os.path.abspath(env_prefix)
17-
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
60+
prefix = Path(env_prefix).expanduser().resolve()
61+
else:
62+
editable_prefix = editable_prefix.resolve()
63+
if (editable_prefix / assets_dirname).is_dir():
64+
return str(editable_prefix)
65+
prefix = default_prefix.resolve()
66+
67+
assets_dir = prefix / assets_dirname
68+
if not assets_dir.is_dir():
69+
prefix.mkdir(parents=True, exist_ok=True)
70+
print(f"Assets not found at {assets_dir}, downloading them now.")
71+
download_fn(version, prefix, github_asset_archive_url, assets_dirname)
72+
73+
return str(prefix)
1874

1975

20-
RCS_PREFIX = _rcs_prefix()
76+
RCS_PREFIX = get_prefix(
77+
os.environ.get("RCS_PREFIX"),
78+
Path(__file__).resolve().parents[2],
79+
Path.home() / ".rcs",
80+
__version__,
81+
GITHUB_ASSET_ARCHIVE_URL,
82+
)
2183

2284

23-
# TODO: assets must be "downloaded" first time this is imported
2485
@dataclass(kw_only=True)
2586
class RobotMetaConfig:
2687

0 commit comments

Comments
 (0)