44import re
55import subprocess
66import select
7+ import site
78from packaging .requirements import Requirement
89from agentstack import conf , log
910
2021# In testing, when this was not set, packages could end up in the pyenv's
2122# site-packages directory; it's possible an environment variable can control this.
2223
24+ _python_executable = ".venv/bin/python"
25+
26+ def set_python_executable (path : str ):
27+ global _python_executable
28+
29+ _python_executable = path
30+
2331
2432def install (package : str ):
2533 """Install a package with `uv` and add it to pyproject.toml."""
26-
34+ global _python_executable
2735 from agentstack .cli .spinner import Spinner
2836
2937 def on_progress (line : str ):
@@ -35,15 +43,15 @@ def on_error(line: str):
3543
3644 with Spinner (f"Installing { package } " ) as spinner :
3745 _wrap_command_with_callbacks (
38- [get_uv_bin (), 'add' , '--python' , '.venv/bin/python' , package ],
46+ [get_uv_bin (), 'add' , '--python' , _python_executable , package ],
3947 on_progress = on_progress ,
4048 on_error = on_error ,
4149 )
4250
4351
4452def install_project ():
4553 """Install all dependencies for the user's project."""
46-
54+ global _python_executable
4755 from agentstack .cli .spinner import Spinner
4856
4957 def on_progress (line : str ):
@@ -56,14 +64,14 @@ def on_error(line: str):
5664 try :
5765 with Spinner (f"Installing project dependencies." ) as spinner :
5866 result = _wrap_command_with_callbacks (
59- [get_uv_bin (), 'pip' , 'install' , '--python' , '.venv/bin/python' , '.' ],
67+ [get_uv_bin (), 'pip' , 'install' , '--python' , _python_executable , '.' ],
6068 on_progress = on_progress ,
6169 on_error = on_error ,
6270 )
6371 if result is False :
6472 spinner .clear_and_log ("Retrying uv installation with --no-cache flag..." , 'info' )
6573 _wrap_command_with_callbacks (
66- [get_uv_bin (), 'pip' , 'install' , '--no-cache' , '--python' , '.venv/bin/python' , '.' ],
74+ [get_uv_bin (), 'pip' , 'install' , '--no-cache' , '--python' , _python_executable , '.' ],
6775 on_progress = on_progress ,
6876 on_error = on_error ,
6977 )
@@ -87,13 +95,13 @@ def on_error(line: str):
8795
8896 log .info (f"Uninstalling { requirement .name } " )
8997 _wrap_command_with_callbacks (
90- [get_uv_bin (), 'remove' , '--python' , '.venv/bin/python' , requirement .name ],
98+ [get_uv_bin (), 'remove' , '--python' , _python_executable , requirement .name ],
9199 on_progress = on_progress ,
92100 on_error = on_error ,
93101 )
94102
95103
96- def upgrade (package : str ):
104+ def upgrade (package : str , use_venv : bool = True ):
97105 """Upgrade a package with `uv`."""
98106
99107 # TODO should we try to update the project's pyproject.toml as well?
@@ -104,11 +112,17 @@ def on_progress(line: str):
104112 def on_error (line : str ):
105113 log .error (f"uv: [error]\n { line .strip ()} " )
106114
115+ extra_args = []
116+ if not use_venv :
117+ # uv won't let us install without a venv if we don't specify a target
118+ extra_args = ['--target' , site .getusersitepackages ()]
119+
107120 log .info (f"Upgrading { package } " )
108121 _wrap_command_with_callbacks (
109- [get_uv_bin (), 'pip' , 'install' , '-U' , '--python' , '.venv/bin/python' , package ],
122+ [get_uv_bin (), 'pip' , 'install' , '-U' , '--python' , _python_executable , * extra_args , package ],
110123 on_progress = on_progress ,
111124 on_error = on_error ,
125+ use_venv = use_venv ,
112126 )
113127
114128
@@ -156,19 +170,21 @@ def _wrap_command_with_callbacks(
156170 on_progress : Callable [[str ], None ] = lambda x : None ,
157171 on_complete : Callable [[str ], None ] = lambda x : None ,
158172 on_error : Callable [[str ], None ] = lambda x : None ,
173+ use_venv : bool = True ,
159174) -> bool :
160175 """Run a command with progress callbacks. Returns bool for cmd success."""
161176 process = None
162177 try :
163178 all_lines = ''
164- process = subprocess .Popen (
165- command ,
166- cwd = conf .PATH .absolute (),
167- env = _setup_env (),
168- stdout = subprocess .PIPE ,
169- stderr = subprocess .PIPE ,
170- text = True ,
171- )
179+ sub_args = {
180+ 'cwd' : conf .PATH .absolute (),
181+ 'stdout' : subprocess .PIPE ,
182+ 'stderr' : subprocess .PIPE ,
183+ 'text' : True ,
184+ }
185+ if use_venv :
186+ sub_args ['env' ] = _setup_env ()
187+ process = subprocess .Popen (command , ** sub_args ) # type: ignore
172188 assert process .stdout and process .stderr # appease type checker
173189
174190 readable = [process .stdout , process .stderr ]
0 commit comments