import subprocess
from argparse import Namespace
from dataclasses import dataclass
from pathlib import Path
from rich import print as rprint
from rich.prompt import Prompt
from uv_start.config import clean_env
from uv_start.exceptions import ProjectCreationError
[docs]
@dataclass
class CommandDispatcher:
"""Dispatch commands based on argument pattern"""
args: Namespace
original_cwd: Path
def __post_init__(self) -> None:
self.project_path = self.original_cwd / self.args.project_name
[docs]
def check_dir_exists(self) -> None:
if self.project_path.exists():
raise ProjectCreationError(
f"Cannot create project '{self.args.project_name}'\n"
f"Directory already exists at: {self.original_cwd}\n\n"
"Try using a different project name or remove the existing directory."
)
[docs]
def dispatch(self) -> None:
"""Route to appropriate command handler based on argument pattern"""
if getattr(self.args, "data", False):
self._create_data_project()
return
flags = self._get_project_flags()
self._create_project(flags, workspace=self.args.workspace)
def _get_project_flags(self) -> list[str]:
"""Convert project type to uv flags"""
match self.args.type:
case "lib":
return ["--lib"]
case "app":
return ["--app", "--package"]
case "package":
return ["--package"]
case _:
raise ValueError(f"Unknown project type: {self.args.type}")
def _create_project(
self, flags: list[str], workspace: bool = False
) -> None:
"""Create a new project with specified flags"""
project_type = " ".join(flag.strip("-") for flag in flags)
rprint(
f"[green]Creating {project_type} project at {self.original_cwd}...[/green]"
)
try:
subprocess.run(
[
"uv",
"init",
self.args.project_name,
*flags,
"--python",
self.args.python,
],
check=True,
cwd=self.original_cwd,
env=clean_env(),
)
# Create tests directory
tests_dir = self.project_path / "tests"
tests_dir.mkdir(exist_ok=True)
# Create an empty __init__.py in tests directory
(tests_dir / "test_init.py").touch()
with open(tests_dir / "test_init.py", "w") as f:
f.write("def test_init():\n assert True\n")
rprint(
f"[green]✓[/green] Successfully created {project_type} project '[bold]{self.args.project_name}[/bold]'"
)
if workspace:
self._initialize_workspace()
except subprocess.CalledProcessError as e:
raise ProjectCreationError(
f"Failed to create {project_type} project: {e}"
) from e
def _initialize_workspace(self) -> None:
"""Initialize workspace configuration after project creation"""
packages_path = self.project_path / "packages"
packages_path.mkdir(exist_ok=True)
rprint("[green]Initializing workspace...[/green]")
common_utils = Prompt.ask(
"Do you want to add common utilities?",
choices=["y", "n"],
default="n",
)
if common_utils == "y":
utils_name = Prompt.ask("Enter the name of the utils library: ")
self._add_common_utils(utils_name)
other_projects = Prompt.ask(
"Do you want to add other projects?",
choices=["y", "n"],
default="n",
)
if other_projects == "y":
project_name = Prompt.ask("Enter the project-name: ")
self._add_other_projects(project_name)
def _add_common_utils(self, utils_name: str) -> None:
"""Add common utilities to the workspace"""
try:
subprocess.run(
[
"uv",
"init",
utils_name,
"--lib",
],
check=True,
cwd=self.project_path / "packages",
env=clean_env(),
)
subprocess.run(
[
"uv",
"add",
f"./packages/{utils_name}",
"--editable",
],
check=True,
cwd=self.project_path,
env=clean_env(),
)
rprint("[green]✓[/green] Successfully added common_utils'")
except subprocess.CalledProcessError as e:
raise ProjectCreationError(
f"Failed to create common_utils: {e}"
) from e
def _create_data_project(self) -> None:
"""Create a plain data analysis project with scientific Python stack."""
rprint(
f"[green]Creating data analysis project at {self.original_cwd}...[/green]"
)
try:
subprocess.run(
[
"uv",
"init",
self.args.project_name,
"--python",
self.args.python,
],
check=True,
cwd=self.original_cwd,
env=clean_env(),
)
subprocess.run(
[
"uv",
"add",
"jupyter",
"pandas",
"matplotlib",
"seaborn",
],
check=True,
cwd=self.project_path,
env=clean_env(),
)
rprint(
f"[green]✓[/green] Successfully created data project '[bold]{self.args.project_name}[/bold]'"
)
except subprocess.CalledProcessError as e:
raise ProjectCreationError(
f"Failed to create data project: {e}"
) from e
def _add_other_projects(self, project_name: str) -> None:
"""Add other projects to the workspace"""
try:
subprocess.run(
[
"uv",
"init",
project_name,
"--package",
"--app",
],
check=True,
cwd=self.project_path / "packages",
env=clean_env(),
)
subprocess.run(
[
"uv",
"add",
f"./packages/{project_name}",
"--editable",
],
check=True,
cwd=self.project_path,
env=clean_env(),
)
rprint(f"[green]✓[/green] Successfully created {project_name}")
except subprocess.CalledProcessError as e:
raise ProjectCreationError(
f"Failed to create {project_name}: {e}"
) from e