diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..dd252e4 --- /dev/null +++ b/.env-example @@ -0,0 +1,5 @@ +postgres_user=exampleUser +postgres_password=examplePasswd +postgres_db=exampleDB +db_port=5432 +api_port=8000 diff --git a/README.md b/README.md index d81ba64..9469dc4 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,14 @@ project/ ### Setup -Create a .env file in the root directory: +Create a .env file, by copying .env-example, in the root directory and adjustign the vaulues as needed: ```text -postgres_user=exampleUser -postgres_password=examplePasswd -postgres_db=exampleDB -db_port=5432 -api_port=8000 +POSTGRES_USER=exampleUser +POSTGRES_PASSWORD=examplePasswd +POSTGRES_DB=exampleDB +API_PORT=8000 +DEBUG=true ``` Start the stack: @@ -71,4 +71,4 @@ Do not run in production. The database schema is managed via Create.sql — no ORM migrations The .env file is excluded from version control via .gitignore -``` \ No newline at end of file +``` diff --git a/app/config.py b/app/config.py index 6937ed1..87f7bed 100644 --- a/app/config.py +++ b/app/config.py @@ -1,6 +1,23 @@ from pydantic_settings import BaseSettings +from pydantic import computed_field class Settings(BaseSettings): - DATABASE_URL: str = "${DATABASE_URL}" + POSTGRES_USER: str + POSTGRES_PASSWORD: str + POSTGRES_DB: str + API_PORT: int = 8000 + DEBUG: bool = False + + @computed_field + @property + def DATABASE_URL(self) -> str: + return ( + f"postgresql+asyncpg://" + f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}" + f"@db/{self.POSTGRES_DB}" + ) + + class Config: + env_file = ".env" settings = Settings() diff --git a/app/database.py b/app/database.py index b24cc41..0634bdd 100644 --- a/app/database.py +++ b/app/database.py @@ -1,11 +1,22 @@ +from typing import AsyncGenerator from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker -from sqlalchemy import text from app.config import settings -engine = create_async_engine(settings.DATABASE_URL, echo=True) +engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG) AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) -async def get_db(): +async def connect(): + pass # engine initializes at module level + +async def disconnect(): + await engine.dispose() + +async def get_db() -> AsyncGenerator[AsyncSession, None]: async with AsyncSessionLocal() as session: - yield session + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise diff --git a/app/main.py b/app/main.py index 7786b8b..1c70405 100644 --- a/app/main.py +++ b/app/main.py @@ -1,14 +1,15 @@ from contextlib import asynccontextmanager from fastapi import FastAPI -from app.database import engine +from app.database import connect, disconnect from app.routers import authors, books @asynccontextmanager async def lifespan(app: FastAPI): - yield # tables already created by Create.sql - await engine.dispose() + await connect() + yield + await disconnect() app = FastAPI(title="Database API", lifespan=lifespan) -app.include_router(authors.router) -app.include_router(books.router) +for router in [authors.router, books.router]: + app.include_router(router) diff --git a/compose.yaml b/compose.yaml index fe133d2..c6fb9b5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,14 +1,12 @@ services: api: restart: unless-stopped - container_name: api_${postgres_db} + container_name: api_${POSTGRES_DB} build: . env_file: - .env ports: - - "${api_port}:8000" - environment: - DATABASE_URL: postgresql+asyncpg://${postgres_user}:${postgres_password}@db:${db_port}/${postgres_db} + - "${API_PORT}:8000" depends_on: db: condition: service_healthy @@ -17,20 +15,20 @@ services: db: image: postgres:18-alpine - container_name: postgres_${postgres_db} + container_name: postgres_${POSTGRES_DB} restart: unless-stopped shm_size: 128mb - ports: - - ${db_port}:5432 + #ports: #Not needed as the API container will connect to the DB container using the internal Docker network. Uncomment if you want to access the database from outside the Docker network. + # - 5432:5432 volumes: - ./postgresData:/var/lib/postgresql/data - ./Create.sql:/docker-entrypoint-initdb.d/Create.sql environment: - POSTGRES_USER: ${postgres_user} - POSTGRES_PASSWORD: ${postgres_password} - POSTGRES_DB: ${postgres_db} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${postgres_user} -d ${postgres_db}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5