4
4
import re
5
5
import subprocess
6
6
import select
7
+ import site
7
8
from packaging .requirements import Requirement
8
9
from agentstack import conf , log
9
10
20
21
# In testing, when this was not set, packages could end up in the pyenv's
21
22
# site-packages directory; it's possible an environment variable can control this.
22
23
24
+ _python_executable = ".venv/bin/python"
25
+
26
+ def set_python_executable (path : str ):
27
+ global _python_executable
28
+
29
+ _python_executable = path
30
+
23
31
24
32
def install (package : str ):
25
33
"""Install a package with `uv` and add it to pyproject.toml."""
26
-
34
+ global _python_executable
27
35
from agentstack .cli .spinner import Spinner
28
36
29
37
def on_progress (line : str ):
@@ -35,15 +43,15 @@ def on_error(line: str):
35
43
36
44
with Spinner (f"Installing { package } " ) as spinner :
37
45
_wrap_command_with_callbacks (
38
- [get_uv_bin (), 'add' , '--python' , '.venv/bin/python' , package ],
46
+ [get_uv_bin (), 'add' , '--python' , _python_executable , package ],
39
47
on_progress = on_progress ,
40
48
on_error = on_error ,
41
49
)
42
50
43
51
44
52
def install_project ():
45
53
"""Install all dependencies for the user's project."""
46
-
54
+ global _python_executable
47
55
from agentstack .cli .spinner import Spinner
48
56
49
57
def on_progress (line : str ):
@@ -56,14 +64,14 @@ def on_error(line: str):
56
64
try :
57
65
with Spinner (f"Installing project dependencies." ) as spinner :
58
66
result = _wrap_command_with_callbacks (
59
- [get_uv_bin (), 'pip' , 'install' , '--python' , '.venv/bin/python' , '.' ],
67
+ [get_uv_bin (), 'pip' , 'install' , '--python' , _python_executable , '.' ],
60
68
on_progress = on_progress ,
61
69
on_error = on_error ,
62
70
)
63
71
if result is False :
64
72
spinner .clear_and_log ("Retrying uv installation with --no-cache flag..." , 'info' )
65
73
_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 , '.' ],
67
75
on_progress = on_progress ,
68
76
on_error = on_error ,
69
77
)
@@ -87,13 +95,13 @@ def on_error(line: str):
87
95
88
96
log .info (f"Uninstalling { requirement .name } " )
89
97
_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 ],
91
99
on_progress = on_progress ,
92
100
on_error = on_error ,
93
101
)
94
102
95
103
96
- def upgrade (package : str ):
104
+ def upgrade (package : str , use_venv : bool = True ):
97
105
"""Upgrade a package with `uv`."""
98
106
99
107
# TODO should we try to update the project's pyproject.toml as well?
@@ -104,11 +112,17 @@ def on_progress(line: str):
104
112
def on_error (line : str ):
105
113
log .error (f"uv: [error]\n { line .strip ()} " )
106
114
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
+
107
120
log .info (f"Upgrading { package } " )
108
121
_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 ],
110
123
on_progress = on_progress ,
111
124
on_error = on_error ,
125
+ use_venv = use_venv ,
112
126
)
113
127
114
128
@@ -156,19 +170,21 @@ def _wrap_command_with_callbacks(
156
170
on_progress : Callable [[str ], None ] = lambda x : None ,
157
171
on_complete : Callable [[str ], None ] = lambda x : None ,
158
172
on_error : Callable [[str ], None ] = lambda x : None ,
173
+ use_venv : bool = True ,
159
174
) -> bool :
160
175
"""Run a command with progress callbacks. Returns bool for cmd success."""
161
176
process = None
162
177
try :
163
178
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
172
188
assert process .stdout and process .stderr # appease type checker
173
189
174
190
readable = [process .stdout , process .stderr ]
0 commit comments