Skip to content

Commit 4d5a2f7

Browse files
committed
feat: allow dbx install to accept '.' or any path as repo argument
- Import find_repo_by_path in install.py - Add path detection in both the --show-options else branch and the main install else branch - Update repo_name to real name after path resolution so install-dir config, extras/groups lookups, and messages use the actual repo name - Add tests for '.' from repo root and from an unmanaged directory
1 parent db102dc commit 4d5a2f7

2 files changed

Lines changed: 113 additions & 25 deletions

File tree

src/dbx_python_cli/commands/install.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from dbx_python_cli.utils.repo import (
1212
find_all_repos,
1313
find_repo_by_name,
14+
find_repo_by_path,
1415
get_base_dir,
1516
get_build_commands,
1617
get_config,
@@ -560,12 +561,31 @@ def install_callback(
560561

561562
selected_group = repo_group
562563
else:
563-
# Find the repository across all groups
564-
repo = find_repo_by_name(repo_name, base_dir, config)
565-
if not repo:
566-
typer.echo(f"❌ Error: Repository '{repo_name}' not found", err=True)
567-
typer.echo("\nUse 'dbx install --list' to see available repositories")
568-
raise typer.Exit(1)
564+
# Detect path-like inputs: ".", "..", absolute paths, relative paths with /
565+
_is_path_like = (
566+
repo_name in (".", "..")
567+
or repo_name.startswith(("./", "../", "/", "~/"))
568+
or "/" in repo_name
569+
or Path(repo_name).is_dir()
570+
)
571+
572+
if _is_path_like:
573+
repo = find_repo_by_path(repo_name, base_dir, config)
574+
if not repo:
575+
typer.echo(
576+
f"❌ Error: No managed repository found at '{Path(repo_name).resolve()}'",
577+
err=True,
578+
)
579+
typer.echo("\nUse 'dbx install --list' to see available repositories")
580+
raise typer.Exit(1)
581+
repo_name = repo["name"]
582+
else:
583+
# Find the repository across all groups
584+
repo = find_repo_by_name(repo_name, base_dir, config)
585+
if not repo:
586+
typer.echo(f"❌ Error: Repository '{repo_name}' not found", err=True)
587+
typer.echo("\nUse 'dbx install --list' to see available repositories")
588+
raise typer.Exit(1)
569589

570590
repo_path = repo["path"]
571591
selected_group = repo["group"]
@@ -824,29 +844,48 @@ def install_callback(
824844
"group": venv_group,
825845
}
826846
else:
827-
# Find the repository (will return highest priority match if multiple exist)
828-
repo = find_repo_by_name(repo_name, base_dir, config)
829-
if not repo:
830-
typer.echo(f"❌ Error: Repository '{repo_name}' not found", err=True)
831-
typer.echo("\nRun 'dbx install --list' to see available repositories")
832-
raise typer.Exit(1)
847+
# Detect path-like inputs: ".", "..", absolute paths, relative paths with /
848+
_is_path_like = (
849+
repo_name in (".", "..")
850+
or repo_name.startswith(("./", "../", "/", "~/"))
851+
or "/" in repo_name
852+
or Path(repo_name).is_dir()
853+
)
833854

834-
# Check if repo exists in multiple groups (suppress warning if one is a global group)
835-
all_repos = find_all_repos(base_dir, config)
836-
matching_repos = [r for r in all_repos if r["name"] == repo_name]
837-
if len(matching_repos) > 1:
838-
groups = [r["group"] for r in matching_repos]
839-
global_group_names = set(get_global_groups(config))
840-
# Only warn if none of the groups are global groups
841-
if not any(g in global_group_names for g in groups):
842-
typer.echo(
843-
f"⚠️ Warning: Repository '{repo_name}' found in multiple groups: {', '.join(groups)}",
844-
err=True,
845-
)
855+
if _is_path_like:
856+
repo = find_repo_by_path(repo_name, base_dir, config)
857+
if not repo:
846858
typer.echo(
847-
f"⚠️ Using '{repo['group']}' group. Use -g to specify a different group.\n",
859+
f"❌ Error: No managed repository found at '{Path(repo_name).resolve()}'",
848860
err=True,
849861
)
862+
typer.echo("\nRun 'dbx install --list' to see available repositories")
863+
raise typer.Exit(1)
864+
repo_name = repo["name"]
865+
else:
866+
# Find the repository (will return highest priority match if multiple exist)
867+
repo = find_repo_by_name(repo_name, base_dir, config)
868+
if not repo:
869+
typer.echo(f"❌ Error: Repository '{repo_name}' not found", err=True)
870+
typer.echo("\nRun 'dbx install --list' to see available repositories")
871+
raise typer.Exit(1)
872+
873+
# Check if repo exists in multiple groups (suppress warning if one is a global group)
874+
all_repos = find_all_repos(base_dir, config)
875+
matching_repos = [r for r in all_repos if r["name"] == repo_name]
876+
if len(matching_repos) > 1:
877+
groups = [r["group"] for r in matching_repos]
878+
global_group_names = set(get_global_groups(config))
879+
# Only warn if none of the groups are global groups
880+
if not any(g in global_group_names for g in groups):
881+
typer.echo(
882+
f"⚠️ Warning: Repository '{repo_name}' found in multiple groups: {', '.join(groups)}",
883+
err=True,
884+
)
885+
typer.echo(
886+
f"⚠️ Using '{repo['group']}' group. Use -g to specify a different group.\n",
887+
err=True,
888+
)
850889

851890
repo_path = Path(repo["path"])
852891
# Default to repo's own group

tests/test_install_command.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,55 @@ def test_install_nonexistent_repo(tmp_path):
4747
assert "not found" in result.stdout or "dbx install --list" in result.stdout
4848

4949

50+
def test_install_dot_from_repo_root(tmp_path, monkeypatch):
51+
"""Test that '.' resolves to the repo at the current directory."""
52+
group_dir = tmp_path / "pymongo"
53+
repo_dir = group_dir / "mongo-python-driver"
54+
repo_dir.mkdir(parents=True)
55+
(repo_dir / ".git").mkdir()
56+
(repo_dir / "setup.py").write_text("# setup.py")
57+
58+
monkeypatch.chdir(repo_dir)
59+
60+
with patch("dbx_python_cli.utils.repo.get_config_path") as _mock_path:
61+
with patch("dbx_python_cli.commands.install.get_config") as mock_config:
62+
with patch("dbx_python_cli.commands.install.get_venv_info") as mock_venv:
63+
with patch("subprocess.run") as mock_run:
64+
mock_config.return_value = {"repo": {"base_dir": str(tmp_path)}}
65+
mock_venv.return_value = ("python", "venv")
66+
mock_result = MagicMock()
67+
mock_result.returncode = 0
68+
mock_run.return_value = mock_result
69+
70+
result = runner.invoke(app, ["install", "."])
71+
assert result.exit_code == 0
72+
assert "Installing dependencies" in result.stdout
73+
assert "Package installed successfully" in result.stdout
74+
# Confirm the real repo name appears, not "."
75+
assert "mongo-python-driver" in result.stdout
76+
77+
78+
def test_install_dot_not_in_managed_repo(tmp_path, monkeypatch):
79+
"""Test that '.' in an unmanaged directory gives a clear error."""
80+
group_dir = tmp_path / "pymongo"
81+
repo_dir = group_dir / "mongo-python-driver"
82+
repo_dir.mkdir(parents=True)
83+
(repo_dir / ".git").mkdir()
84+
85+
unrelated = tmp_path / "unrelated"
86+
unrelated.mkdir()
87+
monkeypatch.chdir(unrelated)
88+
89+
with patch("dbx_python_cli.utils.repo.get_config_path") as _mock_path:
90+
with patch("dbx_python_cli.commands.install.get_config") as mock_config:
91+
mock_config.return_value = {"repo": {"base_dir": str(tmp_path)}}
92+
93+
result = runner.invoke(app, ["install", "."])
94+
assert result.exit_code == 1
95+
output = result.stdout + result.stderr
96+
assert "No managed repository found" in output
97+
98+
5099
def test_install_basic_success(tmp_path):
51100
"""Test basic install without extras or groups."""
52101
# Create mock repository structure

0 commit comments

Comments
 (0)