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,10 @@
services:
app:
build: .
ports:
- "18765:8000"
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/docs')"]
interval: 3s
timeout: 5s
retries: 5

View File

@@ -0,0 +1,64 @@
from fastapi import FastAPI, HTTPException, Depends, Query
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import date
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/{todo_id}", response_model=TodoResponse)
async def read_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="Todo not found")
return todo
@app.put("/todos/{todo_id}", response_model=TodoResponse)
async def update_todo(todo_id: int, todo_update: TodoCreate, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Todo not found")
todo.title = todo_update.title
todo.description = todo_update.description
todo.due_date = todo_update.due_date
todo.status = todo_update.status
db.commit()
db.refresh(todo)
return todo
@app.delete("/todos/{todo_id}", status_code=204)
async def delete_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="Todo not found")
db.delete(todo)
db.commit()

View File

@@ -0,0 +1,18 @@
from sqlalchemy import create_engine, Column, Integer, String, Text, Date, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./todos.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(255), nullable=False)
description = Column(Text, nullable=False)
due_date = Column(Date, nullable=False)
status = Column(Enum("pending", "completed"), nullable=False)
Base.metadata.create_all(bind=engine)

View File

@@ -0,0 +1,16 @@
[project]
name = "todo-app"
version = "0.1.0"
description = "A simple todo app using FastAPI and SQLAlchemy"
authors = [
{ name="Your Name", email="your.email@example.com" }
]
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy",
"pytest",
"httpx",
"python-dateutil"
]

View File

@@ -0,0 +1,134 @@
{
"run_id": "v3_docker",
"steps": [
{
"step": "requirements",
"agent": "client",
"attempts": [
{
"attempt": 1,
"elapsed": 6.2,
"errors": [],
"code_length": 1105
}
],
"final_errors": [],
"passed": true
},
{
"step": "models.py",
"agent": "data",
"attempts": [
{
"attempt": 1,
"elapsed": 5.4,
"errors": [
"SQLite create_engine puuttuu connect_args={'check_same_thread': False}"
],
"code_length": 672
},
{
"attempt": 2,
"elapsed": 4.6,
"errors": [],
"code_length": 742
}
],
"final_errors": [],
"passed": true
},
{
"step": "schemas.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 4.2,
"errors": [],
"code_length": 370
}
],
"final_errors": [],
"passed": true
},
{
"step": "main.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 14.9,
"errors": [
"Kutsutaan 'Depends()' mutta sitä ei ole importattu eikä määritelty",
"Kutsutaan 'Depends()' mutta sitä ei ole importattu eikä määritelty",
"Kutsutaan 'Depends()' mutta sitä ei ole importattu eikä määritelty",
"Kutsutaan 'Depends()' mutta sitä ei ole importattu eikä määritelty",
"Kutsutaan 'Depends()' mutta sitä ei ole importattu eikä määritelty"
],
"code_length": 1975
},
{
"attempt": 2,
"elapsed": 14.4,
"errors": [],
"code_length": 2058
}
],
"final_errors": [],
"passed": true
},
{
"step": "pyproject.toml",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 3.6,
"errors": [],
"code_length": 279
}
],
"final_errors": [],
"passed": true
},
{
"step": "test_main.py",
"agent": "qa",
"attempts": [
{
"attempt": 1,
"elapsed": 21.1,
"errors": [],
"code_length": 2415
}
],
"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",
"docker": {
"build": true,
"pytest": false,
"api": false,
"errors": [
"pytest failed (exit 2):\n Network v3_docker_default Creating\n Network v3_docker_default Created\nerror: Failed to initialize cache at `/.cache/uv`\n Caused by: failed to create directory `/.cache/uv`: Permission denied (os error 13)\n\n",
"Kontti ei käynnistynyt 15s:ssa"
]
}
}

View File

@@ -0,0 +1,30 @@
**PROJECT NAME:** TodoSovellus
**GOAL:** To create a simple task management application for individuals to keep track of their daily tasks and manage them efficiently.
**CORE FEATURES:**
1. Create new todos with title, description, due date, and status.
2. Read (view) all todos and filter by status (e.g., pending, completed).
3. Update existing todos to change details like title, description, due date, or status.
4. Delete todos that are no longer needed.
5. Mark todos as completed or pending.
6. View the total number of todos and count by status.
**DATA MODEL:**
1. **Todo**
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: "pending", "completed")
**API ENDPOINTS:**
1. **POST /todos** - Create a new todo
2. **GET /todos** - Retrieve all todos, optionally filter by status
3. **GET /todos/{id}** - Retrieve a single todo by ID
4. **PUT /todos/{id}** - Update an existing todo
5. **DELETE /todos/{id}** - Delete a todo
**CONSTRAINTS:**
- Must use SQLite as the database.
- No authentication is required for accessing endpoints.

View File

@@ -0,0 +1,18 @@
from pydantic import BaseModel, Field
from datetime import date
class TodoCreate(BaseModel):
title: str = Field(..., max_length=255)
description: str
due_date: date
status: str = "pending"
class TodoResponse(BaseModel):
id: int
title: str
description: str
due_date: date
status: str
class Config:
from_attributes = True

View File

@@ -0,0 +1,57 @@
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", "description": "This is a test todo.", "due_date": "2023-12-31"})
assert response.status_code == 201
assert response.json()["title"] == "Test Todo"
assert response.json()["status"] == "pending"
def test_read_todos():
client.post("/todos", json={"title": "Todo 1", "description": "First todo.", "due_date": "2023-12-31"})
client.post("/todos", json={"title": "Todo 2", "description": "Second todo.", "due_date": "2024-01-01"})
response = client.get("/todos")
assert response.status_code == 200
assert len(response.json()) == 2
def test_read_todo():
todo = client.post("/todos", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"})
response = client.get(f"/todos/{todo.json()['id']}")
assert response.status_code == 200
assert response.json()["title"] == "Test Todo"
def test_read_todo_not_found():
response = client.get("/todos/999")
assert response.status_code == 404
def test_update_todo():
todo = client.post("/todos", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"})
response = client.put(f"/todos/{todo.json()['id']}", json={"title": "Updated Todo", "description": "Updated description.", "due_date": "2024-01-01", "status": "completed"})
assert response.status_code == 200
assert response.json()["title"] == "Updated Todo"
assert response.json()["status"] == "completed"
def test_delete_todo():
todo = client.post("/todos", json={"title": "Test Todo", "description": "This is a test todo.", "due_date": "2023-12-31"})
response = client.delete(f"/todos/{todo.json()['id']}")
assert response.status_code == 204
response = client.get(f"/todos/{todo.json()['id']}")
assert response.status_code == 404