11# environment_manager.py
22# A comprehensive system for managing a dedicated virtual environment for graph execution.
3- # Now handles venv creation from a Nuitka-compiled executable.
3+ # Now correctly handles venv creation from a Nuitka-compiled executable.
44
55import os
66import sys
@@ -29,11 +29,12 @@ def __init__(self, venv_path, requirements, task="setup"):
2929 self .requirements = requirements
3030 self .task = task
3131
32- def get_pip_executable (self ):
32+ def get_venv_python_executable (self ):
33+ """Gets the path to the python executable inside the created venv."""
3334 if sys .platform == "win32" :
34- return os .path .join (self .venv_path , "Scripts" , "pip .exe" )
35+ return os .path .join (self .venv_path , "Scripts" , "python .exe" )
3536 else :
36- return os .path .join (self .venv_path , "bin" , "pip " )
37+ return os .path .join (self .venv_path , "bin" , "python " )
3738
3839 def run (self ):
3940 try :
@@ -51,29 +52,31 @@ def run_setup(self):
5152
5253 if is_frozen ():
5354 # For compiled apps, find the bundled Python runtime and use it to create the venv.
54- # Assumes the runtime is in a 'python_runtime' folder next to the main executable.
5555 base_path = os .path .dirname (sys .executable )
56- python_exe = os .path .join (base_path , "python_runtime" , "python.exe" )
57- if not os .path .exists (python_exe ):
58- self .finished .emit (False , "Bundled Python runtime not found!" )
56+ runtime_python_exe = os .path .join (base_path , "python_runtime" , "python.exe" )
57+
58+ if not os .path .exists (runtime_python_exe ):
59+ error_msg = f"Bundled Python runtime not found at '{ runtime_python_exe } '. " "The application build is incomplete."
60+ self .finished .emit (False , error_msg )
5961 return
6062
61- cmd = [python_exe , "-m" , "venv" , self .venv_path ]
62- result = subprocess .run (cmd , capture_output = True , text = True )
63+ cmd = [runtime_python_exe , "-m" , "venv" , self .venv_path ]
64+ result = subprocess .run (cmd , capture_output = True , text = True , encoding = "utf-8" )
6365 if result .returncode != 0 :
6466 self .finished .emit (False , f"Failed to create venv: { result .stderr } " )
6567 return
6668 else :
6769 # For development, use the standard venv creation.
6870 venv .create (self .venv_path , with_pip = True )
6971
70- pip_exe = self .get_pip_executable ()
72+ venv_python_exe = self .get_venv_python_executable ()
7173 if not self .requirements :
7274 self .finished .emit (True , "Environment exists. No packages to install." )
7375 return
7476
7577 self .progress .emit (f"Installing { len (self .requirements )} dependencies..." )
76- cmd = [pip_exe , "install" ] + self .requirements
78+ # Use python -m pip to be robust
79+ cmd = [venv_python_exe , "-m" , "pip" , "install" ] + self .requirements
7780 process = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , text = True , encoding = "utf-8" )
7881 for line in iter (process .stdout .readline , "" ):
7982 self .progress .emit (line .strip ())
@@ -87,8 +90,8 @@ def run_setup(self):
8790 def run_verify (self ):
8891 """Verifies the venv and checks for installed packages."""
8992 self .progress .emit ("Starting verification..." )
90- pip_exe = self .get_pip_executable ()
91- if not os .path .exists (pip_exe ):
93+ venv_python_exe = self .get_venv_python_executable ()
94+ if not os .path .exists (venv_python_exe ):
9295 self .finished .emit (False , "Verification Failed: Virtual environment not found." )
9396 return
9497
@@ -98,7 +101,7 @@ def run_verify(self):
98101 return
99102
100103 self .progress .emit ("Checking installed packages..." )
101- cmd = [pip_exe , "freeze" ]
104+ cmd = [venv_python_exe , "-m" , "pip" , "freeze" ]
102105 result = subprocess .run (cmd , capture_output = True , text = True , encoding = "utf-8" )
103106 if result .returncode != 0 :
104107 self .finished .emit (False , "Verification Failed: Could not list installed packages." )
@@ -163,7 +166,6 @@ def __init__(self, venv_path, requirements, parent=None):
163166 action_layout .addWidget (self .verify_button )
164167 layout .addLayout (action_layout )
165168
166- # UI FIX: Use a read-only QLineEdit for the status to make it copyable.
167169 self .status_display = QLineEdit ("Status: Ready" )
168170 self .status_display .setReadOnly (True )
169171 layout .addWidget (self .status_display )
0 commit comments