Pipelinen parannuksia building blockeilla
This commit is contained in:
13
zipit/todo_new/Dockerfile
Normal file
13
zipit/todo_new/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY pyproject.toml /app/
|
||||
RUN uv sync -y
|
||||
COPY models.py schemas.py main.py test_main.py /app/
|
||||
|
||||
ENV UV_CACHE_DIR=/tmp/uv-cache
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
63
zipit/todo_new/README.md
Normal file
63
zipit/todo_new/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
VERDICT: GREEN
|
||||
|
||||
## Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
|
||||
|
||||
### Overview
|
||||
This project is a simple todo application built using the FastAPI framework and an SQLite database. It provides CRUD (Create, Read, Update, Delete) endpoints for managing tasks.
|
||||
|
||||
### Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| models.py | Defines the data model for tasks using SQLAlchemy ORM. |
|
||||
| schemas.py | Contains Pydantic models for input validation. |
|
||||
| main.py | Implements FastAPI routes and database operations. |
|
||||
| pyproject.toml | Project metadata and dependencies configuration. |
|
||||
| test_main.py | Unit tests for all CRUD endpoints using FastAPI TestClient. |
|
||||
| Dockerfile | Defines the build steps to create a Docker image of 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 | /tasks/ | Create a new task. |
|
||||
| GET | /tasks/ | List all tasks or filter by status. |
|
||||
| GET | /tasks/{id} | Get a task by ID. |
|
||||
| PUT | /tasks/{id} | Update an existing task. |
|
||||
| DELETE | /tasks/{id} | Delete a task. |
|
||||
|
||||
### Architecture
|
||||
The project structure is logical and organized into separate files for models, schemas, main application logic, testing, and Docker configuration.
|
||||
- **models.py**: Defines the `Task` model using SQLAlchemy ORM to interact with the SQLite database.
|
||||
- **schemas.py**: Contains Pydantic models (`TaskCreate`, `TaskResponse`) for input validation and response formatting.
|
||||
- **main.py**: Implements FastAPI routes that handle CRUD operations. It uses dependency injection via `get_db` function to manage database sessions.
|
||||
- **pyproject.toml**: Specifies project metadata and dependencies such as FastAPI, Uvicorn, and SQLAlchemy.
|
||||
- **test_main.py**: Contains unit tests for all CRUD endpoints using the FastAPI TestClient.
|
||||
- **Dockerfile**: Defines the Docker image build process.
|
||||
|
||||
### Risk Assessment
|
||||
| Severity | Issue |
|
||||
|----------|-------|
|
||||
| LOW | SQL injection risks are mitigated by using ORM (SQLAlchemy) which prevents direct SQL execution. |
|
||||
| MEDIUM | Input validation is done at the schema level with Pydantic, but there's no explicit check for sensitive data like passwords in `TaskCreate`. |
|
||||
| HIGH | Error handling could be more graceful and informative to improve user experience and debugging. |
|
||||
| LOW | Database connection management using SQLAlchemy sessionmaker is handled correctly. It ensures that sessions are properly opened and closed.
|
||||
| MEDIUM | Edge cases such as concurrent access or large datasets aren't explicitly handled, which might lead to performance issues.
|
||||
|
||||
### Maintanability
|
||||
- **Consistent Naming**: Variable names like `task_id`, `db_task` follow a consistent naming convention.
|
||||
- **Clear Code Structure**: Each operation (CRUD) has its own function in `main.py`, and the code is structured clearly for readability.
|
||||
- **New Developer Understanding**: A new developer would find it easy to understand the project due to organized file structure, clear comments, and well-defined responsibilities.
|
||||
|
||||
### Overall Assessment
|
||||
**SHIP IT**
|
||||
|
||||
The project demonstrates a good balance between functionality and maintainability. The use of FastAPI, SQLAlchemy, and Pydantic ensures that the application is both performant and secure. The addition of unit tests further enhances confidence in the reliability and correctness of the codebase.
|
||||
160
zipit/todo_new/agents.yaml
Normal file
160
zipit/todo_new/agents.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
# Agents — Kipinä Agentic Studio → CrewAI
|
||||
|
||||
client:
|
||||
role: >-
|
||||
Client
|
||||
goal: >-
|
||||
product owner who turns vague ideas into clear, actionable software requirements
|
||||
backstory: |
|
||||
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
|
||||
llm: qwen-coder
|
||||
|
||||
data:
|
||||
role: >-
|
||||
Data Engineer
|
||||
goal: >-
|
||||
database architect specializing in SQLAlchemy and relational databases
|
||||
backstory: |
|
||||
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)
|
||||
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
|
||||
7. SQLite: create_engine(url, connect_args={"check_same_thread": False})
|
||||
|
||||
ENUM HANDLING (IMPORTANT):
|
||||
- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible
|
||||
- Do NOT define Python Enum classes — use plain strings instead
|
||||
- Example: status = Column(String(20), default="pending")
|
||||
|
||||
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
|
||||
- create_engine with connect_args={"check_same_thread": False}
|
||||
llm: qwen-coder
|
||||
|
||||
coder:
|
||||
role: >-
|
||||
Coder
|
||||
goal: >-
|
||||
expert Python developer
|
||||
backstory: |
|
||||
You are an expert Python developer. Write complete, production-ready code.
|
||||
|
||||
CRITICAL RULES:
|
||||
1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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)
|
||||
llm: qwen-coder
|
||||
|
||||
qa:
|
||||
role: >-
|
||||
QA
|
||||
goal: >-
|
||||
QA engineer responsible for code review and automated testing
|
||||
backstory: |
|
||||
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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
llm: qwen-coder
|
||||
|
||||
tester:
|
||||
role: >-
|
||||
DevOps
|
||||
goal: >-
|
||||
DevOps engineer specializing in containerization and deployment
|
||||
backstory: |
|
||||
You are a DevOps engineer specializing in containerization and deployment.
|
||||
|
||||
DOCKERFILE RULES:
|
||||
- Use python:3.12-slim as base
|
||||
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync)
|
||||
- Copy pyproject.toml first, then RUN uv sync, then COPY source files
|
||||
- Set USER AFTER installing dependencies (uv sync needs write access)
|
||||
- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache
|
||||
- NEVER use pip, poetry, or requirements.txt
|
||||
- Expose port 8000
|
||||
- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
Write ONLY the Dockerfile, no explanations.
|
||||
llm: qwen-coder
|
||||
|
||||
observer:
|
||||
role: >-
|
||||
Observer
|
||||
goal: >-
|
||||
independent technical observer and risk analyst
|
||||
backstory: |
|
||||
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"
|
||||
llm: qwen-coder
|
||||
85
zipit/todo_new/crew.py
Normal file
85
zipit/todo_new/crew.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
|
||||
|
||||
CrewAI crew — generated by Kipinä Agentic Studio.
|
||||
Run: crewai run
|
||||
"""
|
||||
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
|
||||
|
||||
@CrewBase
|
||||
class ProjectCrew:
|
||||
"""Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit"""
|
||||
|
||||
agents_config = "agents.yaml"
|
||||
tasks_config = "tasks.yaml"
|
||||
|
||||
@agent
|
||||
def client(self) -> Agent:
|
||||
return Agent(config=self.agents_config["client"])
|
||||
|
||||
@agent
|
||||
def data(self) -> Agent:
|
||||
return Agent(config=self.agents_config["data"])
|
||||
|
||||
@agent
|
||||
def coder(self) -> Agent:
|
||||
return Agent(config=self.agents_config["coder"])
|
||||
|
||||
@agent
|
||||
def qa(self) -> Agent:
|
||||
return Agent(config=self.agents_config["qa"])
|
||||
|
||||
@agent
|
||||
def tester(self) -> Agent:
|
||||
return Agent(config=self.agents_config["tester"])
|
||||
|
||||
@agent
|
||||
def observer(self) -> Agent:
|
||||
return Agent(config=self.agents_config["observer"])
|
||||
|
||||
@task
|
||||
def step_0_requirements(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_0_requirements"])
|
||||
|
||||
@task
|
||||
def step_1_models_py(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_1_models_py"])
|
||||
|
||||
@task
|
||||
def step_2_schemas_py(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_2_schemas_py"])
|
||||
|
||||
@task
|
||||
def step_3_main_py(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_3_main_py"])
|
||||
|
||||
@task
|
||||
def step_4_pyproject_toml(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_4_pyproject_toml"])
|
||||
|
||||
@task
|
||||
def step_5_review(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_5_review"])
|
||||
|
||||
@task
|
||||
def step_6_test_main_py(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_6_test_main_py"])
|
||||
|
||||
@task
|
||||
def step_7_dockerfile(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_7_dockerfile"])
|
||||
|
||||
@task
|
||||
def step_8_readme_md(self) -> Task:
|
||||
return Task(config=self.tasks_config["step_8_readme_md"])
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
56
zipit/todo_new/main.py
Normal file
56
zipit/todo_new/main.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
19
zipit/todo_new/models.py
Normal file
19
zipit/todo_new/models.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
66
zipit/todo_new/prompts/0_client_requirements.md
Normal file
66
zipit/todo_new/prompts/0_client_requirements.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 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
|
||||
|
||||
```
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
3. **Edit Task**: Users can update the details of an existing task.
|
||||
4. **Delete Task**: Users can remove tasks they no longer need.
|
||||
5. **Mark as Complete**: Users can mark tasks as completed.
|
||||
6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed).
|
||||
7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description.
|
||||
|
||||
### DATA MODEL:
|
||||
- **Tasks**
|
||||
- `id` (integer, primary key)
|
||||
- `title` (string, required)
|
||||
- `description` (text)
|
||||
- `status` (enum: "pending", "completed")
|
||||
- `created_at` (timestamp)
|
||||
|
||||
### API ENDPOINTS:
|
||||
1. **GET /tasks**: Retrieve a list of all tasks.
|
||||
2. **POST /tasks**: Create a new task with title, description, and status.
|
||||
3. **PUT /tasks/{id}**: Update an existing task by ID.
|
||||
4. **DELETE /tasks/{id}**: Delete a task by ID.
|
||||
5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status.
|
||||
|
||||
### CONSTRAINTS:
|
||||
- Must use SQLite as the database.
|
||||
- No authentication required for simplicity.
|
||||
```
|
||||
141
zipit/todo_new/prompts/1_data_models.py.md
Normal file
141
zipit/todo_new/prompts/1_data_models.py.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 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)
|
||||
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
|
||||
7. SQLite: create_engine(url, connect_args={"check_same_thread": False})
|
||||
|
||||
ENUM HANDLING (IMPORTANT):
|
||||
- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible
|
||||
- Do NOT define Python Enum classes — use plain strings instead
|
||||
- Example: status = Column(String(20), default="pending")
|
||||
|
||||
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
|
||||
- create_engine with connect_args={"check_same_thread": False}
|
||||
```
|
||||
|
||||
## 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)
|
||||
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
|
||||
7. SQLite: create_engine(url, connect_args={"check_same_thread": False})
|
||||
|
||||
ENUM HANDLING (IMPORTANT):
|
||||
- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible
|
||||
- Do NOT define Python Enum classes — use plain strings instead
|
||||
- Example: status = Column(String(20), default="pending")
|
||||
|
||||
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
|
||||
- create_engine with connect_args={"check_same_thread": False}
|
||||
|
||||
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):
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
3. **Edit Task**: Users can update the details of an existing task.
|
||||
4. **Delete Task**: Users can remove tasks they no longer need.
|
||||
5. **Mark as Complete**: Users can mark tasks as completed.
|
||||
6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed).
|
||||
7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description.
|
||||
|
||||
### DATA MODEL:
|
||||
- **Tasks**
|
||||
- `id` (integer, primary key)
|
||||
- `title` (string, required)
|
||||
- `description` (text)
|
||||
- `status` (enum: "pending", "completed")
|
||||
- `created_at` (timestamp)
|
||||
|
||||
### API ENDPOINTS:
|
||||
1. **GET /tasks**: Retrieve a list of all tasks.
|
||||
2. **POST /tasks**: Create a new task with title, description, and status.
|
||||
3. **PUT /tasks/{id}**: Update an existing task by ID.
|
||||
4. **DELETE /tasks/{id}**: Delete a task by ID.
|
||||
5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status.
|
||||
|
||||
### CONSTRAINTS:
|
||||
- Must use SQLite as the database.
|
||||
- No authentication required for simplicity.
|
||||
|
||||
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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
```
|
||||
150
zipit/todo_new/prompts/2_coder_schemas.py.md
Normal file
150
zipit/todo_new/prompts/2_coder_schemas.py.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
PROJECT REQUIREMENTS (from product owner):
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
3. **Edit Task**: Users can update the details of an existing task.
|
||||
4. **Delete Task**: Users can remove tasks they no longer need.
|
||||
5. **Mark as Complete**: Users can mark tasks as completed.
|
||||
6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed).
|
||||
7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description.
|
||||
|
||||
### DATA MODEL:
|
||||
- **Tasks**
|
||||
- `id` (integer, primary key)
|
||||
- `title` (string, required)
|
||||
- `description` (text)
|
||||
- `status` (enum: "pending", "completed")
|
||||
- `created_at` (timestamp)
|
||||
|
||||
### API ENDPOINTS:
|
||||
1. **GET /tasks**: Retrieve a list of all tasks.
|
||||
2. **POST /tasks**: Create a new task with title, description, and status.
|
||||
3. **PUT /tasks/{id}**: Update an existing task by ID.
|
||||
4. **DELETE /tasks/{id}**: Delete a task by ID.
|
||||
5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status.
|
||||
|
||||
### CONSTRAINTS:
|
||||
- Must use SQLite as the database.
|
||||
- No authentication required for simplicity.
|
||||
|
||||
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 TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
```
|
||||
252
zipit/todo_new/prompts/3_coder_main.py.md
Normal file
252
zipit/todo_new/prompts/3_coder_main.py.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
--- schemas.py ---
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
PROJECT REQUIREMENTS (from product owner):
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
3. **Edit Task**: Users can update the details of an existing task.
|
||||
4. **Delete Task**: Users can remove tasks they no longer need.
|
||||
5. **Mark as Complete**: Users can mark tasks as completed.
|
||||
6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed).
|
||||
7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description.
|
||||
|
||||
### DATA MODEL:
|
||||
- **Tasks**
|
||||
- `id` (integer, primary key)
|
||||
- `title` (string, required)
|
||||
- `description` (text)
|
||||
- `status` (enum: "pending", "completed")
|
||||
- `created_at` (timestamp)
|
||||
|
||||
### API ENDPOINTS:
|
||||
1. **GET /tasks**: Retrieve a list of all tasks.
|
||||
2. **POST /tasks**: Create a new task with title, description, and status.
|
||||
3. **PUT /tasks/{id}**: Update an existing task by ID.
|
||||
4. **DELETE /tasks/{id}**: Delete a task by ID.
|
||||
5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status.
|
||||
|
||||
### CONSTRAINTS:
|
||||
- Must use SQLite as the database.
|
||||
- No authentication required for simplicity.
|
||||
|
||||
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, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
```
|
||||
220
zipit/todo_new/prompts/4_coder_pyproject.toml.md
Normal file
220
zipit/todo_new/prompts/4_coder_pyproject.toml.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
--- schemas.py ---
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
--- main.py ---
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
|
||||
PROJECT REQUIREMENTS (from product owner):
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
3. **Edit Task**: Users can update the details of an existing task.
|
||||
4. **Delete Task**: Users can remove tasks they no longer need.
|
||||
5. **Mark as Complete**: Users can mark tasks as completed.
|
||||
6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed).
|
||||
7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description.
|
||||
|
||||
### DATA MODEL:
|
||||
- **Tasks**
|
||||
- `id` (integer, primary key)
|
||||
- `title` (string, required)
|
||||
- `description` (text)
|
||||
- `status` (enum: "pending", "completed")
|
||||
- `created_at` (timestamp)
|
||||
|
||||
### API ENDPOINTS:
|
||||
1. **GET /tasks**: Retrieve a list of all tasks.
|
||||
2. **POST /tasks**: Create a new task with title, description, and status.
|
||||
3. **PUT /tasks/{id}**: Update an existing task by ID.
|
||||
4. **DELETE /tasks/{id}**: Delete a task by ID.
|
||||
5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status.
|
||||
|
||||
### CONSTRAINTS:
|
||||
- Must use SQLite as the database.
|
||||
- No authentication required for simplicity.
|
||||
|
||||
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-app"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastapi",
|
||||
"uvicorn[standard]",
|
||||
"sqlalchemy",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
dev = "uvicorn main:app --reload"
|
||||
```
|
||||
182
zipit/todo_new/prompts/5_qa_review.md
Normal file
182
zipit/todo_new/prompts/5_qa_review.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
```
|
||||
|
||||
## 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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
|
||||
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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
--- schemas.py ---
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
--- main.py ---
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
|
||||
--- pyproject.toml ---
|
||||
[project]
|
||||
name = "todo-app"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastapi",
|
||||
"uvicorn[standard]",
|
||||
"sqlalchemy",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
dev = "uvicorn main:app --reload"
|
||||
```
|
||||
|
||||
## Tulos
|
||||
|
||||
```
|
||||
LGTM
|
||||
```
|
||||
277
zipit/todo_new/prompts/6_qa_test_main.py.md
Normal file
277
zipit/todo_new/prompts/6_qa_test_main.py.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
```
|
||||
|
||||
## 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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
|
||||
Write pytest tests for this project:
|
||||
|
||||
--- models.py ---
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
--- schemas.py ---
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
--- main.py ---
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
|
||||
--- pyproject.toml ---
|
||||
[project]
|
||||
name = "todo-app"
|
||||
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
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(DATABASE_URL)
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
yield TestingSessionLocal()
|
||||
finally:
|
||||
pass
|
||||
|
||||
from main import app, get_db
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_database():
|
||||
with engine.begin() as conn:
|
||||
conn.execute("DELETE FROM tasks")
|
||||
|
||||
def test_create_task():
|
||||
response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["id"] > 0
|
||||
assert data["title"] == "Test Task"
|
||||
assert data["description"] == "This is a test task"
|
||||
|
||||
def test_list_tasks():
|
||||
client.post("/tasks/", json={"title": "Task 1", "description": "First task"})
|
||||
client.post("/tasks/", json={"title": "Task 2", "description": "Second task"})
|
||||
response = client.get("/tasks/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 2
|
||||
|
||||
def test_get_task():
|
||||
response = client.post("/tasks/", json={"title": "Get Task", "description": "Task to get"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
|
||||
def test_get_task_not_found():
|
||||
response = client.get("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_task():
|
||||
response = client.post("/tasks/", json={"title": "Update Task", "description": "Task to update"})
|
||||
task_id = response.json()["id"]
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put(f"/tasks/{task_id}", json=updated_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
assert data["title"] == "Updated Title"
|
||||
|
||||
def test_update_task_not_found():
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put("/tasks/999", json=updated_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task():
|
||||
response = client.post("/tasks/", json={"title": "Delete Task", "description": "Task to delete"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.delete(f"/tasks/{task_id}")
|
||||
assert response.status_code == 204
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task_not_found():
|
||||
response = client.delete("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
This `test_main.py` file includes tests for all CRUD operations using the FastAPI TestClient. Each test creates its own data and ensures that there are no dependencies between tests. The database is reset before each test to ensure isolation.
|
||||
```
|
||||
72
zipit/todo_new/prompts/7_tester_dockerfile.md
Normal file
72
zipit/todo_new/prompts/7_tester_dockerfile.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# 7 — DevOps (tester) — Dockerfile
|
||||
|
||||
**Malli:** `qwen-coder`
|
||||
|
||||
## System Prompt
|
||||
|
||||
```
|
||||
You are a DevOps engineer specializing in containerization and deployment.
|
||||
|
||||
DOCKERFILE RULES:
|
||||
- Use python:3.12-slim as base
|
||||
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync)
|
||||
- Copy pyproject.toml first, then RUN uv sync, then COPY source files
|
||||
- Set USER AFTER installing dependencies (uv sync needs write access)
|
||||
- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache
|
||||
- NEVER use pip, poetry, or requirements.txt
|
||||
- Expose port 8000
|
||||
- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
Write ONLY the Dockerfile, no explanations.
|
||||
```
|
||||
|
||||
## Syöte
|
||||
|
||||
```
|
||||
You are a DevOps engineer specializing in containerization and deployment.
|
||||
|
||||
DOCKERFILE RULES:
|
||||
- Use python:3.12-slim as base
|
||||
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync)
|
||||
- Copy pyproject.toml first, then RUN uv sync, then COPY source files
|
||||
- Set USER AFTER installing dependencies (uv sync needs write access)
|
||||
- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache
|
||||
- NEVER use pip, poetry, or requirements.txt
|
||||
- Expose port 8000
|
||||
- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
Write ONLY the Dockerfile, 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
|
||||
|
||||
```
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY pyproject.toml /app/
|
||||
RUN uv sync -y
|
||||
COPY models.py schemas.py main.py test_main.py /app/
|
||||
|
||||
ENV UV_CACHE_DIR=/tmp/uv-cache
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
380
zipit/todo_new/prompts/8_observer_readme.md.md
Normal file
380
zipit/todo_new/prompts/8_observer_readme.md.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
--- schemas.py ---
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
--- main.py ---
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from models import Base, engine, SessionLocal, Task
|
||||
from schemas import TaskCreate, TaskResponse
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
app = FastAPI()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
|
||||
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = Task(**task.model_dump())
|
||||
db.add(db_task)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.get("/tasks/", response_model=list[TaskResponse])
|
||||
def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)):
|
||||
if status:
|
||||
tasks = db.query(Task).filter(Task.status == status).all()
|
||||
else:
|
||||
tasks = db.query(Task).all()
|
||||
return tasks
|
||||
|
||||
@app.get("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def get_task(task_id: int, db: Session = Depends(get_db)):
|
||||
task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return task
|
||||
|
||||
@app.put("/tasks/{task_id}", response_model=TaskResponse)
|
||||
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
for key, value in task.model_dump().items():
|
||||
setattr(db_task, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_task)
|
||||
return db_task
|
||||
|
||||
@app.delete("/tasks/{task_id}", status_code=204)
|
||||
def delete_task(task_id: int, db: Session = Depends(get_db)):
|
||||
db_task = db.query(Task).filter(Task.id == task_id).first()
|
||||
if not db_task:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(db_task)
|
||||
db.commit()
|
||||
|
||||
--- pyproject.toml ---
|
||||
[project]
|
||||
name = "todo-app"
|
||||
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
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(DATABASE_URL)
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
yield TestingSessionLocal()
|
||||
finally:
|
||||
pass
|
||||
|
||||
from main import app, get_db
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_database():
|
||||
with engine.begin() as conn:
|
||||
conn.execute("DELETE FROM tasks")
|
||||
|
||||
def test_create_task():
|
||||
response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["id"] > 0
|
||||
assert data["title"] == "Test Task"
|
||||
assert data["description"] == "This is a test task"
|
||||
|
||||
def test_list_tasks():
|
||||
client.post("/tasks/", json={"title": "Task 1", "description": "First task"})
|
||||
client.post("/tasks/", json={"title": "Task 2", "description": "Second task"})
|
||||
response = client.get("/tasks/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 2
|
||||
|
||||
def test_get_task():
|
||||
response = client.post("/tasks/", json={"title": "Get Task", "description": "Task to get"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
|
||||
def test_get_task_not_found():
|
||||
response = client.get("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_task():
|
||||
response = client.post("/tasks/", json={"title": "Update Task", "description": "Task to update"})
|
||||
task_id = response.json()["id"]
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put(f"/tasks/{task_id}", json=updated_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
assert data["title"] == "Updated Title"
|
||||
|
||||
def test_update_task_not_found():
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put("/tasks/999", json=updated_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task():
|
||||
response = client.post("/tasks/", json={"title": "Delete Task", "description": "Task to delete"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.delete(f"/tasks/{task_id}")
|
||||
assert response.status_code == 204
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task_not_found():
|
||||
response = client.delete("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
This `test_main.py` file includes tests for all CRUD operations using the FastAPI TestClient. Each test creates its own data and ensures that there are no dependencies between tests. The database is reset before each test to ensure isolation.
|
||||
|
||||
--- Dockerfile ---
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY pyproject.toml /app/
|
||||
RUN uv sync -y
|
||||
COPY models.py schemas.py main.py test_main.py /app/
|
||||
|
||||
ENV UV_CACHE_DIR=/tmp/uv-cache
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
## Tulos
|
||||
|
||||
```
|
||||
VERDICT: GREEN
|
||||
|
||||
## Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit
|
||||
|
||||
### Overview
|
||||
This project is a simple todo application built using the FastAPI framework and an SQLite database. It provides CRUD (Create, Read, Update, Delete) endpoints for managing tasks.
|
||||
|
||||
### Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| models.py | Defines the data model for tasks using SQLAlchemy ORM. |
|
||||
| schemas.py | Contains Pydantic models for input validation. |
|
||||
| main.py | Implements FastAPI routes and database operations. |
|
||||
| pyproject.toml | Project metadata and dependencies configuration. |
|
||||
| test_main.py | Unit tests for all CRUD endpoints using FastAPI TestClient. |
|
||||
| Dockerfile | Defines the build steps to create a Docker image of 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 | /tasks/ | Create a new task. |
|
||||
| GET | /tasks/ | List all tasks or filter by status. |
|
||||
| GET | /tasks/{id} | Get a task by ID. |
|
||||
| PUT | /tasks/{id} | Update an existing task. |
|
||||
| DELETE | /tasks/{id} | Delete a task. |
|
||||
|
||||
### Architecture
|
||||
The project structure is logical and organized into separate files for models, schemas, main application logic, testing, and Docker configuration.
|
||||
- **models.py**: Defines the `Task` model using SQLAlchemy ORM to interact with the SQLite database.
|
||||
- **schemas.py**: Contains Pydantic models (`TaskCreate`, `TaskResponse`) for input validation and response formatting.
|
||||
- **main.py**: Implements FastAPI routes that handle CRUD operations. It uses dependency injection via `get_db` function to manage database sessions.
|
||||
- **pyproject.toml**: Specifies project metadata and dependencies such as FastAPI, Uvicorn, and SQLAlchemy.
|
||||
- **test_main.py**: Contains unit tests for all CRUD endpoints using the FastAPI TestClient.
|
||||
- **Dockerfile**: Defines the Docker image build process.
|
||||
|
||||
### Risk Assessment
|
||||
| Severity | Issue |
|
||||
|----------|-------|
|
||||
| LOW | SQL injection risks are mitigated by using ORM (SQLAlchemy) which prevents direct SQL execution. |
|
||||
| MEDIUM | Input validation is done at the schema level with Pydantic, but there's no explicit check for sensitive data like passwords in `TaskCreate`. |
|
||||
| HIGH | Error handling could be more graceful and informative to improve user experience and debugging. |
|
||||
| LOW | Database connection management using SQLAlchemy sessionmaker is handled correctly. It ensures that sessions are properly opened and closed.
|
||||
| MEDIUM | Edge cases such as concurrent access or large datasets aren't explicitly handled, which might lead to performance issues.
|
||||
|
||||
### Maintanability
|
||||
- **Consistent Naming**: Variable names like `task_id`, `db_task` follow a consistent naming convention.
|
||||
- **Clear Code Structure**: Each operation (CRUD) has its own function in `main.py`, and the code is structured clearly for readability.
|
||||
- **New Developer Understanding**: A new developer would find it easy to understand the project due to organized file structure, clear comments, and well-defined responsibilities.
|
||||
|
||||
### Overall Assessment
|
||||
**SHIP IT**
|
||||
|
||||
The project demonstrates a good balance between functionality and maintainability. The use of FastAPI, SQLAlchemy, and Pydantic ensures that the application is both performant and secure. The addition of unit tests further enhances confidence in the reliability and correctness of the codebase.
|
||||
```
|
||||
12
zipit/todo_new/pyproject.toml
Normal file
12
zipit/todo_new/pyproject.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "todo-app"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastapi",
|
||||
"uvicorn[standard]",
|
||||
"sqlalchemy",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
dev = "uvicorn main:app --reload"
|
||||
12
zipit/todo_new/schemas.py
Normal file
12
zipit/todo_new/schemas.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
status: str = "pending"
|
||||
|
||||
class TaskResponse(TaskCreate):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
440
zipit/todo_new/tasks.yaml
Normal file
440
zipit/todo_new/tasks.yaml
Normal file
@@ -0,0 +1,440 @@
|
||||
# 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)
|
||||
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
|
||||
7. SQLite: create_engine(url, connect_args={"check_same_thread": False})
|
||||
|
||||
ENUM HANDLING (IMPORTANT):
|
||||
- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible
|
||||
- Do NOT define Python Enum classes — use plain strings instead
|
||||
- Example: status = Column(String(20), default="pending")
|
||||
|
||||
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
|
||||
- create_engine with connect_args={"check_same_thread": False}
|
||||
|
||||
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):
|
||||
### PROJECT NAME: Todo-App
|
||||
|
||||
### GOAL:
|
||||
Create a simple task manager application for individuals to keep track of their daily tasks.
|
||||
|
||||
### CORE FEATURES:
|
||||
1. **Add Task**: Users can add new tasks with title and description.
|
||||
2. **View Tasks**: Users can see all their tasks in a list.
|
||||
# ... (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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
# ... (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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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()
|
||||
# ... (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 — including stdlib (from datetime import date, etc.)
|
||||
2. Import from other project files: from models import Todo, SessionLocal
|
||||
3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ...
|
||||
4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo)
|
||||
5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False})
|
||||
6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close()
|
||||
8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True
|
||||
9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204)
|
||||
|
||||
NEVER:
|
||||
- Leave out any import (EVERY type you use must be imported)
|
||||
- Use relative imports (from .models)
|
||||
- Add explanations or comments
|
||||
- Leave placeholder code or TODO comments
|
||||
- Use Flask syntax (app.run) in FastAPI projects
|
||||
- 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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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()
|
||||
|
||||
# ... (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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
|
||||
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, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
# ... (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:
|
||||
- ALWAYS import app from main.py: from main import app, get_db
|
||||
- ALWAYS import Base from models.py: from models import Base
|
||||
- NEVER redefine the app, models, or routes in the test file
|
||||
- Use file-based SQLite for test isolation: sqlite:///./test.db
|
||||
- Override the get_db dependency to use test database
|
||||
- Use TestClient from fastapi.testclient
|
||||
- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204)
|
||||
- Each test should create its own data, not depend on other tests
|
||||
|
||||
Write pytest tests for this project:
|
||||
|
||||
--- models.py ---
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
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 Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
# ... (truncated)
|
||||
expected_output: >-
|
||||
test_main.py
|
||||
agent: qa
|
||||
|
||||
step_7_dockerfile:
|
||||
description: |
|
||||
You are a DevOps engineer specializing in containerization and deployment.
|
||||
|
||||
DOCKERFILE RULES:
|
||||
- Use python:3.12-slim as base
|
||||
- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync)
|
||||
- Copy pyproject.toml first, then RUN uv sync, then COPY source files
|
||||
- Set USER AFTER installing dependencies (uv sync needs write access)
|
||||
- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache
|
||||
- NEVER use pip, poetry, or requirements.txt
|
||||
- Expose port 8000
|
||||
- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
Write ONLY the Dockerfile, 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 <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
|
||||
```
|
||||
# ... (truncated)
|
||||
expected_output: >-
|
||||
README.md
|
||||
agent: observer
|
||||
94
zipit/todo_new/test_main.py
Normal file
94
zipit/todo_new/test_main.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(DATABASE_URL)
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text)
|
||||
status = Column(String(20), default="pending")
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
yield TestingSessionLocal()
|
||||
finally:
|
||||
pass
|
||||
|
||||
from main import app, get_db
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_database():
|
||||
with engine.begin() as conn:
|
||||
conn.execute("DELETE FROM tasks")
|
||||
|
||||
def test_create_task():
|
||||
response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["id"] > 0
|
||||
assert data["title"] == "Test Task"
|
||||
assert data["description"] == "This is a test task"
|
||||
|
||||
def test_list_tasks():
|
||||
client.post("/tasks/", json={"title": "Task 1", "description": "First task"})
|
||||
client.post("/tasks/", json={"title": "Task 2", "description": "Second task"})
|
||||
response = client.get("/tasks/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 2
|
||||
|
||||
def test_get_task():
|
||||
response = client.post("/tasks/", json={"title": "Get Task", "description": "Task to get"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
|
||||
def test_get_task_not_found():
|
||||
response = client.get("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_task():
|
||||
response = client.post("/tasks/", json={"title": "Update Task", "description": "Task to update"})
|
||||
task_id = response.json()["id"]
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put(f"/tasks/{task_id}", json=updated_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
assert data["title"] == "Updated Title"
|
||||
|
||||
def test_update_task_not_found():
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put("/tasks/999", json=updated_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task():
|
||||
response = client.post("/tasks/", json={"title": "Delete Task", "description": "Task to delete"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.delete(f"/tasks/{task_id}")
|
||||
assert response.status_code == 204
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task_not_found():
|
||||
response = client.delete("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
This `test_main.py` file includes tests for all CRUD operations using the FastAPI TestClient. Each test creates its own data and ensures that there are no dependencies between tests. The database is reset before each test to ensure isolation.
|
||||
Reference in New Issue
Block a user