Skip to content

Commit e40c74b

Browse files
committed
fix: ensure django_mongodb_extensions is installed before dbx project run
Replace the conditional install check (only install if project module not importable) with an unconditional dependency sync, and add _ensure_package_installed to handle django_mongodb_extensions separately. The helper checks for a local clone first (flat: base_dir/<repo>, group: base_dir/django/<repo>) and falls back to PyPI. It always uses --reinstall to recover from stale editable installs pointing to deleted source directories, which uv would otherwise consider already-installed and skip.
1 parent 227ccd9 commit e40c74b

1 file changed

Lines changed: 67 additions & 32 deletions

File tree

src/dbx_python_cli/commands/project.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,56 @@ def add_project(
515515
)
516516

517517

518+
def _ensure_package_installed(
519+
import_name: str,
520+
python_path: str,
521+
base_dir,
522+
verbose: bool = False,
523+
) -> None:
524+
"""Ensure a package is installed, preferring a local clone over PyPI.
525+
526+
Checks if already importable; if not, looks for a local clone at
527+
base_dir/<repo-name> (flat layout) or base_dir/django/<repo-name>
528+
(group layout), then falls back to installing from PyPI.
529+
"""
530+
check = subprocess.run(
531+
[python_path, "-c", f"import {import_name}"],
532+
capture_output=True,
533+
cwd="/tmp",
534+
)
535+
if check.returncode == 0:
536+
return
537+
538+
repo_name = import_name.replace("_", "-")
539+
540+
if base_dir is not None:
541+
for clone_path in [Path(base_dir) / repo_name, Path(base_dir) / "django" / repo_name]:
542+
if clone_path.exists() and (
543+
(clone_path / "pyproject.toml").exists() or (clone_path / "setup.py").exists()
544+
):
545+
typer.echo(f"📦 Installing {repo_name} from local clone at {clone_path}...")
546+
subprocess.run(
547+
["uv", "pip", "install", "--reinstall", "--python", python_path, "-e", str(clone_path)],
548+
capture_output=not verbose,
549+
check=False,
550+
)
551+
return
552+
553+
# Use --reinstall to override any stale editable installs pointing to a
554+
# deleted source directory (uv skips reinstall otherwise, leaving a broken .pth).
555+
typer.echo(f"📦 Installing {repo_name} from PyPI...")
556+
result = subprocess.run(
557+
["uv", "pip", "install", "--reinstall", "--python", python_path, repo_name],
558+
capture_output=True,
559+
text=True,
560+
check=False,
561+
)
562+
if result.returncode != 0:
563+
typer.echo(f"⚠️ Failed to install {repo_name} from PyPI:", err=True)
564+
if result.stderr:
565+
typer.echo(result.stderr.strip(), err=True)
566+
567+
518568
def _create_pyproject_toml(
519569
project_path: Path, project_name: str, settings_path: str = "settings.base"
520570
):
@@ -735,43 +785,28 @@ def run_project(
735785
proj = resolve_project_path(name, directory)
736786
python_path, venv_type = get_django_python_path(proj, directory)
737787

738-
# Check if the project is installed in the venv
739-
# This is important when using the Django group venv
788+
# Always sync project dependencies before starting to ensure all declared
789+
# dependencies (including ones added after the project was first installed)
790+
# are present in the venv. uv pip install is fast and idempotent.
740791
pyproject_path = proj.project_path / "pyproject.toml"
741792
if pyproject_path.exists():
742-
# Check if the project is installed by trying to import it
743-
# We need to clear PYTHONPATH and run from a different directory to check actual installation
744-
module_name = proj.name.replace("-", "_")
745-
check_env = os.environ.copy()
746-
check_env.pop(
747-
"PYTHONPATH", None
748-
) # Remove PYTHONPATH to check actual installation
749-
check_cmd = [
793+
typer.echo(f"📦 Syncing project dependencies for '{proj.name}'...")
794+
install_result = install_package(
795+
proj.project_path,
750796
python_path,
751-
"-c",
752-
f"import importlib.util; import sys; sys.exit(0 if importlib.util.find_spec('{module_name}') else 1)",
753-
]
754-
# Run from /tmp to avoid Python adding the current directory to sys.path
755-
result = subprocess.run(
756-
check_cmd, capture_output=True, env=check_env, cwd="/tmp"
797+
install_dir=None,
798+
extras=None,
799+
groups=None,
800+
verbose=verbose,
757801
)
758-
759-
if result.returncode != 0:
760-
# Project not installed, install it
761-
typer.echo(f"📦 Installing project dependencies for '{proj.name}'...")
762-
install_result = install_package(
763-
proj.project_path,
764-
python_path,
765-
install_dir=None,
766-
extras=None,
767-
groups=None,
768-
verbose=False,
802+
if install_result == "failed":
803+
typer.echo(
804+
f"⚠️ Warning: Failed to install project '{proj.name}'. Some dependencies may be missing.",
805+
err=True,
769806
)
770-
if install_result != "success":
771-
typer.echo(
772-
f"⚠️ Warning: Failed to install project '{proj.name}'. Some dependencies may be missing.",
773-
err=True,
774-
)
807+
808+
# Ensure django_mongodb_extensions is available: prefer a local clone over PyPI.
809+
_ensure_package_installed("django_mongodb_extensions", python_path, proj.base_dir, verbose)
775810

776811
# Check if frontend exists
777812
frontend_path = proj.project_path / "frontend"

0 commit comments

Comments
 (0)