@@ -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+
518568def _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