From cd2f13caf2a9b55d4198eb651e0cc37093e85240 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Sun, 28 Dec 2025 12:00:26 -0600 Subject: [PATCH] Cleanup boilerplate --- app/core/__init__.py | 0 app/core/config.py | 45 ++++++++++++++++-------------------- app/core/database.py | 48 +++++++++++++++++++++++++++++++++++++++ app/resources/__init__.py | 0 app/resources/health.py | 23 +++++++++++++++++++ main.py | 29 ++++++++--------------- 6 files changed, 99 insertions(+), 46 deletions(-) create mode 100644 app/core/__init__.py create mode 100644 app/core/database.py create mode 100644 app/resources/__init__.py create mode 100644 app/resources/health.py diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/config.py b/app/core/config.py index 4531a2a..3d3e4a5 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,39 +1,32 @@ -""" -Core configuration settings for the Loan Operations API. -""" +from typing import List from pydantic import Field from pydantic_settings import BaseSettings class Settings(BaseSettings): - """Application settings.""" + """Application settings following 12-factor app principles.""" - # API Configuration - api_title: str = "Loan Operations API" - api_description: str = "A comprehensive API for managing loan operations" - api_version: str = "1.0.0" + # Server settings + host: str = Field(default="0.0.0.0", alias="HOST") + port: int = Field(default=8000, alias="PORT") + debug: bool = Field(default=False, alias="DEBUG") + log_level: str = Field(default="INFO", alias="LOG_LEVEL") + + # Database settings + oracle_user: str = Field(alias="ORACLE_USER") + oracle_password: str = Field(alias="ORACLE_PASSWORD") + oracle_dsn: str = Field(alias="ORACLE_DSN") - # Server Configuration - host: str = Field(default="0.0.0.0", description="Server host") - port: int = Field(default=8000, description="Server port") - debug: bool = Field(default=False, description="Debug mode") + # Sentry settings + sentry_dsn: str | None = Field(default=None, alias="SENTRY_DSN") - # API Configuration - api_v1_prefix: str = "/api/v1" - - # CORS Configuration - allowed_origins: list[str] = Field( - default=["*"], - description="Allowed CORS origins" - ) - - # Logging Configuration - log_level: str = Field(default="INFO", description="Logging level") + # Datadog settings + dd_service: str = Field(default="loapi", alias="DD_SERVICE") + dd_env: str = Field(default="development", alias="DD_ENV") + dd_version: str = Field(default="1.0.0", alias="DD_VERSION") class Config: env_file = ".env" case_sensitive = False - -# Global settings instance -settings = Settings() +settings = Settings() # type:ignore[call-arg] diff --git a/app/core/database.py b/app/core/database.py new file mode 100644 index 0000000..2c67567 --- /dev/null +++ b/app/core/database.py @@ -0,0 +1,48 @@ +"""Database configuration and connection management.""" +from typing import AsyncGenerator +from sqlalchemy import create_engine, MetaData +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker +from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session +from app.core.config import settings + + +class Base(DeclarativeBase): + """Base class for SQLAlchemy models.""" + metadata = MetaData() + + +# Database URL construction +DATABASE_URL = f"oracle+oracledb://{settings.oracle_user}:{settings.oracle_password}@{settings.oracle_dsn}" + +# Synchronous engine for Oracle +engine = create_engine( + DATABASE_URL, + echo=settings.debug, + pool_pre_ping=True, + pool_recycle=3600, +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +def get_db() -> AsyncGenerator[Session, None]: + """Dependency to get database session.""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +class DatabaseService: + """Database service for health checks and utilities.""" + + @staticmethod + def health_check() -> bool: + """Check database connectivity.""" + try: + with SessionLocal() as db: + db.execute("SELECT 1 FROM DUAL") # type:ignore[call-overload] + return True + except Exception: + return False diff --git a/app/resources/__init__.py b/app/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/resources/health.py b/app/resources/health.py new file mode 100644 index 0000000..7d6cd4e --- /dev/null +++ b/app/resources/health.py @@ -0,0 +1,23 @@ +"""Health check endpoints.""" +from fastapi import APIRouter +from app.core.database import DatabaseService + +router = APIRouter() + + +@router.get("/health-check") +async def health_check() -> dict[str, str]: + """Comprehensive health check endpoint.""" + + # Check database connectivity + db_healthy = DatabaseService.health_check() + + status = "healthy" if db_healthy else "unhealthy" + + result = { + "status": status, + "service": "loapi", + "database": "connected" if db_healthy else "disconnected" + } + + return result diff --git a/main.py b/main.py index 9d34a2d..850ddd7 100644 --- a/main.py +++ b/main.py @@ -6,40 +6,28 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.core.config import settings +from app.resources import health def create_app() -> FastAPI: """Create and configure the FastAPI application.""" app = FastAPI( - title=settings.api_title, - description=settings.api_description, - version=settings.api_version, + title="Loan Operations API", + description="SBA Loan Operations API", + version="1.0.0", docs_url="/docs", redoc_url="/redoc", ) - - # Add CORS middleware - app.add_middleware( - CORSMiddleware, - allow_origins=settings.allowed_origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - # Health check endpoint - @app.get("/health-check") - async def health_check(): - """Health check endpoint.""" - return {"status": "healthy", "service": "loapi"} + + # Include all endpoint routers + app.include_router(health.router, tags=["health"]) return app -def main(): +def main() -> None: """Run the application.""" - app = create_app() uvicorn.run( app, @@ -49,6 +37,7 @@ def main(): reload=settings.debug, ) +app = create_app() if __name__ == "__main__": main()