Skip to content

Commit 36f405e

Browse files
authored
Merge pull request #318 from tcdent/install
Install script
2 parents b2e0469 + 54ceaec commit 36f405e

File tree

5 files changed

+810
-20
lines changed

5 files changed

+810
-20
lines changed

agentstack/conf.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
PATH: Path = Path()
1616

1717

18+
class NoProjectError(Exception):
19+
pass
20+
21+
1822
def assert_project() -> None:
1923
try:
2024
ConfigFile()
2125
return
2226
except FileNotFoundError:
23-
raise Exception("Could not find agentstack.json, are you in an AgentStack project directory?")
27+
raise NoProjectError("Could not find agentstack.json, are you in an AgentStack project directory?")
2428

2529

2630
def set_path(path: Union[str, Path, None]):

agentstack/packaging.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import subprocess
66
import select
7+
import site
78
from packaging.requirements import Requirement
89
from agentstack import conf, log
910

@@ -20,10 +21,17 @@
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

2432
def 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

4452
def 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]

agentstack/update.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from packaging.version import parse as parse_version, Version
66
import inquirer
7-
from agentstack import log
7+
from agentstack import conf, log
88
from agentstack.utils import term_color, get_version, get_framework, get_base_dir
99
from agentstack import packaging
1010

@@ -24,7 +24,7 @@
2424
USER_GUID_FILE_PATH = get_base_dir() / ".cli-user-guid"
2525
INSTALL_PATH = Path(sys.executable).parent.parent
2626
ENDPOINT_URL = "https://pypi.org/simple"
27-
CHECK_EVERY = 3600 # hour
27+
CHECK_EVERY = 12 * 60 * 60 # 12 hours
2828

2929

3030
def _is_ci_environment():
@@ -113,7 +113,15 @@ def check_for_updates(update_requested: bool = False):
113113
if inquirer.confirm(
114114
f"New version of {AGENTSTACK_PACKAGE} available: {latest_version}! Do you want to install?"
115115
):
116-
packaging.upgrade(f'{AGENTSTACK_PACKAGE}[{get_framework()}]')
116+
try:
117+
# handle update inside a user project
118+
conf.assert_project()
119+
packaging.upgrade(f'{AGENTSTACK_PACKAGE}[{get_framework()}]')
120+
except conf.NoProjectError:
121+
# handle update for system version of agentstack
122+
packaging.set_python_executable(sys.executable)
123+
packaging.upgrade(AGENTSTACK_PACKAGE, use_venv=False)
124+
117125
log.success(f"{AGENTSTACK_PACKAGE} updated. Re-run your command to use the latest version.")
118126
else:
119127
log.info("Skipping update. Run `agentstack update` to install the latest version.")

0 commit comments

Comments
 (0)