Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .envrc

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,7 @@ cython_debug/
.pypirc
.tetra_resources.pkl
.DS_Store

# dev only
.envrc
test_app/
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9
3.11
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"python.envFile": "${workspaceFolder}/.env",
"python.analysis.extraPaths": [
"${workspaceFolder}/src",
]
}
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ help: # Show this help menu
@awk 'BEGIN {FS = ":.*# "; printf "%-20s %s\n", "Target", "Description"} /^[a-zA-Z_-]+:.*# / {printf "%-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
@echo ""

dev: # Install development dependencies
dev: # Install development dependencies and package in editable mode
uv sync --all-groups
uv pip install -e .

update:
uv sync --upgrade --all-groups
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ flash = "tetra_rp.cli.main:app"
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
Expand Down
163 changes: 106 additions & 57 deletions src/tetra_rp/cli/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,86 +1,135 @@
"""Project initialization command."""

from pathlib import Path

import typer
from typing import Optional
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
import questionary

from tetra_rp.config import get_paths
from ..utils.skeleton import create_project_skeleton, get_available_templates
from ..utils.skeleton import create_project_skeleton
from ..utils.conda import (
check_conda_available,
create_conda_environment,
install_packages_in_env,
environment_exists,
get_activation_command,
)

console = Console()

# Required packages for flash run to work smoothly
REQUIRED_PACKAGES = [
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
"python-dotenv>=1.0.0",
"pydantic>=2.0.0",
"aiohttp>=3.9.0",
]


def init_command(
template: Optional[str] = typer.Option(
None, "--template", "-t", help="Project template to use"
project_name: str = typer.Argument(..., help="Project name"),
no_env: bool = typer.Option(
False, "--no-env", help="Skip conda environment creation"
),
force: bool = typer.Option(
False, "--force", "-f", help="Overwrite existing directory"
),
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
):
"""Create skeleton application with starter files."""
"""Create new Flash project with Flash Server and GPU workers."""

# Check if we're already in a Tetra project
paths = get_paths()
if paths.tetra_dir.exists() and not force:
console.print("Already in a Tetra project directory")
console.print("Use --force to overwrite existing configuration")
raise typer.Exit(1)
# Create project directory
project_dir = Path(project_name)

# Get available templates
available_templates = get_available_templates()

# Interactive template selection if not provided
if not template:
template_choices = []
for name, info in available_templates.items():
template_choices.append(f"{name} - {info['description']}")

try:
selected = questionary.select(
"Choose a project template:", choices=template_choices
).ask()

if not selected:
console.print("Template selection cancelled")
raise typer.Exit(1)

template = selected.split(" - ")[0]
except KeyboardInterrupt:
console.print("\nTemplate selection cancelled")
raise typer.Exit(1)

# Validate template choice
if template not in available_templates:
console.print(f"Unknown template: {template}")
console.print("Available templates:")
for name, info in available_templates.items():
console.print(f" • {name} - {info['description']}")
if project_dir.exists() and not force:
console.print(f"Directory '{project_name}' already exists")
console.print("Use --force to overwrite")
raise typer.Exit(1)

# Create project skeleton
template_info = available_templates[template]

with console.status(f"Creating project with {template} template..."):
created_files = create_project_skeleton(template, template_info, force)
# Create project directory
project_dir.mkdir(parents=True, exist_ok=True)

with console.status(f"Creating Flash project '{project_name}'..."):
create_project_skeleton(project_dir, force)

# Create conda environment if requested
env_created = False
if not no_env:
if not check_conda_available():
console.print(
"[yellow]Warning: conda not found. Skipping environment creation.[/yellow]"
)
console.print(
"Install Miniconda or Anaconda, or use --no-env flag to skip this step."
)
else:
# Check if environment already exists
if environment_exists(project_name):
console.print(
f"[yellow]Conda environment '{project_name}' already exists. Skipping creation.[/yellow]"
)
env_created = True
else:
# Create conda environment
with console.status(f"Creating conda environment '{project_name}'..."):
success, message = create_conda_environment(project_name)

if not success:
console.print(f"[yellow]Warning: {message}[/yellow]")
console.print(
"You can manually create the environment and install dependencies."
)
else:
env_created = True

# Install required packages
with console.status("Installing dependencies..."):
success, message = install_packages_in_env(
project_name, REQUIRED_PACKAGES, use_pip=True
)

if not success:
console.print(f"[yellow]Warning: {message}[/yellow]")
console.print(
"You can manually install dependencies: pip install -r requirements.txt"
)

# Success output
panel_content = f"Project initialized with [bold]{template}[/bold] template\n\n"
panel_content += "Created files:\n"
for file_path in created_files:
panel_content += f" • {file_path}\n"

console.print(Panel(panel_content, title="Project Initialized", expand=False))
panel_content = (
f"Flash project '[bold]{project_name}[/bold]' created successfully!\n\n"
)
panel_content += "Project structure:\n"
panel_content += f" {project_name}/\n"
panel_content += " ├── main.py # Flash Server (FastAPI)\n"
panel_content += " ├── workers/ # GPU workers\n"
panel_content += " │ └── example_worker.py\n"
panel_content += " ├── .env.example\n"
panel_content += " ├── requirements.txt\n"
panel_content += " └── README.md\n"

if env_created:
panel_content += (
f"\nConda environment '[bold]{project_name}[/bold]' created and configured"
)

console.print(Panel(panel_content, title="Project Created", expand=False))

# Next steps
console.print("\n[bold]Next steps:[/bold]")
steps_table = Table(show_header=False, box=None, padding=(0, 1))
steps_table.add_column("Step", style="bold cyan")
steps_table.add_column("Description")

steps_table.add_row("1.", "Edit .env with your RunPod API key")
steps_table.add_row("2.", "Install dependencies: pip install -r requirements.txt")
steps_table.add_row("3.", "Run your project: flash run")
steps_table.add_row("1.", f"cd {project_name}")

if env_created:
steps_table.add_row("2.", f"{get_activation_command(project_name)}")
steps_table.add_row("3.", "cp .env.example .env # Add your RUNPOD_API_KEY")
steps_table.add_row("4.", "flash run")
else:
steps_table.add_row("2.", "pip install -r requirements.txt")
steps_table.add_row("3.", "cp .env.example .env # Add your RUNPOD_API_KEY")
steps_table.add_row("4.", "flash run")

console.print(steps_table)
Loading
Loading