# 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 cd project uv sync uv run uvicorn main:app --reload ``` ## Docker ```bash docker build -t todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit . docker run -p 8000:8000 todo-sovellus-fastapi---sqlite--crud-endpointit-ja-testit ``` ## API Endpoints | Method | Path | Description | |--------|------|-------------| | POST | /items/ | Create | | GET | /items/ | List all | | GET | /items/{id} | Get by ID | | PUT | /items/{id} | Update | | DELETE | /items/{id} | Delete | (Adapt paths and descriptions to match the actual code) ## Architecture Describe the project structure and design decisions. ## Risk Assessment | Severity | Issue | |----------|-------| | ... | ... | Project code: --- models.py --- from sqlalchemy import create_engine, Column, Integer, String, Date, Enum from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = "sqlite:///./todo.db" engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class Todo(Base): __tablename__ = "todos" id = Column(Integer, primary_key=True, index=True) title = Column(String(100), nullable=False) description = Column(String(500)) due_date = Column(Date, nullable=False) status = Column(Enum('pending', 'completed'), default='pending') --- schemas.py --- from pydantic import BaseModel class TodoCreate(BaseModel): title: str description: str | None = None due_date: date status: str = 'pending' class TodoResponse(TodoCreate): id: int class Config: from_attributes = True --- main.py --- from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.orm import Session from models import Base, engine, SessionLocal, Todo from schemas import TodoCreate, TodoResponse Base.metadata.create_all(bind=engine) app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.post("/todos/", response_model=TodoResponse, status_code=201) def create_todo(todo: TodoCreate, db: Session = Depends(get_db)): db_todo = Todo(**todo.model_dump()) db.add(db_todo) db.commit() db.refresh(db_todo) return db_todo @app.get("/todos/", response_model=list[TodoResponse]) def list_todos(status: str | None = None, db: Session = Depends(get_db)): if status: query = db.query(Todo).filter_by(status=status) else: query = db.query(Todo) return query.all() @app.get("/todos/{todo_id}", response_model=TodoResponse) def get_todo(todo_id: int, db: Session = Depends(get_db)): todo = db.query(Todo).filter(Todo.id == todo_id).first() if not todo: raise HTTPException(status_code=404, detail="Not found") return todo @app.put("/todos/{todo_id}", response_model=TodoResponse) def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)): db_todo = db.query(Todo).filter(Todo.id == todo_id).first() if not db_todo: raise HTTPException(status_code=404, detail="Not found") for key, value in todo.model_dump().items(): setattr(db_todo, key, value) db.commit() db.refresh(db_todo) return db_todo @app.delete("/todos/{todo_id}", status_code=204) def delete_todo(todo_id: int, db: Session = Depends(get_db)): db_todo = db.query(Todo).filter(Todo.id == todo_id).first() if not db_todo: raise HTTPException(status_code=404, detail="Not found") db.delete(db_todo) db.commit() --- pyproject.toml --- [project] name = "todo-sovellus" version = "0.1.0" requires-python = ">=3.11" dependencies = [ "fastapi", "uvicorn[standard]", "sqlalchemy", ] [project.scripts] dev = "uvicorn main:app --reload" --- test_main.py --- from fastapi.testclient import TestClient from sqlalchemy import create_engine, Column, Integer, String, Date, Enum from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = "sqlite:///./test_todo.db" engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class Todo(Base): __tablename__ = "todos" id = Column(Integer, primary_key=True, index=True) title = Column(String(100), nullable=False) description = Column(String(500)) due_date = Column(Date, nullable=False) status = Column(Enum('pending', 'completed'), default='pending') Base.metadata.create_all(bind=engine) def get_db(): db = SessionLocal() try: yield db finally: db.close() app = FastAPI() @app.post("/todos/", response_model=Todo, status_code=201) def create_todo(todo: TodoCreate, db: Session = Depends(get_db)): db_todo = Todo(**todo.model_dump()) db.add(db_todo) db.commit() db.refresh(db_todo) return db_todo @app.get("/todos/", response_model=list[Todo]) def list_todos(status: str | None = None, db: Session = Depends(get_db)): if status: query = db.query(Todo).filter_by(status=status) else: query = db.query(Todo) return query.all() @app.get("/todos/{todo_id}", response_model=Todo) def get_todo(todo_id: int, db: Session = Depends(get_db)): todo = db.query(Todo).filter(Todo.id == todo_id).first() if not todo: raise HTTPException(status_code=404, detail="Not found") return todo @app.put("/todos/{todo_id}", response_model=Todo) def update_todo(todo_id: int, todo: TodoCreate, db: Session = Depends(get_db)): db_todo = db.query(Todo).filter(Todo.id == todo_id).first() if not db_todo: raise HTTPException(status_code=404, detail="Not found") for key, value in todo.model_dump().items(): setattr(db_todo, key, value) db.commit() db.refresh(db_todo) return db_todo @app.delete("/todos/{todo_id}", status_code=204) def delete_todo(todo_id: int, db: Session = Depends(get_db)): db_todo = db.query(Todo).filter(Todo.id == todo_id).first() if not db_todo: raise HTTPException(status_code=404, detail="Not found") db.delete(db_todo) db.commit() client = TestClient(app) def test_create_todo(): response = client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"}) assert response.status_code == 201 data = response.json() assert "id" in data assert data["title"] == "Test Todo" assert data["description"] == "This is a test todo." assert data["due_date"] == "2023-12-31" assert data["status"] == "pending" def test_list_todos(): response = client.get("/todos/") assert response.status_code == 200 data = response.json() assert len(data) > 0 def test_get_todo_by_id(): todo_id = create_test_todo().json()["id"] response = client.get(f"/todos/{todo_id}") assert response.status_code == 200 data = response.json() assert "id" in data assert data["title"] == "Test Todo" def test_get_nonexistent_todo(): response = client.get("/todos/999") assert response.status_code == 404 def test_update_todo(): todo_id = create_test_todo().json()["id"] response = client.put(f"/todos/{todo_id}", json={"title": "Updated Todo"}) assert response.status_code == 200 data = response.json() assert "id" in data assert data["title"] == "Updated Todo" def test_delete_todo(): todo_id = create_test_todo().json()["id"] response = client.delete(f"/todos/{todo_id}") assert response.status_code == 204 def create_test_todo(): return client.post("/todos/", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023 --- Dockerfile --- # Stage 1: Build FROM python:3.12-slim as builder WORKDIR /app COPY pyproject.toml . RUN pip install --no-cache-dir poetry && \ poetry config virtualenvs.create false && \ poetry install --no-interaction --no-ansi COPY models.py schemas.py main.py test_main.py . # Stage 2: Production FROM python:3.12-slim WORKDIR /app COPY --from=builder /root/.local/share/python-poetry/cache /root/.local/share/python-poetry/cache COPY --from=builder /app /app RUN useradd -m appuser && \ chown -R appuser:appuser /app USER appuser COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv EXPOSE 8000 CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ## Tulos ``` VERDICT: GREEN ## Overview This project is a simple Todo application using FastAPI for the backend and SQLite as the database. It includes CRUD endpoints for managing tasks. ## Files | File | Purpose | |------|---------| | models.py | Defines the data model for the todo items using SQLAlchemy ORM. | | schemas.py | Contains Pydantic schemas for validating request and response data. | | main.py | The FastAPI application, defining routes and business logic. | | pyproject.toml | Project metadata and dependencies. | | test_main.py | Unit tests for the FastAPI endpoints. | | Dockerfile | Docker configuration to build and run the application. | ## Quick Start git clone 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" ```