fastapi-template.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. """
  2. Production-ready FastAPI application template.
  3. Usage:
  4. uvicorn main:app --reload # Development
  5. uvicorn main:app --host 0.0.0.0 --port 8000 # Production
  6. """
  7. from contextlib import asynccontextmanager
  8. from typing import Annotated
  9. from fastapi import Depends, FastAPI, HTTPException, Request
  10. from fastapi.middleware.cors import CORSMiddleware
  11. from fastapi.responses import JSONResponse
  12. from pydantic import BaseModel, Field
  13. from pydantic_settings import BaseSettings
  14. # =============================================================================
  15. # Configuration
  16. # =============================================================================
  17. class Settings(BaseSettings):
  18. """Application settings from environment variables."""
  19. app_name: str = "My API"
  20. debug: bool = False
  21. database_url: str = "postgresql+asyncpg://user:pass@localhost/db"
  22. redis_url: str = "redis://localhost:6379/0"
  23. api_key: str = ""
  24. model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
  25. # Cache settings
  26. from functools import lru_cache
  27. @lru_cache
  28. def get_settings() -> Settings:
  29. return Settings()
  30. # =============================================================================
  31. # Lifespan Management
  32. # =============================================================================
  33. @asynccontextmanager
  34. async def lifespan(app: FastAPI):
  35. """Application startup and shutdown."""
  36. settings = get_settings()
  37. # Startup
  38. # app.state.db = await create_db_pool(settings.database_url)
  39. # app.state.redis = await create_redis_client(settings.redis_url)
  40. print(f"Starting {settings.app_name}...")
  41. yield
  42. # Shutdown
  43. # await app.state.db.close()
  44. # await app.state.redis.close()
  45. print("Shutting down...")
  46. # =============================================================================
  47. # Application
  48. # =============================================================================
  49. app = FastAPI(
  50. title="My API",
  51. version="1.0.0",
  52. lifespan=lifespan,
  53. )
  54. # CORS
  55. app.add_middleware(
  56. CORSMiddleware,
  57. allow_origins=["*"] if get_settings().debug else ["https://myapp.com"],
  58. allow_credentials=True,
  59. allow_methods=["*"],
  60. allow_headers=["*"],
  61. )
  62. # =============================================================================
  63. # Error Handling
  64. # =============================================================================
  65. @app.exception_handler(Exception)
  66. async def global_exception_handler(request: Request, exc: Exception):
  67. """Handle unhandled exceptions."""
  68. return JSONResponse(
  69. status_code=500,
  70. content={"detail": "Internal server error"},
  71. )
  72. # =============================================================================
  73. # Dependencies
  74. # =============================================================================
  75. async def get_db():
  76. """Database session dependency."""
  77. # async with async_session() as session:
  78. # yield session
  79. yield None # Placeholder
  80. DB = Annotated[None, Depends(get_db)] # Replace None with actual type
  81. # =============================================================================
  82. # Models
  83. # =============================================================================
  84. class HealthResponse(BaseModel):
  85. status: str
  86. version: str
  87. class ItemCreate(BaseModel):
  88. name: str = Field(..., min_length=1, max_length=100)
  89. description: str | None = None
  90. class ItemResponse(BaseModel):
  91. id: int
  92. name: str
  93. description: str | None
  94. model_config = {"from_attributes": True}
  95. # =============================================================================
  96. # Routes
  97. # =============================================================================
  98. @app.get("/health", response_model=HealthResponse)
  99. async def health_check():
  100. """Health check endpoint."""
  101. return HealthResponse(status="healthy", version="1.0.0")
  102. @app.get("/items", response_model=list[ItemResponse])
  103. async def list_items(
  104. db: DB,
  105. skip: int = 0,
  106. limit: int = 10,
  107. ):
  108. """List all items."""
  109. # items = await db.execute(select(Item).offset(skip).limit(limit))
  110. # return items.scalars().all()
  111. return []
  112. @app.post("/items", response_model=ItemResponse, status_code=201)
  113. async def create_item(item: ItemCreate, db: DB):
  114. """Create a new item."""
  115. # db_item = Item(**item.model_dump())
  116. # db.add(db_item)
  117. # await db.commit()
  118. # await db.refresh(db_item)
  119. # return db_item
  120. return ItemResponse(id=1, name=item.name, description=item.description)
  121. @app.get("/items/{item_id}", response_model=ItemResponse)
  122. async def get_item(item_id: int, db: DB):
  123. """Get a single item."""
  124. # item = await db.get(Item, item_id)
  125. # if not item:
  126. # raise HTTPException(status_code=404, detail="Item not found")
  127. # return item
  128. raise HTTPException(status_code=404, detail="Item not found")
  129. if __name__ == "__main__":
  130. import uvicorn
  131. uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)