Skip to content

Commit 88a8292

Browse files
committed
now support hf space with autoenv
1 parent 0356bd1 commit 88a8292

File tree

2 files changed

+172
-20
lines changed

2 files changed

+172
-20
lines changed

src/envs/auto_action.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,8 @@ def from_name(cls, name: str) -> Type:
120120
"""
121121
# Check if it's a HuggingFace Hub URL or repo ID
122122
if _is_hub_url(name):
123-
# Download from Hub and install (reuse AutoEnv logic)
124-
env_path = AutoEnv._download_from_hub(name)
125-
package_name = AutoEnv._install_from_path(env_path)
126-
127-
# Clear discovery cache to pick up the newly installed package
128-
get_discovery().clear_cache()
129-
130-
# Extract environment name from package name
131-
# "openenv-coding_env" -> "coding_env"
132-
env_name = package_name.replace("openenv-", "").replace("-", "_")
123+
# Ensure package is installed (reuse AutoEnv logic, downloads only if needed)
124+
env_name = AutoEnv._ensure_package_from_hub(name)
133125
else:
134126
env_name = name
135127

src/envs/auto_env.py

Lines changed: 170 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import os
3636
import subprocess
3737
import tempfile
38+
import requests
3839
from pathlib import Path
3940
from typing import Any, Optional, TYPE_CHECKING, Dict
4041

@@ -83,6 +84,62 @@ def __init__(self):
8384
"Use AutoEnv.from_name() instead."
8485
)
8586

87+
@classmethod
88+
def _resolve_space_url(cls, repo_id: str) -> str:
89+
"""
90+
Resolve HuggingFace Space repo ID to Space URL.
91+
92+
Args:
93+
repo_id: HuggingFace repo ID (e.g., "wukaixingxp/coding-env-test")
94+
95+
Returns:
96+
Space URL (e.g., "https://wukaixingxp-coding-env-test.hf.space")
97+
98+
Examples:
99+
>>> AutoEnv._resolve_space_url("wukaixingxp/coding-env-test")
100+
'https://wukaixingxp-coding-env-test.hf.space'
101+
"""
102+
# Clean up repo_id if it's a full URL
103+
if "huggingface.co" in repo_id:
104+
# Extract org/repo from URL
105+
# https://huggingface.co/wukaixingxp/coding-env-test -> wukaixingxp/coding-env-test
106+
parts = repo_id.split("/")
107+
if len(parts) >= 2:
108+
repo_id = f"{parts[-2]}/{parts[-1]}"
109+
110+
# Convert user/space-name to user-space-name.hf.space
111+
space_slug = repo_id.replace("/", "-")
112+
return f"https://{space_slug}.hf.space"
113+
114+
@classmethod
115+
def _check_space_availability(cls, space_url: str, timeout: float = 5.0) -> bool:
116+
"""
117+
Check if HuggingFace Space is running and accessible.
118+
119+
Args:
120+
space_url: Space URL to check
121+
timeout: Request timeout in seconds
122+
123+
Returns:
124+
True if Space is accessible, False otherwise
125+
126+
Examples:
127+
>>> AutoEnv._check_space_availability("https://wukaixingxp-coding-env-test.hf.space")
128+
True
129+
"""
130+
try:
131+
# Try to access the health endpoint
132+
response = requests.get(f"{space_url}/health", timeout=timeout)
133+
if response.status_code == 200:
134+
return True
135+
136+
# If health endpoint doesn't exist, try root endpoint
137+
response = requests.get(space_url, timeout=timeout)
138+
return response.status_code == 200
139+
except (requests.RequestException, Exception) as e:
140+
logger.debug(f"Space {space_url} not accessible: {e}")
141+
return False
142+
86143
@classmethod
87144
def _download_from_hub(
88145
cls, repo_id: str, cache_dir: Optional[Path] = None
@@ -185,6 +242,94 @@ def _install_from_path(cls, env_path: Path) -> str:
185242
except Exception as e:
186243
raise ValueError(f"Failed to install environment package: {e}") from e
187244

245+
@classmethod
246+
def _get_package_name_from_hub(cls, name: str) -> tuple[str, Path]:
247+
"""
248+
Download Space and get the package name from pyproject.toml.
249+
250+
Args:
251+
name: HuggingFace repo ID (e.g., "wukaixingxp/coding-env-test")
252+
253+
Returns:
254+
Tuple of (package_name, env_path)
255+
Example: ("openenv-coding_env", Path("/tmp/..."))
256+
"""
257+
# Download from Hub
258+
env_path = cls._download_from_hub(name)
259+
260+
# Read package name from pyproject.toml
261+
import toml
262+
263+
pyproject_path = env_path / "pyproject.toml"
264+
if not pyproject_path.exists():
265+
raise ValueError(
266+
f"Environment directory does not contain pyproject.toml: {env_path}"
267+
)
268+
269+
with open(pyproject_path, "r") as f:
270+
pyproject = toml.load(f)
271+
272+
package_name = pyproject.get("project", {}).get("name")
273+
if not package_name:
274+
raise ValueError(
275+
f"Could not determine package name from pyproject.toml at {pyproject_path}"
276+
)
277+
278+
return package_name, env_path
279+
280+
@classmethod
281+
def _is_package_installed(cls, package_name: str) -> bool:
282+
"""
283+
Check if a package is already installed.
284+
285+
Args:
286+
package_name: Package name (e.g., "openenv-coding_env")
287+
288+
Returns:
289+
True if installed, False otherwise
290+
"""
291+
try:
292+
import importlib.metadata
293+
importlib.metadata.distribution(package_name)
294+
return True
295+
except importlib.metadata.PackageNotFoundError:
296+
return False
297+
298+
@classmethod
299+
def _ensure_package_from_hub(cls, name: str) -> str:
300+
"""
301+
Ensure package from HuggingFace Hub is installed.
302+
303+
Only downloads and installs if not already installed.
304+
305+
Args:
306+
name: HuggingFace repo ID (e.g., "wukaixingxp/coding-env-test")
307+
308+
Returns:
309+
Environment name (e.g., "coding_env")
310+
"""
311+
# Download and get actual package name from pyproject.toml
312+
logger.info(f"📦 Checking package from HuggingFace Space...")
313+
package_name, env_path = cls._get_package_name_from_hub(name)
314+
315+
# Check if already installed
316+
if cls._is_package_installed(package_name):
317+
logger.info(f"✅ Package already installed: {package_name}")
318+
# Clear and refresh discovery cache to make sure it's detected
319+
get_discovery().clear_cache()
320+
get_discovery().discover(use_cache=False)
321+
else:
322+
# Not installed, install it now
323+
logger.info(f"📦 Package not found, installing: {package_name}")
324+
cls._install_from_path(env_path)
325+
# Clear discovery cache to pick up the newly installed package
326+
get_discovery().clear_cache()
327+
328+
# Extract environment name from package name
329+
# "openenv-coding_env" -> "coding_env"
330+
env_name = package_name.replace("openenv-", "").replace("-", "_")
331+
return env_name
332+
188333
@classmethod
189334
def from_name(
190335
cls,
@@ -243,16 +388,31 @@ def from_name(
243388
"""
244389
# Check if it's a HuggingFace Hub URL or repo ID
245390
if _is_hub_url(name):
246-
# Download from Hub and install
247-
env_path = cls._download_from_hub(name)
248-
package_name = cls._install_from_path(env_path)
249-
250-
# Clear discovery cache to pick up the newly installed package
251-
get_discovery().clear_cache()
252-
253-
# Extract environment name from package name
254-
# "openenv-coding_env" -> "coding_env"
255-
env_name = package_name.replace("openenv-", "").replace("-", "_")
391+
# Try to connect to Space directly first
392+
space_url = cls._resolve_space_url(name)
393+
logger.info(f"Checking if HuggingFace Space is accessible: {space_url}")
394+
395+
space_is_available = cls._check_space_availability(space_url)
396+
397+
if space_is_available and base_url is None:
398+
# Space is accessible! We'll connect directly without Docker
399+
logger.info(f"✅ Space is accessible at: {space_url}")
400+
logger.info("📦 Installing package for client code (no Docker needed)...")
401+
402+
# Ensure package is installed (downloads only if needed)
403+
env_name = cls._ensure_package_from_hub(name)
404+
405+
# Set base_url to connect to remote Space
406+
base_url = space_url
407+
logger.info(f"🚀 Will connect to remote Space (no local Docker)")
408+
else:
409+
# Space not accessible or user provided explicit base_url
410+
if not space_is_available:
411+
logger.info(f"❌ Space not accessible at {space_url}")
412+
logger.info("📦 Falling back to local Docker mode...")
413+
414+
# Ensure package is installed (downloads only if needed)
415+
env_name = cls._ensure_package_from_hub(name)
256416
else:
257417
env_name = name
258418

0 commit comments

Comments
 (0)