mirror of
https://github.com/RichieCahill/dotfiles.git
synced 2026-04-17 04:58:19 -04:00
114 lines
3.3 KiB
Python
114 lines
3.3 KiB
Python
"""FastAPI interface for Contact database."""
|
|
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
from os import environ
|
|
from pathlib import Path
|
|
from typing import Annotated
|
|
|
|
import typer
|
|
import uvicorn
|
|
from fastapi import FastAPI
|
|
|
|
from python.api.routers import contact_router, create_frontend_router
|
|
from python.orm.base import get_postgres_engine
|
|
|
|
|
|
def create_app(frontend_dir: Path | None = None) -> FastAPI:
|
|
"""Create and configure the FastAPI application."""
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
"""Manage application lifespan."""
|
|
app.state.engine = get_postgres_engine()
|
|
yield
|
|
app.state.engine.dispose()
|
|
|
|
app = FastAPI(title="Contact Database API", lifespan=lifespan)
|
|
|
|
app.include_router(contact_router)
|
|
|
|
if frontend_dir:
|
|
print(f"Serving frontend from {frontend_dir}")
|
|
frontend_router = create_frontend_router(frontend_dir)
|
|
app.include_router(frontend_router)
|
|
|
|
return app
|
|
|
|
|
|
cli = typer.Typer()
|
|
|
|
|
|
def build_frontend(source_dir: Path | None, cache_dir: Path | None = None) -> Path | None:
|
|
"""Run npm build and copy output to a temp directory.
|
|
|
|
Works even if source_dir is read-only by copying to a temp directory first.
|
|
|
|
Args:
|
|
source_dir: Frontend source directory.
|
|
cache_dir: Optional npm cache directory for faster repeated builds.
|
|
|
|
Returns:
|
|
Path to frontend build directory, or None if no source_dir provided.
|
|
"""
|
|
if not source_dir:
|
|
return None
|
|
|
|
if not source_dir.exists():
|
|
error = f"Error: Frontend directory {source_dir} does not exist"
|
|
raise FileExistsError(error)
|
|
|
|
print(f"Building frontend from {source_dir}...")
|
|
|
|
# Copy source to a writable temp directory
|
|
build_dir = Path(tempfile.mkdtemp(prefix="contact_frontend_build_"))
|
|
shutil.copytree(source_dir, build_dir, dirs_exist_ok=True)
|
|
|
|
env = dict(environ)
|
|
if cache_dir:
|
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
env["npm_config_cache"] = str(cache_dir)
|
|
|
|
subprocess.run(["npm", "install"], cwd=build_dir, env=env, check=True)
|
|
subprocess.run(["npm", "run", "build"], cwd=build_dir, env=env, check=True)
|
|
|
|
dist_dir = build_dir / "dist"
|
|
if not dist_dir.exists():
|
|
error = f"Build output not found at {dist_dir}"
|
|
raise FileNotFoundError(error)
|
|
|
|
output_dir = Path(tempfile.mkdtemp(prefix="contact_frontend_"))
|
|
shutil.copytree(dist_dir, output_dir, dirs_exist_ok=True)
|
|
print(f"Frontend built and copied to {output_dir}")
|
|
|
|
shutil.rmtree(build_dir)
|
|
|
|
return output_dir
|
|
|
|
|
|
@cli.command()
|
|
def serve(
|
|
frontend_dir: Annotated[
|
|
Path | None,
|
|
typer.Option(
|
|
"--frontend-dir",
|
|
"-f",
|
|
help="Frontend source directory. If provided, runs npm build and serves from temp dir.",
|
|
),
|
|
] = None,
|
|
host: Annotated[str, typer.Option("--host", "-h", help="Host to bind to")] = "0.0.0.0",
|
|
port: Annotated[int, typer.Option("--port", "-p", help="Port to bind to")] = 8000,
|
|
) -> None:
|
|
"""Start the Contact API server."""
|
|
serve_dir = build_frontend(frontend_dir)
|
|
|
|
app = create_app(frontend_dir=serve_dir)
|
|
uvicorn.run(app, host=host, port=port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|