# Example 1: Todo App (single entity) ## models.py ```python """Tietokantamallit — SQLAlchemy 2.0, Mapped-tyypitys, SQLite.""" from datetime import date from sqlalchemy import String, Text, Date, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker DATABASE_URL = "sqlite:///./app.db" engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) class Base(DeclarativeBase): pass class Todo(Base): __tablename__ = "todos" id: Mapped[int] = mapped_column(primary_key=True, index=True) title: Mapped[str] = mapped_column(String(255)) description: Mapped[str | None] = mapped_column(Text, default=None) due_date: Mapped[date | None] = mapped_column(Date, default=None) priority: Mapped[int] = mapped_column(default=1) status: Mapped[str] = mapped_column(String(20), default="pending") Base.metadata.create_all(bind=engine) ``` ## schemas.py ```python from datetime import date from pydantic import BaseModel, ConfigDict class TodoCreate(BaseModel): title: str description: str | None = None due_date: date | None = None priority: int = 1 status: str = "pending" class TodoResponse(TodoCreate): id: int model_config = ConfigDict(from_attributes=True) ``` ## test_main.py — exactly 6 tests per entity ```python from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from main import app, get_db from models import Base test_engine = create_engine("sqlite:///./test.db", connect_args={"check_same_thread": False}) TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) Base.metadata.create_all(bind=test_engine) def override_get_db(): db = TestSession() try: yield db finally: db.close() app.dependency_overrides[get_db] = override_get_db client = TestClient(app) def test_create_todo(): response = client.post("/todos/", json={"title": "Osta maitoa", "priority": 2}) assert response.status_code == 201 assert "id" in response.json() def test_list_todos(): client.post("/todos/", json={"title": "Listattava"}) response = client.get("/todos/") assert response.status_code == 200 assert len(response.json()) >= 1 def test_get_todo_by_id(): created = client.post("/todos/", json={"title": "Haettava"}).json() response = client.get(f"/todos/{created['id']}") assert response.status_code == 200 def test_get_todo_not_found(): response = client.get("/todos/99999") assert response.status_code == 404 def test_update_todo(): created = client.post("/todos/", json={"title": "Vanha"}).json() response = client.put(f"/todos/{created['id']}", json={"title": "Uusi"}) assert response.status_code == 200 def test_delete_todo(): created = client.post("/todos/", json={"title": "Poistettava"}).json() response = client.delete(f"/todos/{created['id']}") assert response.status_code == 204 ``` # Example 2: Blog (two entities with ForeignKey) NOTE: ForeignKey is imported from sqlalchemy, NOT from sqlalchemy.orm! ## models.py ```python from datetime import datetime from sqlalchemy import String, Text, DateTime, ForeignKey, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, sessionmaker DATABASE_URL = "sqlite:///./app.db" engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) class Base(DeclarativeBase): pass class Author(Base): __tablename__ = "authors" id: Mapped[int] = mapped_column(primary_key=True, index=True) name: Mapped[str] = mapped_column(String(255)) email: Mapped[str] = mapped_column(String(255), unique=True) bio: Mapped[str | None] = mapped_column(Text, default=None) posts: Mapped[list["Post"]] = relationship(back_populates="author") class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(primary_key=True, index=True) title: Mapped[str] = mapped_column(String(255)) content: Mapped[str] = mapped_column(Text) author_id: Mapped[int] = mapped_column(ForeignKey("authors.id")) published_at: Mapped[datetime | None] = mapped_column(DateTime, default=None) status: Mapped[str] = mapped_column(String(20), default="draft") author: Mapped["Author"] = relationship(back_populates="posts") Base.metadata.create_all(bind=engine) ``` ## schemas.py ```python from datetime import datetime from pydantic import BaseModel, ConfigDict class AuthorCreate(BaseModel): name: str email: str bio: str | None = None class AuthorResponse(AuthorCreate): id: int model_config = ConfigDict(from_attributes=True) class PostCreate(BaseModel): title: str content: str author_id: int published_at: datetime | None = None status: str = "draft" class PostResponse(PostCreate): id: int model_config = ConfigDict(from_attributes=True) ``` ## test_main.py — 6 tests per entity, create parent FIRST for child tests ```python client = TestClient(app) # same setup as above def _create_author(name="Kirjailija", email=None): if email is None: email = f"{name.lower().replace(' ', '.')}@example.com" return client.post("/authors/", json={"name": name, "email": email}).json() def test_create_author(): response = client.post("/authors/", json={"name": "Aleksis Kivi", "email": "aleksis@example.com"}) assert response.status_code == 201 def test_list_authors(): _create_author("Minna Canth", "minna@example.com") response = client.get("/authors/") assert response.status_code == 200 assert len(response.json()) >= 1 # ... (same pattern: get_by_id, not_found, update, delete) def test_create_post(): author = _create_author("Tove Jansson", "tove@example.com") response = client.post("/posts/", json={"title": "Artikkeli", "content": "Sisältö", "author_id": author["id"]}) assert response.status_code == 201 def test_update_post(): author = _create_author("Joel Lehtonen", "joel@example.com") created = client.post("/posts/", json={"title": "Vanha", "content": "Teksti", "author_id": author["id"]}).json() response = client.put(f"/posts/{created['id']}", json={"title": "Uusi", "content": "Muokattu", "author_id": author["id"]}) assert response.status_code == 200 def test_delete_post(): author = _create_author("Aino Kallas", "aino@example.com") created = client.post("/posts/", json={"title": "Poistettava", "content": "Poistetaan", "author_id": author["id"]}).json() response = client.delete(f"/posts/{created['id']}") assert response.status_code == 204 ```