-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initialize project now creates a virtual enviroment for you.
All package management done with `uv`. TODO: `packaging.remove` and `packaging.upgrade` still need to be implemented.
- Loading branch information
Showing
9 changed files
with
239 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from .cli import init_project_builder, configure_default_model, export_template | ||
from .cli import init_project_builder, configure_default_model, export_template, welcome_message | ||
from .init import init_project | ||
from .tools import list_tools, add_tool | ||
from .run import run_project |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import os, sys | ||
from typing import Optional | ||
from pathlib import Path | ||
from agentstack import conf | ||
from agentstack import packaging | ||
from agentstack.cli import welcome_message, init_project_builder | ||
from agentstack.utils import term_color | ||
|
||
|
||
# TODO move the rest of the CLI init tooling into this file | ||
|
||
|
||
def require_uv(): | ||
try: | ||
uv_bin = packaging.get_uv_bin() | ||
assert os.path.exists(uv_bin) | ||
except (AssertionError, ImportError): | ||
print(term_color("Error: uv is not installed.", 'red')) | ||
print("Full installation instructions at: https://docs.astral.sh/uv/getting-started/installation") | ||
match sys.platform: | ||
case 'linux' | 'darwin': | ||
print("Hint: run `curl -LsSf https://astral.sh/uv/install.sh | sh`") | ||
case _: | ||
pass | ||
sys.exit(1) | ||
|
||
|
||
def init_project( | ||
slug_name: Optional[str] = None, | ||
template: Optional[str] = None, | ||
use_wizard: bool = False, | ||
): | ||
""" | ||
Initialize a new project in the current directory. | ||
- create a new virtual environment | ||
- copy project skeleton | ||
- install dependencies | ||
""" | ||
welcome_message() | ||
|
||
# conf.PATH may have been set by the argument parser, but if not, use the slug_name | ||
if slug_name: | ||
conf.set_path(conf.PATH / slug_name) | ||
else: | ||
print("Error: No project directory specified.") | ||
print("Run `agentstack init <project_name> or use the --path flag.") | ||
sys.exit(1) | ||
|
||
if os.path.exists(conf.PATH): | ||
print(f"Error: Directory already exists: {conf.PATH}") | ||
sys.exit(1) | ||
|
||
print(term_color("🦾 Creating a new AgentStack project...", 'blue')) | ||
print(f"Using project directory: {conf.PATH.absolute()}") | ||
# copy the project skeleton, create a virtual environment, and install dependencies | ||
init_project_builder(slug_name, template, use_wizard) | ||
packaging.create_venv() | ||
packaging.install('.') | ||
|
||
print( | ||
"\n" | ||
"🚀 \033[92mAgentStack project generated successfully!\033[0m\n\n" | ||
" To get started, activate the virtual environment with:\n" | ||
f" cd {conf.PATH}\n" | ||
" source .venv/bin/activate\n\n" | ||
" Run your new agent with:\n" | ||
" agentstack run\n\n" | ||
" Or, run `agentstack quickstart` or `agentstack docs` for more next steps.\n" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,131 @@ | ||
import os | ||
from typing import Optional | ||
import os, sys | ||
from typing import Optional, Callable | ||
from pathlib import Path | ||
import re | ||
import subprocess | ||
import select | ||
from agentstack import conf | ||
from agentstack.exceptions import EnvironmentError | ||
|
||
PACKAGING_CMD = "poetry" | ||
|
||
DEFAULT_PYTHON_VERSION = "3.12" | ||
VENV_DIR_NAME: Path = Path(".venv") | ||
|
||
def install(package: str, path: Optional[str] = None): | ||
if path: | ||
os.chdir(path) | ||
os.system(f"{PACKAGING_CMD} add {package}") | ||
|
||
def install(package: str): | ||
""" | ||
Install a package with `uv`. | ||
Filter output to only show useful progress messages. | ||
""" | ||
RE_USEFUL_PROGRESS = re.compile(r'^(Resolved|Prepared|Installed|Audited)') | ||
|
||
def on_progress(line: str): | ||
# only print these four messages: | ||
# Resolved 78 packages in 225ms | ||
# Prepared 12 packages in 915ms | ||
# Installed 78 packages in 65ms | ||
# Audited 1 package in 28ms | ||
if RE_USEFUL_PROGRESS.match(line): | ||
print(line.strip()) | ||
|
||
def on_error(line: str): | ||
print(f"uv: [error]\n {line.strip()}") | ||
|
||
# explicitly specify the --python executable to use so that the packages | ||
# are installed into the correct virtual environment | ||
_wrap_command_with_callbacks( | ||
[get_uv_bin(), 'pip', 'install', '--python', '.venv/bin/python', package], | ||
on_progress=on_progress, | ||
on_error=on_error, | ||
) | ||
|
||
|
||
def remove(package: str): | ||
os.system(f"{PACKAGING_CMD} remove {package}") | ||
raise NotImplementedError("TODO `packaging.remove`") | ||
|
||
|
||
def upgrade(package: str): | ||
os.system(f"{PACKAGING_CMD} add {package}") | ||
raise NotImplementedError("TODO `packaging.upgrade`") | ||
|
||
|
||
def create_venv(python_version: str = DEFAULT_PYTHON_VERSION): | ||
"""Intialize a virtual environment in the project directory of one does not exist.""" | ||
if os.path.exists(conf.PATH / VENV_DIR_NAME): | ||
return # venv already exists | ||
|
||
RE_USEFUL_PROGRESS = re.compile(r'^(Using|Creating)') | ||
|
||
def on_progress(line: str): | ||
if RE_USEFUL_PROGRESS.match(line): | ||
print(line.strip()) | ||
|
||
def on_error(line: str): | ||
print(f"uv: [error]\n {line.strip()}") | ||
|
||
_wrap_command_with_callbacks( | ||
[get_uv_bin(), 'venv', '--python', python_version], | ||
on_progress=on_progress, | ||
on_error=on_error, | ||
) | ||
|
||
|
||
def get_uv_bin() -> str: | ||
"""Find the path to the uv binary.""" | ||
try: | ||
import uv | ||
|
||
return uv.find_uv_bin() | ||
except ImportError as e: | ||
raise e | ||
|
||
|
||
def _setup_env() -> dict[str, str]: | ||
"""Copy the current environment and add the virtual environment path for use by a subprocess.""" | ||
env = os.environ.copy() | ||
env.setdefault("VIRTUAL_ENV", VENV_DIR_NAME) | ||
env["UV_INTERNAL__PARENT_INTERPRETER"] = sys.executable | ||
return env | ||
|
||
|
||
def _wrap_command_with_callbacks( | ||
command: list[str], | ||
on_progress: Callable[[str], None] = lambda x: None, | ||
on_complete: Callable[[str], None] = lambda x: None, | ||
on_error: Callable[[str], None] = lambda x: None, | ||
) -> None: | ||
"""Run a command with progress callbacks.""" | ||
try: | ||
all_lines = '' | ||
process = subprocess.Popen( | ||
command, | ||
cwd=conf.PATH.absolute(), | ||
env=_setup_env(), | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
text=True, | ||
) | ||
assert process.stdout and process.stderr # appease type checker | ||
|
||
readable = [process.stdout, process.stderr] | ||
while readable: | ||
ready, _, _ = select.select(readable, [], []) | ||
for fd in ready: | ||
line = fd.readline() | ||
if not line: | ||
readable.remove(fd) | ||
continue | ||
|
||
on_progress(line) | ||
all_lines += line | ||
|
||
if process.wait() == 0: # return code: success | ||
on_complete(all_lines) | ||
else: | ||
on_error(all_lines) | ||
except Exception as e: | ||
on_error(str(e)) | ||
finally: | ||
try: | ||
process.terminate() | ||
except: | ||
pass |
23 changes: 9 additions & 14 deletions
23
agentstack/templates/crewai/{{cookiecutter.project_metadata.project_slug}}/pyproject.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,13 @@ | ||
[tool.poetry] | ||
[project] | ||
name = "{{cookiecutter.project_metadata.project_name}}" | ||
version = "{{cookiecutter.project_metadata.version}}" | ||
description = "{{cookiecutter.project_metadata.description}}" | ||
authors = ["{{cookiecutter.project_metadata.author_name}}"] | ||
license = "{{cookiecutter.project_metadata.license}}" | ||
package-mode = false | ||
authors = [ | ||
{ name = "{{cookiecutter.project_metadata.author_name}}" } | ||
] | ||
license = { text = "{{cookiecutter.project_metadata.license}}" } | ||
requires-python = ">=3.10" | ||
|
||
[tool.poetry.dependencies] | ||
python = ">=3.10,<=3.13" | ||
agentstack = {extras = ["{{cookiecutter.framework}}"], version="{{cookiecutter.project_metadata.agentstack_version}}"} | ||
|
||
[project.scripts] | ||
{{cookiecutter.project_metadata.project_name}} = "{{cookiecutter.project_metadata.project_name}}.main:run" | ||
run_crew = "{{cookiecutter.project_metadata.project_name}}.main:run" | ||
train = "{{cookiecutter.project_metadata.project_name}}.main:train" | ||
replay = "{{cookiecutter.project_metadata.project_name}}.main:replay" | ||
test = "{{cookiecutter.project_metadata.project_name}}.main:test" | ||
dependencies = [ | ||
"agentstack[{{cookiecutter.framework}}]=={{cookiecutter.project_metadata.agentstack_version}}", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters