Pipelinen parannuksia building blockeilla

This commit is contained in:
Jaakko Vanhala
2026-04-12 18:48:14 +03:00
parent c1a5f8aff5
commit b2ee8b9031
175 changed files with 13311 additions and 237 deletions

View File

@@ -0,0 +1,16 @@
FROM python:3.12-slim as builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
COPY pyproject.toml .
RUN uv sync
COPY . .
USER 1000
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,76 @@
from fastapi import FastAPI, HTTPException, Depends, Query
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime
from models import Todo, SessionLocal
from schemas import TodoCreate, TodoResponse
app = FastAPI()
# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/todos", response_model=TodoResponse, status_code=201)
async 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])
async def read_todos(status: Optional[str] = Query(None), db: Session = Depends(get_db)):
if status:
todos = db.query(Todo).filter(Todo.status == status).all()
else:
todos = db.query(Todo).all()
return todos
@app.get("/todos/{id}", response_model=TodoResponse)
async def read_todo(id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == id).first()
if not todo:
raise HTTPException(status_code=404, detail="Todo not found")
return todo
@app.put("/todos/{id}", response_model=TodoResponse)
async def update_todo(id: int, todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Todo 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/{id}", status_code=204)
async def delete_todo(id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Todo not found")
db.delete(db_todo)
db.commit()
@app.patch("/todos/{id}/complete", status_code=204)
async def complete_todo(id: int, db: Session = Depends(get_db)):
db_todo = db.query(Todo).filter(Todo.id == id).first()
if not db_todo:
raise HTTPException(status_code=404, detail="Todo not found")
db_todo.status = 'completed'
db.commit()
@app.get("/todos/count", response_model=int)
async def get_todo_count(db: Session = Depends(get_db)):
return db.query(Todo).count()
@app.get("/todos/search?q={query}", response_model=List[TodoResponse])
async def search_todos(query: str, db: Session = Depends(get_db)):
todos = db.query(Todo).filter(Todo.title.contains(query)).all()
return todos

View File

@@ -0,0 +1,23 @@
from sqlalchemy import create_engine, Column, Integer, String, Text, Enum, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime
DATABASE_URL = "sqlite:///./todos.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class TodoStatus(Enum):
completed = 'completed'
pending = 'pending'
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
description = Column(Text, nullable=True)
status = Column(Enum(TodoStatus), default=TodoStatus.pending)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)

View File

@@ -0,0 +1,32 @@
[project]
name = "todo-app"
version = "0.1.0"
description = "A simple todo application using FastAPI and SQLAlchemy"
authors = [
{ name="Your Name", email="your.email@example.com" }
]
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
"python-dotenv"
]
[tool.poetry]
name = "todo-app"
version = "0.1.0"
description = "A simple todo application using FastAPI and SQLAlchemy"
authors = [
{ name="Your Name", email="your.email@example.com" }
]
[tool.poetry.dependencies]
fastapi = "^0.85.0"
uvicorn = { version = "^0.20.0", extras = ["standard"] }
sqlalchemy = "^1.4.36"
python-dotenv = "^0.21.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -0,0 +1,105 @@
{
"run_id": "baseline_v1",
"steps": [
{
"step": "requirements",
"agent": "client",
"attempts": [
{
"attempt": 1,
"elapsed": 7.5,
"errors": [],
"code_length": 1196
}
],
"final_errors": [],
"passed": true
},
{
"step": "models.py",
"agent": "data",
"attempts": [
{
"attempt": 1,
"elapsed": 5.8,
"errors": [],
"code_length": 824
}
],
"final_errors": [],
"passed": true
},
{
"step": "schemas.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 4.0,
"errors": [],
"code_length": 361
}
],
"final_errors": [],
"passed": true
},
{
"step": "main.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 18.9,
"errors": [],
"code_length": 2617
}
],
"final_errors": [],
"passed": true
},
{
"step": "pyproject.toml",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 7.9,
"errors": [],
"code_length": 724
}
],
"final_errors": [],
"passed": true
},
{
"step": "test_main.py",
"agent": "qa",
"attempts": [
{
"attempt": 1,
"elapsed": 15.3,
"errors": [],
"code_length": 1689
}
],
"final_errors": [],
"passed": true
},
{
"step": "Dockerfile",
"agent": "tester",
"attempts": [
{
"attempt": 1,
"elapsed": 3.3,
"errors": [],
"code_length": 249
}
],
"final_errors": [],
"passed": true
}
],
"model": "qwen2.5-coder:7b-instruct-q4_K_M",
"summary": "7/7 passed"
}

View File

@@ -0,0 +1,34 @@
**PROJECT NAME:** Todo Manager
**GOAL:** A simple todo application for individuals to manage their tasks efficiently.
**CORE FEATURES:**
1. Create a new todo item with title and description.
2. Read (view) all todos or filter by status (e.g., completed, pending).
3. Update an existing todo item's details.
4. Delete a todo item.
5. Mark a todo as completed.
6. View the total number of todos.
7. Search for todos by partial title.
**DATA MODEL:**
1. **Todo**
- id (integer, primary key)
- title (string, required)
- description (text, optional)
- status (enum [completed, pending], default: pending)
- created_at (timestamp, auto-generated)
**API ENDPOINTS:**
1. **POST /todos**: Create a new todo item.
2. **GET /todos**: Retrieve all todos.
3. **GET /todos/status/{status}**: Filter todos by status.
4. **PUT /todos/{id}**: Update an existing todo item.
5. **DELETE /todos/{id}**: Delete a todo item.
6. **PATCH /todos/{id}/complete**: Mark a todo as completed.
7. **GET /todos/count**: Get the total number of todos.
8. **GET /todos/search?q={query}**: Search for todos by partial title.
**CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication is required.

View File

@@ -0,0 +1,16 @@
from pydantic import BaseModel, Field
from datetime import datetime
class TodoCreate(BaseModel):
title: str = Field(..., max_length=255)
description: str | None = Field(None)
class TodoResponse(BaseModel):
id: int
title: str
description: str | None
status: str
created_at: datetime
class Config:
from_attributes = True

View File

@@ -0,0 +1,59 @@
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from main import app
from models import Base
from main import get_db
engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
TestSession = sessionmaker(bind=engine)
Base.metadata.create_all(bind=engine)
def override_get_db():
db = TestSession()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
def test_create_todo():
response = client.post("/todos", json={"title": "Test Todo"})
assert response.status_code == 201
todo = response.json()
assert todo["title"] == "Test Todo"
assert todo["status"] == "pending"
def test_read_todos():
response = client.get("/todos")
assert response.status_code == 200
todos = response.json()
assert len(todos) == 1
def test_get_todo_by_id():
response = client.get("/todos/1")
assert response.status_code == 200
todo = response.json()
assert todo["id"] == 1
assert todo["title"] == "Test Todo"
def test_get_nonexistent_todo():
response = client.get("/todos/999")
assert response.status_code == 404
def test_update_todo():
response = client.put("/todos/1", json={"title": "Updated Todo"})
assert response.status_code == 200
todo = response.json()
assert todo["id"] == 1
assert todo["title"] == "Updated Todo"
def test_delete_todo():
response = client.delete("/todos/1")
assert response.status_code == 204
def test_complete_todo():
response = client.patch("/todos/1/complete")
assert response.status_code == 204