Skip to content

Commit c0ced4b

Browse files
authored
refactor CLI Hugging Face username helper (#861)
1 parent 91f84d8 commit c0ced4b

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
@@ -11,6 +11,22 @@
1111
console = Console()
1212

1313

14+
def _extract_hf_username(user_info: object) -> str | None:
15+
"""Extract a username from the supported Hugging Face whoami shapes."""
16+
if isinstance(user_info, dict):
17+
return (
18+
user_info.get("name")
19+
or user_info.get("fullname")
20+
or user_info.get("username")
21+
)
22+
23+
return (
24+
getattr(user_info, "name", None)
25+
or getattr(user_info, "fullname", None)
26+
or getattr(user_info, "username", None)
27+
)
28+
29+
1430
def validate_env_structure(env_dir: Path, strict: bool = False) -> List[str]:
1531
"""
1632
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
@@ -9,7 +9,7 @@
99
import typer
1010
from huggingface_hub import HfApi, login, whoami
1111

12-
from .._cli_utils import console
12+
from .._cli_utils import _extract_hf_username, console
1313

1414
app = typer.Typer(
1515
help="Fork (duplicate) an OpenEnv environment on Hugging Face to your account"
@@ -33,19 +33,7 @@ def _parse_key_value(s: str) -> tuple[str, str]:
3333
def _ensure_hf_authenticated() -> str:
3434
"""Ensure user is authenticated with Hugging Face. Returns username."""
3535
try:
36-
user_info = whoami()
37-
if isinstance(user_info, dict):
38-
username = (
39-
user_info.get("name")
40-
or user_info.get("fullname")
41-
or user_info.get("username")
42-
)
43-
else:
44-
username = (
45-
getattr(user_info, "name", None)
46-
or getattr(user_info, "fullname", None)
47-
or getattr(user_info, "username", None)
48-
)
36+
username = _extract_hf_username(whoami())
4937
if not username:
5038
raise ValueError("Could not extract username from whoami response")
5139
console.print(f"[bold green]✓[/bold green] Authenticated as: {username}")
@@ -56,19 +44,7 @@ def _ensure_hf_authenticated() -> str:
5644
)
5745
try:
5846
login()
59-
user_info = whoami()
60-
if isinstance(user_info, dict):
61-
username = (
62-
user_info.get("name")
63-
or user_info.get("fullname")
64-
or user_info.get("username")
65-
)
66-
else:
67-
username = (
68-
getattr(user_info, "name", None)
69-
or getattr(user_info, "fullname", None)
70-
or getattr(user_info, "username", None)
71-
)
47+
username = _extract_hf_username(whoami())
7248
if not username:
7349
raise ValueError("Could not extract username from whoami response")
7450
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
@@ -15,7 +15,7 @@
1515
import yaml
1616
from huggingface_hub import HfApi, login, whoami
1717

18-
from .._cli_utils import console, validate_env_structure
18+
from .._cli_utils import _extract_hf_username, console, validate_env_structure
1919

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

@@ -239,22 +239,6 @@ def _validate_openenv_directory(directory: Path) -> tuple[str, dict]:
239239
return env_name, manifest
240240

241241

242-
def _extract_hf_username(user_info: object) -> str | None:
243-
"""Extract a username from the supported Hugging Face whoami shapes."""
244-
if isinstance(user_info, dict):
245-
return (
246-
user_info.get("name")
247-
or user_info.get("fullname")
248-
or user_info.get("username")
249-
)
250-
251-
return (
252-
getattr(user_info, "name", None)
253-
or getattr(user_info, "fullname", None)
254-
or getattr(user_info, "username", None)
255-
)
256-
257-
258242
def _get_hf_username() -> str:
259243
"""Return the authenticated Hugging Face username from whoami()."""
260244
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
@@ -2,6 +2,7 @@
22

33
"""Tests for the openenv fork command."""
44

5+
from types import SimpleNamespace
56
from unittest.mock import MagicMock, patch
67

78
from openenv.cli.__main__ import app
@@ -48,6 +49,27 @@ def test_fork_calls_duplicate_space_with_from_id() -> None:
4849
assert call_kwargs["hardware"] == "cpu-basic"
4950

5051

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

0 commit comments

Comments
 (0)