# Tasks — Kipinä Agentic Studio → CrewAI step_0_requirements: description: | Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit expected_output: >- requirements agent: client step_1_models_py: description: | 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) # ... (truncated) expected_output: >- models.py agent: data step_2_schemas_py: description: | 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) # ... (truncated) expected_output: >- schemas.py agent: coder step_3_main_py: description: | 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) # ... (truncated) expected_output: >- main.py agent: coder step_4_pyproject_toml: description: | 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) # ... (truncated) expected_output: >- pyproject.toml agent: coder step_5_review: description: | 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 # ... (truncated) expected_output: >- review agent: qa step_6_test_main_py: description: | 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 # ... (truncated) expected_output: >- test_main.py agent: qa step_7_dockerfile: description: | 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. expected_output: >- Dockerfile agent: tester step_8_readme_md: description: | 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 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 ``` # ... (truncated) expected_output: >- README.md agent: observer