Skip to content

Commit 5237225

Browse files
fix(cli): accept bare repo-name in openenv push --repo-id (#651)
* fix(cli): accept bare repo-name in openenv push --repo-id When the user is already authenticated, the CLI knows their username, so a bare name like `my-env` should expand to `username/my-env` rather than failing with a format error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: remove misleading mock_stage.return_value _prepare_staging_directory is called for side-effects; staging_dir is assigned before the call, so the mock return value has no effect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(cli): update --repo-id help text and rename unused mock - Document bare repo-name expansion in the --repo-id help string - Rename mock_stage to _mock_stage (unused variable convention) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(cli): validate repo_id with count>1, expand bare names for correct URL output Reject repo-ids with more than one slash (mirroring hf_hub's validate_repo_id), and expand bare names using the authenticated username so the printed Space URL is correct end-to-end. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fbbde4e commit 5237225

2 files changed

Lines changed: 40 additions & 11 deletions

File tree

src/openenv/cli/commands/push.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ def push(
540540
typer.Option(
541541
"--repo-id",
542542
"-r",
543-
help="Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml)",
543+
help="Repository ID as 'repo_name' or 'namespace/repo_name'. Defaults to 'username/env-name' from openenv.yaml.",
544544
),
545545
] = None,
546546
base_image: Annotated[
@@ -818,11 +818,12 @@ def push(
818818
if not repo_id:
819819
repo_id = f"{username}/{env_name}"
820820

821-
# Validate repo_id format
822-
if "/" not in repo_id or repo_id.count("/") != 1:
821+
if repo_id.count("/") > 1:
823822
raise typer.BadParameter(
824-
f"Invalid repo-id format: {repo_id}. Expected format: 'username/repo-name'"
823+
f"Invalid repo-id format: {repo_id!r}. Repo id must be in the form 'repo_name' or 'namespace/repo_name'."
825824
)
825+
if "/" not in repo_id:
826+
repo_id = f"{username}/{repo_id}"
826827

827828
# Initialize Hugging Face API
828829
api = HfApi()

tests/test_cli/test_push.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ def test_push_initializes_hf_api_without_token(tmp_path: Path) -> None:
513513

514514

515515
def test_push_validates_repo_id_format(tmp_path: Path) -> None:
516-
"""Test that push validates repo-id format."""
516+
"""Test that push rejects repo-ids with more than one slash."""
517517
_create_test_openenv_env(tmp_path)
518518

519519
with (
@@ -522,23 +522,51 @@ def test_push_validates_repo_id_format(tmp_path: Path) -> None:
522522
patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class,
523523
):
524524
mock_whoami.return_value = {"name": "testuser"}
525-
mock_login.return_value = None # Prevent actual login prompt
526-
# Mock HfApi to prevent actual API calls
527-
mock_api = MagicMock()
528-
mock_hf_api_class.return_value = mock_api
525+
mock_login.return_value = None
526+
mock_hf_api_class.return_value = MagicMock()
529527

530528
old_cwd = os.getcwd()
531529
try:
532530
os.chdir(str(tmp_path))
533-
# Invalid format (no slash)
534-
result = runner.invoke(app, ["push", "--repo-id", "invalid-repo-id"])
531+
result = runner.invoke(app, ["push", "--repo-id", "org/repo/extra"])
535532
finally:
536533
os.chdir(old_cwd)
537534

538535
assert result.exit_code != 0
539536
assert "repo-id" in result.output.lower() or "format" in result.output.lower()
540537

541538

539+
def test_push_bare_repo_id_expands_to_username(tmp_path: Path) -> None:
540+
"""Bare repo-name (no slash) is expanded to username/repo-name before push."""
541+
_create_test_openenv_env(tmp_path)
542+
543+
with (
544+
patch("openenv.cli.commands.push.whoami") as mock_whoami,
545+
patch("openenv.cli.commands.push.login") as mock_login,
546+
patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class,
547+
patch("openenv.cli.commands.push._upload_to_hf_space") as mock_upload,
548+
patch("openenv.cli.commands.push._create_hf_space") as mock_create,
549+
patch("openenv.cli.commands.push._prepare_staging_directory") as _mock_stage,
550+
):
551+
mock_whoami.return_value = {"name": "testuser"}
552+
mock_login.return_value = None
553+
mock_hf_api_class.return_value = MagicMock()
554+
mock_create.return_value = None
555+
mock_upload.return_value = None
556+
557+
old_cwd = os.getcwd()
558+
try:
559+
os.chdir(str(tmp_path))
560+
result = runner.invoke(app, ["push", "--repo-id", "my-env"])
561+
finally:
562+
os.chdir(old_cwd)
563+
564+
assert "Invalid repo-id format" not in result.output
565+
mock_create.assert_called_once()
566+
assert mock_create.call_args.args[0] == "testuser/my-env"
567+
assert "testuser/my-env" in result.output
568+
569+
542570
def test_push_validates_manifest_is_dict(tmp_path: Path) -> None:
543571
"""Test that push validates manifest is a dictionary."""
544572
import yaml

0 commit comments

Comments
 (0)