Skip to content

Commit ca6b150

Browse files
committed
refactor CLI Hugging Face username helper
1 parent 568a185 commit ca6b150

4 files changed

Lines changed: 42 additions & 44 deletions

File tree

src/openenv/cli/_cli_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@
1515
console = Console()
1616

1717

18+
def _extract_hf_username(user_info: object) -> str | None:
19+
"""Extract a username from the supported Hugging Face whoami shapes."""
20+
if isinstance(user_info, dict):
21+
return (
22+
user_info.get("name")
23+
or user_info.get("fullname")
24+
or user_info.get("username")
25+
)
26+
27+
return (
28+
getattr(user_info, "name", None)
29+
or getattr(user_info, "fullname", None)
30+
or getattr(user_info, "username", None)
31+
)
32+
33+
1834
def validate_env_structure(env_dir: Path, strict: bool = False) -> List[str]:
1935
"""
2036
Validate that the directory follows OpenEnv environment structure.

src/openenv/cli/commands/fork.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import typer
1414
from huggingface_hub import HfApi, login, whoami
1515

16-
from .._cli_utils import console
16+
from .._cli_utils import _extract_hf_username, console
1717

1818
app = typer.Typer(
1919
help="Fork (duplicate) an OpenEnv environment on Hugging Face to your account"
@@ -37,19 +37,7 @@ def _parse_key_value(s: str) -> tuple[str, str]:
3737
def _ensure_hf_authenticated() -> str:
3838
"""Ensure user is authenticated with Hugging Face. Returns username."""
3939
try:
40-
user_info = whoami()
41-
if isinstance(user_info, dict):
42-
username = (
43-
user_info.get("name")
44-
or user_info.get("fullname")
45-
or user_info.get("username")
46-
)
47-
else:
48-
username = (
49-
getattr(user_info, "name", None)
50-
or getattr(user_info, "fullname", None)
51-
or getattr(user_info, "username", None)
52-
)
40+
username = _extract_hf_username(whoami())
5341
if not username:
5442
raise ValueError("Could not extract username from whoami response")
5543
console.print(f"[bold green]✓[/bold green] Authenticated as: {username}")
@@ -60,19 +48,7 @@ def _ensure_hf_authenticated() -> str:
6048
)
6149
try:
6250
login()
63-
user_info = whoami()
64-
if isinstance(user_info, dict):
65-
username = (
66-
user_info.get("name")
67-
or user_info.get("fullname")
68-
or user_info.get("username")
69-
)
70-
else:
71-
username = (
72-
getattr(user_info, "name", None)
73-
or getattr(user_info, "fullname", None)
74-
or getattr(user_info, "username", None)
75-
)
51+
username = _extract_hf_username(whoami())
7652
if not username:
7753
raise ValueError("Could not extract username from whoami response")
7854
console.print(f"[bold green]✓[/bold green] Authenticated as: {username}")

src/openenv/cli/commands/push.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import yaml
2020
from huggingface_hub import HfApi, login, whoami
2121

22-
from .._cli_utils import console, validate_env_structure
22+
from .._cli_utils import _extract_hf_username, console, validate_env_structure
2323

2424
app = typer.Typer(help="Push an OpenEnv environment to Hugging Face Spaces")
2525

@@ -243,22 +243,6 @@ def _validate_openenv_directory(directory: Path) -> tuple[str, dict]:
243243
return env_name, manifest
244244

245245

246-
def _extract_hf_username(user_info: object) -> str | None:
247-
"""Extract a username from the supported Hugging Face whoami shapes."""
248-
if isinstance(user_info, dict):
249-
return (
250-
user_info.get("name")
251-
or user_info.get("fullname")
252-
or user_info.get("username")
253-
)
254-
255-
return (
256-
getattr(user_info, "name", None)
257-
or getattr(user_info, "fullname", None)
258-
or getattr(user_info, "username", None)
259-
)
260-
261-
262246
def _get_hf_username() -> str:
263247
"""Return the authenticated Hugging Face username from whoami()."""
264248
username = _extract_hf_username(whoami())

tests/test_cli/test_fork.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
"""Tests for the openenv fork command."""
88

9+
from types import SimpleNamespace
910
from unittest.mock import MagicMock, patch
1011

1112
from openenv.cli.__main__ import app
@@ -52,6 +53,27 @@ def test_fork_calls_duplicate_space_with_from_id() -> None:
5253
assert call_kwargs["hardware"] == "cpu-basic"
5354

5455

56+
def test_fork_authenticates_object_whoami_response() -> None:
57+
"""Test that fork accepts object-shaped whoami responses."""
58+
with (
59+
patch("openenv.cli.commands.fork.whoami") as mock_whoami,
60+
patch("openenv.cli.commands.fork.login") as mock_login,
61+
patch("openenv.cli.commands.fork.HfApi") as mock_hf_api_class,
62+
):
63+
mock_whoami.return_value = SimpleNamespace(username="testuser")
64+
mock_api = MagicMock()
65+
mock_api.duplicate_space.return_value = (
66+
"https://huggingface.co/spaces/testuser/source-space"
67+
)
68+
mock_hf_api_class.return_value = mock_api
69+
70+
result = runner.invoke(app, ["fork", "owner/source-space"])
71+
72+
assert result.exit_code == 0
73+
mock_login.assert_not_called()
74+
mock_api.duplicate_space.assert_called_once()
75+
76+
5577
def test_fork_passes_private_and_to_id() -> None:
5678
"""Test that fork passes --private and --repo-id to duplicate_space."""
5779
with (

0 commit comments

Comments
 (0)