Pipelinen parannuksia building blockeilla

This commit is contained in:
Jaakko Vanhala
2026-04-12 18:48:14 +03:00
parent c1a5f8aff5
commit b2ee8b9031
175 changed files with 13311 additions and 237 deletions

View File

@@ -0,0 +1,64 @@
# 0 — Client (client) — requirements
**Malli:** `qwen-coder`
## System Prompt
```
You are a product owner who turns vague ideas into clear, actionable software requirements.
GIVEN a short project description from the user, produce a structured brief:
1. PROJECT NAME: a short, descriptive name
2. GOAL: one sentence explaining what the software does and who it's for
3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes)
4. DATA MODEL: list the main entities and their key fields (include field types)
5. API ENDPOINTS: list the REST endpoints (method + path + purpose)
6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed")
RULES:
- Be specific: "User can filter todos by status" not "todo management"
- Use plain English, no code
- Maximum 400 words total
```
## Syöte
```
Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
```
## Tulos
```
1. **PROJECT NAME:** Todo-sovellus
2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently.
3. **CORE FEATURES:**
- Create new todos with title, description, due date.
- Read/View all todos or a single todo by ID.
- Update existing todos (title, description, status).
- Delete completed or unnecessary todos.
- Filter todos by status (e.g., pending, completed).
4. **DATA MODEL:**
- Todo
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: 'pending', 'completed')
5. **API ENDPOINTS:**
- GET /todos: Retrieve all todos.
- GET /todos/{id}: Retrieve a single todo by ID.
- POST /todos: Create a new todo.
- PUT /todos/{id}: Update an existing todo.
- DELETE /todos/{id}: Delete a todo.
6. **CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication required for accessing endpoints.
- Implement unit tests and integration tests to ensure functionality.
```

View File

@@ -0,0 +1,122 @@
# 1 — Data Engineer (data) — models.py
**Malli:** `qwen-coder`
## System Prompt
```
You are a database architect specializing in SQLAlchemy and relational databases.
YOUR RESPONSIBILITIES:
1. Design normalized database schemas with proper column types and constraints
2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships
3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py)
4. Use String(length) not bare String for SQLite compatibility
5. Add nullable=False for required fields, unique=True where appropriate
6. Use Column(Integer, primary_key=True, index=True) for IDs
ALWAYS INCLUDE:
- from sqlalchemy import create_engine, Column, Integer, String
- from sqlalchemy.ext.declarative import declarative_base
- from sqlalchemy.orm import sessionmaker
- DATABASE_URL, engine, SessionLocal, Base
```
## Syöte
```
You are a database architect specializing in SQLAlchemy and relational databases.
YOUR RESPONSIBILITIES:
1. Design normalized database schemas with proper column types and constraints
2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships
3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py)
4. Use String(length) not bare String for SQLite compatibility
5. Add nullable=False for required fields, unique=True where appropriate
6. Use Column(Integer, primary_key=True, index=True) for IDs
ALWAYS INCLUDE:
- from sqlalchemy import create_engine, Column, Integer, String
- from sqlalchemy.ext.declarative import declarative_base
- from sqlalchemy.orm import sessionmaker
- DATABASE_URL, engine, SessionLocal, Base
EXAMPLE of models.py (for a different project, adapt to this one):
```
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import 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)
Base = declarative_base()
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
description = Column(String(500))
```
PROJECT REQUIREMENTS (from product owner):
1. **PROJECT NAME:** Todo-sovellus
2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently.
3. **CORE FEATURES:**
- Create new todos with title, description, due date.
- Read/View all todos or a single todo by ID.
- Update existing todos (title, description, status).
- Delete completed or unnecessary todos.
- Filter todos by status (e.g., pending, completed).
4. **DATA MODEL:**
- Todo
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: 'pending', 'completed')
5. **API ENDPOINTS:**
- GET /todos: Retrieve all todos.
- GET /todos/{id}: Retrieve a single todo by ID.
- POST /todos: Create a new todo.
- PUT /todos/{id}: Update an existing todo.
- DELETE /todos/{id}: Delete a todo.
6. **CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication required for accessing endpoints.
- Implement unit tests and integration tests to ensure functionality.
NOW write "models.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
Define the SQLAlchemy model based on the project description. Always include:
- engine with check_same_thread=False for SQLite
- SessionLocal with autocommit=False
- Base = declarative_base()
- Model class with __tablename__, primary key, and fields
Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations.
```
## Tulos
```
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
```

View File

@@ -0,0 +1,142 @@
# 2 — Coder (coder) — schemas.py
**Malli:** `qwen-coder`
## System Prompt
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
```
## Syöte
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
EXAMPLE of schemas.py (for a different project, adapt to this one):
```
from pydantic import BaseModel
class ItemCreate(BaseModel):
name: str
description: str | None = None
class ItemResponse(ItemCreate):
id: int
class Config:
from_attributes = True
```
Already written files in THIS project:
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
PROJECT REQUIREMENTS (from product owner):
1. **PROJECT NAME:** Todo-sovellus
2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently.
3. **CORE FEATURES:**
- Create new todos with title, description, due date.
- Read/View all todos or a single todo by ID.
- Update existing todos (title, description, status).
- Delete completed or unnecessary todos.
- Filter todos by status (e.g., pending, completed).
4. **DATA MODEL:**
- Todo
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: 'pending', 'completed')
5. **API ENDPOINTS:**
- GET /todos: Retrieve all todos.
- GET /todos/{id}: Retrieve a single todo by ID.
- POST /todos: Create a new todo.
- PUT /todos/{id}: Update an existing todo.
- DELETE /todos/{id}: Delete a todo.
6. **CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication required for accessing endpoints.
- Implement unit tests and integration tests to ensure functionality.
NOW write "schemas.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
Create Pydantic schemas that match the SQLAlchemy model:
- Create schema: fields without id (user provides these)
- Response schema: inherits from Create, adds id
- Add class Config with from_attributes = True (required for SQLAlchemy ORM)
Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations.
```
## Tulos
```
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
```

View File

@@ -0,0 +1,244 @@
# 3 — Coder (coder) — main.py
**Malli:** `qwen-coder`
## System Prompt
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
```
## Syöte
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
EXAMPLE of main.py (for a different project, adapt to this one):
```
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Item
from schemas import ItemCreate, ItemResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/items/", response_model=ItemResponse, status_code=201)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
db_item = Item(**item.model_dump())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.get("/items/", response_model=list[ItemResponse])
def list_items(db: Session = Depends(get_db)):
return db.query(Item).all()
@app.get("/items/{item_id}", response_model=ItemResponse)
def get_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Not found")
return item
@app.put("/items/{item_id}", response_model=ItemResponse)
def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)):
db_item = db.query(Item).filter(Item.id == item_id).first()
if not db_item:
raise HTTPException(status_code=404, detail="Not found")
for key, value in item.model_dump().items():
setattr(db_item, key, value)
db.commit()
db.refresh(db_item)
return db_item
@app.delete("/items/{item_id}", status_code=204)
def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(Item).filter(Item.id == item_id).first()
if not db_item:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_item)
db.commit()
```
Already written files in THIS project:
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
--- schemas.py ---
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
PROJECT REQUIREMENTS (from product owner):
1. **PROJECT NAME:** Todo-sovellus
2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently.
3. **CORE FEATURES:**
- Create new todos with title, description, due date.
- Read/View all todos or a single todo by ID.
- Update existing todos (title, description, status).
- Delete completed or unnecessary todos.
- Filter todos by status (e.g., pending, completed).
4. **DATA MODEL:**
- Todo
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: 'pending', 'completed')
5. **API ENDPOINTS:**
- GET /todos: Retrieve all todos.
- GET /todos/{id}: Retrieve a single todo by ID.
- POST /todos: Create a new todo.
- PUT /todos/{id}: Update an existing todo.
- DELETE /todos/{id}: Delete a todo.
6. **CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication required for accessing endpoints.
- Implement unit tests and integration tests to ensure functionality.
NOW write "main.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
Create the FastAPI app with all CRUD endpoints:
- Import from models.py and schemas.py (use exact class names)
- create_all(bind=engine) at module level
- get_db dependency with yield pattern
- POST (201), GET list, GET by id, PUT, DELETE (204)
- Use response_model for type safety
- Use model_dump() not dict() (Pydantic v2)
Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations.
```
## Tulos
```
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Todo
from schemas import TodoCreate, TodoResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos/", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[TodoResponse])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
```

View File

@@ -0,0 +1,212 @@
# 4 — Coder (coder) — pyproject.toml
**Malli:** `qwen-coder`
## System Prompt
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
```
## Syöte
```
You are an expert Python developer. Write complete, production-ready code.
CRITICAL RULES:
1. Include ALL imports at the top of every file
2. Import from other project files: from models import User, SessionLocal
3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User)
4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
NEVER:
- Add explanations or comments like "# Add routes here"
- Leave placeholder code or TODO comments
- Use Flask syntax (app.run) in FastAPI projects
- Forget to import from other project files
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)
EXAMPLE of pyproject.toml (for a different project, adapt to this one):
```
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
]
[project.scripts]
dev = "uvicorn main:app --reload"
```
Already written files in THIS project:
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
--- schemas.py ---
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
--- main.py ---
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Todo
from schemas import TodoCreate, TodoResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos/", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[TodoResponse])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
PROJECT REQUIREMENTS (from product owner):
1. **PROJECT NAME:** Todo-sovellus
2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently.
3. **CORE FEATURES:**
- Create new todos with title, description, due date.
- Read/View all todos or a single todo by ID.
- Update existing todos (title, description, status).
- Delete completed or unnecessary todos.
- Filter todos by status (e.g., pending, completed).
4. **DATA MODEL:**
- Todo
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: 'pending', 'completed')
5. **API ENDPOINTS:**
- GET /todos: Retrieve all todos.
- GET /todos/{id}: Retrieve a single todo by ID.
- POST /todos: Create a new todo.
- PUT /todos/{id}: Update an existing todo.
- DELETE /todos/{id}: Delete a todo.
6. **CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication required for accessing endpoints.
- Implement unit tests and integration tests to ensure functionality.
NOW write "pyproject.toml" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
Use [project] format (PEP 621, compatible with uv). List dependencies under [project.dependencies]. Add [project.scripts] with dev command. Never use requirements.txt or Poetry format. Run with: uv run uvicorn main:app --reload
Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations.
```
## Tulos
```
[project]
name = "todo-sovellus"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
]
[project.scripts]
dev = "uvicorn main:app --reload"
```

View File

@@ -0,0 +1,174 @@
# 5 — QA (qa) — review
**Malli:** `qwen-coder`
## System Prompt
```
You are a QA engineer responsible for code review and automated testing.
CODE REVIEW CHECKLIST:
1. IMPORTS: Every "from X import Y" must match an actual export in file X
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
3. TYPES: All function parameters have type hints, return types specified
4. ERRORS: Every db query that can return None has a 404 check
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
WHEN REVIEWING:
- If all checks pass: respond "LGTM"
- If issues found: list each as "ISSUE: filename.py: description"
- Be specific and actionable, not vague
WHEN WRITING TESTS:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
- ALWAYS: from fastapi.testclient import TestClient
```
## Syöte
```
You are a QA engineer responsible for code review and automated testing.
CODE REVIEW CHECKLIST:
1. IMPORTS: Every "from X import Y" must match an actual export in file X
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
3. TYPES: All function parameters have type hints, return types specified
4. ERRORS: Every db query that can return None has a 404 check
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
WHEN REVIEWING:
- If all checks pass: respond "LGTM"
- If issues found: list each as "ISSUE: filename.py: description"
- Be specific and actionable, not vague
WHEN WRITING TESTS:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
- ALWAYS: from fastapi.testclient import TestClient
Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description".
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
--- schemas.py ---
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
--- main.py ---
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Todo
from schemas import TodoCreate, TodoResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos/", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[TodoResponse])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
--- pyproject.toml ---
[project]
name = "todo-sovellus"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
]
[project.scripts]
dev = "uvicorn main:app --reload"
```
## Tulos
```
LGTM
```

View File

@@ -0,0 +1,291 @@
# 6 — QA (qa) — test_main.py
**Malli:** `qwen-coder`
## System Prompt
```
You are a QA engineer responsible for code review and automated testing.
CODE REVIEW CHECKLIST:
1. IMPORTS: Every "from X import Y" must match an actual export in file X
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
3. TYPES: All function parameters have type hints, return types specified
4. ERRORS: Every db query that can return None has a 404 check
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
WHEN REVIEWING:
- If all checks pass: respond "LGTM"
- If issues found: list each as "ISSUE: filename.py: description"
- Be specific and actionable, not vague
WHEN WRITING TESTS:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
- ALWAYS: from fastapi.testclient import TestClient
```
## Syöte
```
You are a QA engineer responsible for code review and automated testing.
CODE REVIEW CHECKLIST:
1. IMPORTS: Every "from X import Y" must match an actual export in file X
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
3. TYPES: All function parameters have type hints, return types specified
4. ERRORS: Every db query that can return None has a 404 check
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
WHEN REVIEWING:
- If all checks pass: respond "LGTM"
- If issues found: list each as "ISSUE: filename.py: description"
- Be specific and actionable, not vague
WHEN WRITING TESTS:
- pytest as the test framework
- FastAPI TestClient for API endpoint testing
- SQLAlchemy in-memory SQLite for test database isolation
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
- ALWAYS: from fastapi.testclient import TestClient
Write pytest tests for this project:
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
--- schemas.py ---
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
--- main.py ---
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Todo
from schemas import TodoCreate, TodoResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos/", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[TodoResponse])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
--- pyproject.toml ---
[project]
name = "todo-sovellus"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
]
[project.scripts]
dev = "uvicorn main:app --reload"
Write a complete test_main.py file with TestClient.
```
## Tulos
```
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test_todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
app = FastAPI()
@app.post("/todos/", response_model=Todo, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[Todo])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=Todo)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
client = TestClient(app)
def test_create_todo():
response = client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"})
assert response.status_code == 201
data = response.json()
assert "id" in data
assert data["title"] == "Test Todo"
assert data["description"] == "This is a test todo."
assert data["due_date"] == "2023-12-31"
assert data["status"] == "pending"
def test_list_todos():
response = client.get("/todos/")
assert response.status_code == 200
data = response.json()
assert len(data) > 0
def test_get_todo_by_id():
todo_id = create_test_todo().json()["id"]
response = client.get(f"/todos/{todo_id}")
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["title"] == "Test Todo"
def test_get_nonexistent_todo():
response = client.get("/todos/999")
assert response.status_code == 404
def test_update_todo():
todo_id = create_test_todo().json()["id"]
response = client.put(f"/todos/{todo_id}", json={"title": "Updated Todo"})
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["title"] == "Updated Todo"
def test_delete_todo():
todo_id = create_test_todo().json()["id"]
response = client.delete(f"/todos/{todo_id}")
assert response.status_code == 204
def create_test_todo():
return client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023
```

View File

@@ -0,0 +1,93 @@
# 7 — DevOps (tester) — Dockerfile
**Malli:** `qwen-coder`
## System Prompt
```
You are a DevOps engineer specializing in containerization and deployment.
YOUR RESPONSIBILITIES:
1. Write production-ready Dockerfiles
2. Use multi-stage builds when appropriate
3. Follow security best practices (non-root user, minimal base image)
4. Configure health checks and proper signal handling
DOCKERFILE RULES:
- Use python:3.12-slim as base
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
- Copy pyproject.toml first, then uv sync, then copy source
- Expose appropriate ports
- Use uv run for CMD
Write ONLY the requested files, no explanations.
```
## Syöte
```
You are a DevOps engineer specializing in containerization and deployment.
YOUR RESPONSIBILITIES:
1. Write production-ready Dockerfiles
2. Use multi-stage builds when appropriate
3. Follow security best practices (non-root user, minimal base image)
4. Configure health checks and proper signal handling
DOCKERFILE RULES:
- Use python:3.12-slim as base
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
- Copy pyproject.toml first, then uv sync, then copy source
- Expose appropriate ports
- Use uv run for CMD
Write ONLY the requested files, no explanations.
Write a Dockerfile for this Python FastAPI project.
Project files: models.py, schemas.py, main.py, pyproject.toml, test_main.py
Requirements:
- Use python:3.12-slim as base
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
- Copy pyproject.toml first, then uv sync, then copy source
- Expose port 8000
- CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000
Write ONLY the Dockerfile, no explanations.
```
## Tulos
```
# Stage 1: Build
FROM python:3.12-slim as builder
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir poetry && \
poetry config virtualenvs.create false && \
poetry install --no-interaction --no-ansi
COPY models.py schemas.py main.py test_main.py .
# Stage 2: Production
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local/share/python-poetry/cache /root/.local/share/python-poetry/cache
COPY --from=builder /app /app
RUN useradd -m appuser && \
chown -R appuser:appuser /app
USER appuser
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

View File

@@ -0,0 +1,404 @@
# 8 — Observer (observer) — README.md
**Malli:** `qwen-coder`
## System Prompt
```
You are an independent technical observer and risk analyst.
EVALUATE THE PROJECT FOR:
1. ARCHITECTURE: Is the file structure logical? Are responsibilities separated?
2. SECURITY: SQL injection risks? Input validation? Authentication?
3. RELIABILITY: Error handling? Database connection management? Edge cases?
4. MAINTAINABILITY: Consistent naming? Clear code structure? Would a new developer understand this?
OUTPUT FORMAT:
- RISK: [critical/high/medium/low] Description
- List max 3-5 most important findings
- End with overall assessment: "SHIP IT" or "NEEDS WORK: reason"
```
## Syöte
```
You are an independent technical observer and risk analyst.
EVALUATE THE PROJECT FOR:
1. ARCHITECTURE: Is the file structure logical? Are responsibilities separated?
2. SECURITY: SQL injection risks? Input validation? Authentication?
3. RELIABILITY: Error handling? Database connection management? Edge cases?
4. MAINTAINABILITY: Consistent naming? Clear code structure? Would a new developer understand this?
OUTPUT FORMAT:
- RISK: [critical/high/medium/low] Description
- List max 3-5 most important findings
- End with overall assessment: "SHIP IT" or "NEEDS WORK: reason"
Write a project report in clean markdown for: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
FIRST LINE must be exactly one of:
VERDICT: GREEN
VERDICT: ORANGE
VERDICT: RED
Then write this report:
# Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
## Overview
One paragraph describing what this project does.
## Files
| File | Purpose |
|------|---------|
| models.py | ... |
| schemas.py | ... |
| main.py | ... |
| pyproject.toml | ... |
| test_main.py | ... |
| Dockerfile | ... |
## Quick Start
```bash
git clone <repo>
cd project
uv sync
uv run uvicorn main:app --reload
```
## Docker
```bash
docker build -t todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit .
docker run -p 8000:8000 todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit
```
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | /items/ | Create |
| GET | /items/ | List all |
| GET | /items/{id} | Get by ID |
| PUT | /items/{id} | Update |
| DELETE | /items/{id} | Delete |
(Adapt paths and descriptions to match the actual code)
## Architecture
Describe the project structure and design decisions.
## Risk Assessment
| Severity | Issue |
|----------|-------|
| ... | ... |
Project code:
--- models.py ---
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
--- schemas.py ---
from pydantic import BaseModel
class TodoCreate(BaseModel):
title: str
description: str | None = None
due_date: date
status: str = 'pending'
class TodoResponse(TodoCreate):
id: int
class Config:
from_attributes = True
--- main.py ---
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from models import Base, engine, SessionLocal, Todo
from schemas import TodoCreate, TodoResponse
Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos/", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[TodoResponse])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
--- pyproject.toml ---
[project]
name = "todo-sovellus"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
]
[project.scripts]
dev = "uvicorn main:app --reload"
--- test_main.py ---
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, Column, Integer, String, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test_todo.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), nullable=False)
description = Column(String(500))
due_date = Column(Date, nullable=False)
status = Column(Enum('pending', 'completed'), default='pending')
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
app = FastAPI()
@app.post("/todos/", response_model=Todo, status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos/", response_model=list[Todo])
def list_todos(status: str | None = None, db: Session = Depends(get_db)):
if status:
query = db.query(Todo).filter_by(status=status)
else:
query = db.query(Todo)
return query.all()
@app.get("/todos/{todo_id}", response_model=Todo)
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
for key, value in todo.model_dump().items():
setattr(db_todo, key, value)
db.commit()
db.refresh(db_todo)
return db_todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Not found")
db.delete(db_todo)
db.commit()
client = TestClient(app)
def test_create_todo():
response = client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"})
assert response.status_code == 201
data = response.json()
assert "id" in data
assert data["title"] == "Test Todo"
assert data["description"] == "This is a test todo."
assert data["due_date"] == "2023-12-31"
assert data["status"] == "pending"
def test_list_todos():
response = client.get("/todos/")
assert response.status_code == 200
data = response.json()
assert len(data) > 0
def test_get_todo_by_id():
todo_id = create_test_todo().json()["id"]
response = client.get(f"/todos/{todo_id}")
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["title"] == "Test Todo"
def test_get_nonexistent_todo():
response = client.get("/todos/999")
assert response.status_code == 404
def test_update_todo():
todo_id = create_test_todo().json()["id"]
response = client.put(f"/todos/{todo_id}", json={"title": "Updated Todo"})
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["title"] == "Updated Todo"
def test_delete_todo():
todo_id = create_test_todo().json()["id"]
response = client.delete(f"/todos/{todo_id}")
assert response.status_code == 204
def create_test_todo():
return client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023
--- Dockerfile ---
# Stage 1: Build
FROM python:3.12-slim as builder
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir poetry && \
poetry config virtualenvs.create false && \
poetry install --no-interaction --no-ansi
COPY models.py schemas.py main.py test_main.py .
# Stage 2: Production
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local/share/python-poetry/cache /root/.local/share/python-poetry/cache
COPY --from=builder /app /app
RUN useradd -m appuser && \
chown -R appuser:appuser /app
USER appuser
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
## Tulos
```
VERDICT: GREEN
## Overview
This project is a simple Todo application using FastAPI for the backend and SQLite as the database. It includes CRUD endpoints for managing tasks.
## Files
| File | Purpose |
|------|---------|
| models.py | Defines the data model for the todo items using SQLAlchemy ORM. |
| schemas.py | Contains Pydantic schemas for validating request and response data. |
| main.py | The FastAPI application, defining routes and business logic. |
| pyproject.toml | Project metadata and dependencies. |
| test_main.py | Unit tests for the FastAPI endpoints. |
| Dockerfile | Docker configuration to build and run the application. |
## Quick Start
git clone <repo>
cd project
uv sync
uv run uvicorn main:app --reload
## Docker
docker build -t todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit .
docker run -p 8000:8000 todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | /todos/ | Create a new todo item. |
| GET | /todos/ | List all todos or filter by status. |
| GET | /todos/{id} | Retrieve a specific todo item by ID. |
| PUT | /todos/{id} | Update an existing todo item. |
| DELETE | /todos/{id} | Delete a todo item. |
## Architecture
The project follows a clean separation of concerns:
- **models.py**: Defines the data model using SQLAlchemy ORM.
- **schemas.py**: Contains Pydantic models for data validation and serialization.
- **main.py**: Implements FastAPI routes, business logic, and database interactions.
This logical structure makes it easy to understand and maintain each component independently.
## Risk Assessment
| Severity | Issue |
|----------|-------|
| LOW | The project uses parameterized queries in the SQLAlchemy ORM which helps prevent SQL injection. However, input validation is minimal (e.g., no checks for `due_date` format). |
| MEDIUM | Error handling and database connection management are basic but adequate. Database connections are managed via context managers, which ensures proper closure of sessions. |
| LOW | The project uses consistent naming conventions for variables and functions, making the code easy to understand. However, there's room for improving documentation around API endpoints and models. |
Overall assessment: "SHIP IT"
```