381 lines
13 KiB
Markdown
381 lines
13 KiB
Markdown
# 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.
|
|
```
|