Spaces:
Running
Running
Download scripts/deploy_spaces.py from FineEnvs/RL-Explorer: direct link, hf CLI and curl.
- Browser
- Download file 6.27 kB
-
https://huggingface.co/spaces/FineEnvs/RL-Explorer/resolve/main/scripts/deploy_spaces.py
- Command line
-
hf download hf://spaces/FineEnvs/RL-Explorer/scripts/deploy_spaces.py
-
curl -L -o deploy_spaces.py https://huggingface.co/spaces/FineEnvs/RL-Explorer/resolve/main/scripts/deploy_spaces.py
6.27 kB
| """Deploy HF RL Explorer from this folder: the public explorer Space and its private admin Space. | |
| uv run python scripts/deploy_spaces.py # the plan, nothing changed | |
| uv run python scripts/deploy_spaces.py --apply # create or update both Spaces | |
| uv run python scripts/deploy_spaces.py --apply --only admin # one of them | |
| uv run python scripts/deploy_spaces.py --apply --listing-from-store # once the indexer Job runs (DESIGN.md) | |
| Both run this image (Dockerfile); the admin with RLX_APP=app.admin_app:app. They share only the bucket | |
| FineEnvs/rl-explorer-data, mounted read-write at /data; the explorer also mounts the MiMo explorer's repository | |
| snapshots read-only (one copy, the one MiMo's own runs use). Each Space has its own SESSION_SECRET: created once, | |
| never printed, never replaced (replacing it signs everyone out). Each Space gets one commit with its own README, | |
| so one build, and files no longer here are removed from it. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import fnmatch | |
| import os | |
| import secrets | |
| import sys | |
| from pathlib import Path | |
| from huggingface_hub import CommitOperationAdd, CommitOperationDelete, HfApi, Volume | |
| ROOT = Path(__file__).resolve().parents[1] | |
| BUCKET = "FineEnvs/rl-explorer-data" | |
| EXPLORER_URL = "https://fineenvs-rl-explorer.hf.space" | |
| DATA = Volume(type="bucket", source=BUCKET, mount_path="/data") | |
| SPACES = { | |
| "explorer": { | |
| "repo": "FineEnvs/RL-Explorer", "private": False, "readme": "SPACE_README.md", "hardware": "cpu-basic", | |
| "variables": {"MIMO_SNAPSHOT_DIR": "/mimo-snapshots"}, | |
| "volumes": [DATA, Volume(type="bucket", source="FineEnvs/mimo-explorer-runs", mount_path="/mimo-snapshots", | |
| path="repo-snapshots", read_only=True)], | |
| }, | |
| "admin": { | |
| "repo": "FineEnvs/RL-Explorer-admin", "private": True, "readme": "ADMIN_SPACE_README.md", "hardware": "cpu-basic", | |
| "variables": {"RLX_APP": "app.admin_app:app", "RLX_OAUTH_SCOPES": "openid profile read-memberships", "RLX_EXPLORER_URL": EXPLORER_URL}, | |
| "volumes": [DATA], | |
| }, | |
| } | |
| # never uploaded: local data and caches, the READMEs (each Space gets its own as README.md), editor and OS files | |
| IGNORE = [".venv/*", ".local-data/*", "*/__pycache__/*", "__pycache__/*", "*.pyc", ".pytest_cache/*", ".ruff_cache/*", | |
| ".gradio/*", "node_modules/*", ".DS_Store", "*/.DS_Store", "README.md", "SPACE_README.md", "ADMIN_SPACE_README.md", | |
| ".env", "*.env", ".git/*"] | |
| SKIP_DIRS = {".venv", ".local-data", "__pycache__", ".pytest_cache", ".ruff_cache", ".gradio", "node_modules", ".git"} | |
| def files() -> list[str]: | |
| """What goes into the Space: walked without descending into local data, caches or the virtualenv.""" | |
| out = [] | |
| for root, dirs, names in os.walk(ROOT): | |
| dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS) | |
| for n in sorted(names): | |
| rel = (Path(root) / n).relative_to(ROOT).as_posix() | |
| if not any(fnmatch.fnmatch(rel, pat) for pat in IGNORE): | |
| out.append(rel) | |
| return out | |
| def deploy(api: HfApi, name: str, spec: dict, listing_from_store: bool, apply: bool) -> None: | |
| repo = spec["repo"] | |
| variables = {**spec["variables"], **({"RLX_LISTING_FROM_STORE": "1"} if listing_from_store else {})} | |
| paths = files() | |
| print(f"\n{name}: {repo} ({'private' if spec['private'] else 'public'}, {spec['hardware']})") | |
| print(f" volumes: " + "; ".join(f"{v.source}{'/' + v.path if v.path else ''} -> {v.mount_path}{' (read-only)' if v.read_only else ''}" for v in spec["volumes"])) | |
| print(f" variables: {variables}") | |
| print(f" files: {len(paths)} + README.md from {spec['readme']}") | |
| if not apply: | |
| return | |
| api.create_repo(repo, repo_type="space", space_sdk="docker", private=spec["private"], exist_ok=True, | |
| space_hardware=spec["hardware"]) | |
| info = api.space_info(repo) | |
| if bool(info.private) != spec["private"]: | |
| api.update_repo_settings(repo, repo_type="space", private=spec["private"]) | |
| api.set_space_volumes(repo, spec["volumes"]) | |
| for k, v in variables.items(): | |
| api.add_space_variable(repo, k, v) | |
| if "SESSION_SECRET" not in {getattr(x, "key", x) for x in api.get_space_secrets(repo)}: # names, or objects with .key | |
| api.add_space_secret(repo, "SESSION_SECRET", secrets.token_urlsafe(48), description="signs this Space's sessions") | |
| print(" SESSION_SECRET: created") | |
| runtime = api.get_space_runtime(repo) | |
| if (runtime.requested_hardware or runtime.hardware) != spec["hardware"]: | |
| api.request_space_hardware(repo, spec["hardware"]) | |
| ops = [CommitOperationAdd(path_in_repo=rel, path_or_fileobj=str(ROOT / rel)) for rel in paths] | |
| ops.append(CommitOperationAdd(path_in_repo="README.md", path_or_fileobj=str(ROOT / spec["readme"]))) | |
| keep = set(paths) | {"README.md", ".gitattributes"} | |
| ops += [CommitOperationDelete(path_in_repo=f) for f in api.list_repo_files(repo, repo_type="space") if f not in keep] | |
| commit = api.create_commit(repo, repo_type="space", operations=ops, commit_message="Deploy HF RL Explorer") | |
| print(f" pushed {commit.oid[:10]}: https://huggingface.co/spaces/{repo}") | |
| def main() -> None: | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--apply", action="store_true", help="create or update the Spaces (without it, print the plan)") | |
| ap.add_argument("--only", choices=sorted(SPACES), help="deploy one Space") | |
| ap.add_argument("--listing-from-store", action="store_true", help="read the indexer Job's listing (set once the Job runs)") | |
| args = ap.parse_args() | |
| api = HfApi() | |
| me = api.whoami() | |
| orgs = {o["name"]: o.get("roleInOrg") for o in me.get("orgs", [])} | |
| if orgs.get("FineEnvs") not in ("admin", "write", "contributor"): | |
| sys.exit(f"{me['name']} can't write to FineEnvs (role: {orgs.get('FineEnvs')})") | |
| for name, spec in SPACES.items(): | |
| if not args.only or args.only == name: | |
| deploy(api, name, spec, args.listing_from_store, args.apply) | |
| if not args.apply: | |
| print("\nNothing changed: run with --apply.") | |
| if __name__ == "__main__": | |
| main() | |