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