python-project-generator 3.2.2

Generates a Python project structure.
use anyhow::{bail, Result};

use crate::project_info::{ProjectInfo, ProjectManager};

const FASTAPI_BASE_DEPENDENCIES: &[&str] = &[
    "asyncpg",
    "camel-converter[pydantic]",
    "fastapi",
    "granian[pname,reload]",
    "httptools",
    "loguru",
    "orjson",
    "pwdlib[argon2]",
    "pydantic[email]",
    "pydantic-settings",
    "pyjwt",
    "python-multipart",
    "uvloop; sys_platform != 'win32'",
    "valkey",
];

const FASTAPI_BASE_DEV_DEPENDENCIES: &[&str] = &["httpx", "pytest-xdist"];

pub fn install_fastapi_dependencies(project_info: &ProjectInfo) -> Result<()> {
    match project_info.project_manager {
        ProjectManager::Uv => uv_fastapi_dependency_installer(project_info)?,
        ProjectManager::Poetry => poetry_fastapi_dependency_installer(project_info)?,
        ProjectManager::Setuptools => setuptools_fastapi_dependency_installer(project_info)?,
        ProjectManager::Maturin => maturin_fastapi_dependency_installer(project_info)?,
    };

    Ok(())
}

fn uv_fastapi_dependency_installer(project_info: &ProjectInfo) -> Result<()> {
    let dependencies = FASTAPI_BASE_DEPENDENCIES.to_vec();
    /* if project_info.database_manager == Some(DatabaseManager::SqlAlchemy) {
        dependencies.push("sqlalchemy");
        dependencies.push("alembic");
    } */
    let mut args = vec!["add"];
    args.extend(dependencies);
    let output = std::process::Command::new("uv")
        .args(args)
        .current_dir(project_info.base_dir())
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("Failed to install FastAPI dependencies: {stderr}");
    }

    let dev_dependencies = FASTAPI_BASE_DEV_DEPENDENCIES.to_vec();
    let mut dev_args = vec!["add", "--group=dev"];
    dev_args.extend(dev_dependencies);
    let output = std::process::Command::new("uv")
        .args(dev_args)
        .current_dir(project_info.base_dir())
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("Failed to install FastAPI dependencies: {stderr}");
    }

    Ok(())
}

fn poetry_fastapi_dependency_installer(project_info: &ProjectInfo) -> Result<()> {
    let dependencies = FASTAPI_BASE_DEPENDENCIES.to_vec();
    /* if project_info.database_manager == Some(DatabaseManager::SqlAlchemy) {
        dependencies.push("sqlalchemy");
        dependencies.push("alembic");
    } */
    let mut args = vec!["add"];
    args.extend(dependencies);
    let output = std::process::Command::new("poetry")
        .args(args)
        .current_dir(project_info.base_dir())
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("Failed to install FastAPI dependencies: {stderr}");
    }

    let dev_dependencies = FASTAPI_BASE_DEV_DEPENDENCIES.to_vec();
    let mut dev_args = vec!["add", "--group=dev"];
    dev_args.extend(dev_dependencies);
    let output = std::process::Command::new("poetry")
        .args(dev_args)
        .current_dir(project_info.base_dir())
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("Failed to install FastAPI dependencies: {stderr}");
    }

    Ok(())
}

fn setuptools_fastapi_dependency_installer(project_info: &ProjectInfo) -> Result<()> {
    let venv_path = project_info.base_dir().join(".venv");
    if !venv_path.exists() {
        let venv_output = std::process::Command::new("python")
            .args(["-m", "venv", ".venv"])
            .current_dir(project_info.base_dir())
            .output()?;

        if !venv_output.status.success() {
            let stderr = String::from_utf8_lossy(&venv_output.stderr);
            bail!("Failed to create virtual environment: {stderr}");
        }
    }

    let dependencies = FASTAPI_BASE_DEPENDENCIES.to_vec();
    let dev_dependencies = FASTAPI_BASE_DEV_DEPENDENCIES.to_vec();
    /* if project_info.database_manager == Some(DatabaseManager::SqlAlchemy) {
        dependencies.push("sqlalchemy");
        dependencies.push("alembic");
    } */
    let mut args = vec!["-m", "pip", "install"];
    args.extend(dependencies);
    args.extend(dev_dependencies);
    let output = std::process::Command::new(".venv/bin/python")
        .args(args)
        .current_dir(project_info.base_dir())
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("Failed to install FastAPI dependencies: {stderr}");
    }

    let freeze = std::process::Command::new(".venv/bin/python")
        .args(["-m", "pip", "freeze"])
        .current_dir(project_info.base_dir())
        .output()?;

    if !freeze.status.success() {
        let stderr = String::from_utf8_lossy(&freeze.stderr);
        bail!("Failed to get pip freeze output: {stderr}");
    }

    let requirements_path = project_info.base_dir().join("requirements.txt");
    std::fs::write(requirements_path, freeze.stdout)?;

    Ok(())
}

fn maturin_fastapi_dependency_installer(project_info: &ProjectInfo) -> Result<()> {
    use crate::project_info::Pyo3PythonManager;

    if let Some(pyo3_python_manager) = &project_info.pyo3_python_manager {
        match pyo3_python_manager {
            Pyo3PythonManager::Uv => uv_fastapi_dependency_installer(project_info),
            Pyo3PythonManager::Setuptools => setuptools_fastapi_dependency_installer(project_info),
        }
    } else {
        bail!("No Python project manager provided for PyO3 project");
    }
}