| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- """
- Production-ready FastAPI application template.
- Usage:
- uvicorn main:app --reload # Development
- uvicorn main:app --host 0.0.0.0 --port 8000 # Production
- """
- from contextlib import asynccontextmanager
- from typing import Annotated
- from fastapi import Depends, FastAPI, HTTPException, Request
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.responses import JSONResponse
- from pydantic import BaseModel, Field
- from pydantic_settings import BaseSettings
- # =============================================================================
- # Configuration
- # =============================================================================
- class Settings(BaseSettings):
- """Application settings from environment variables."""
- app_name: str = "My API"
- debug: bool = False
- database_url: str = "postgresql+asyncpg://user:pass@localhost/db"
- redis_url: str = "redis://localhost:6379/0"
- api_key: str = ""
- model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
- # Cache settings
- from functools import lru_cache
- @lru_cache
- def get_settings() -> Settings:
- return Settings()
- # =============================================================================
- # Lifespan Management
- # =============================================================================
- @asynccontextmanager
- async def lifespan(app: FastAPI):
- """Application startup and shutdown."""
- settings = get_settings()
- # Startup
- # app.state.db = await create_db_pool(settings.database_url)
- # app.state.redis = await create_redis_client(settings.redis_url)
- print(f"Starting {settings.app_name}...")
- yield
- # Shutdown
- # await app.state.db.close()
- # await app.state.redis.close()
- print("Shutting down...")
- # =============================================================================
- # Application
- # =============================================================================
- app = FastAPI(
- title="My API",
- version="1.0.0",
- lifespan=lifespan,
- )
- # CORS
- app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"] if get_settings().debug else ["https://myapp.com"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- # =============================================================================
- # Error Handling
- # =============================================================================
- @app.exception_handler(Exception)
- async def global_exception_handler(request: Request, exc: Exception):
- """Handle unhandled exceptions."""
- return JSONResponse(
- status_code=500,
- content={"detail": "Internal server error"},
- )
- # =============================================================================
- # Dependencies
- # =============================================================================
- async def get_db():
- """Database session dependency."""
- # async with async_session() as session:
- # yield session
- yield None # Placeholder
- DB = Annotated[None, Depends(get_db)] # Replace None with actual type
- # =============================================================================
- # Models
- # =============================================================================
- class HealthResponse(BaseModel):
- status: str
- version: str
- class ItemCreate(BaseModel):
- name: str = Field(..., min_length=1, max_length=100)
- description: str | None = None
- class ItemResponse(BaseModel):
- id: int
- name: str
- description: str | None
- model_config = {"from_attributes": True}
- # =============================================================================
- # Routes
- # =============================================================================
- @app.get("/health", response_model=HealthResponse)
- async def health_check():
- """Health check endpoint."""
- return HealthResponse(status="healthy", version="1.0.0")
- @app.get("/items", response_model=list[ItemResponse])
- async def list_items(
- db: DB,
- skip: int = 0,
- limit: int = 10,
- ):
- """List all items."""
- # items = await db.execute(select(Item).offset(skip).limit(limit))
- # return items.scalars().all()
- return []
- @app.post("/items", response_model=ItemResponse, status_code=201)
- async def create_item(item: ItemCreate, db: DB):
- """Create a new item."""
- # db_item = Item(**item.model_dump())
- # db.add(db_item)
- # await db.commit()
- # await db.refresh(db_item)
- # return db_item
- return ItemResponse(id=1, name=item.name, description=item.description)
- @app.get("/items/{item_id}", response_model=ItemResponse)
- async def get_item(item_id: int, db: DB):
- """Get a single item."""
- # item = await db.get(Item, item_id)
- # if not item:
- # raise HTTPException(status_code=404, detail="Item not found")
- # return item
- raise HTTPException(status_code=404, detail="Item not found")
- if __name__ == "__main__":
- import uvicorn
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|