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,11 @@
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
ENV UV_CACHE_DIR=/tmp/uv-cache
WORKDIR /app
COPY pyproject.toml .
RUN uv sync
COPY *.py .
RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache
USER appuser
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,63 @@
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.orm import Session
from typing import List
from datetime import date
from models import Task, SessionLocal
from schemas import TaskCreate, TaskResponse
app = FastAPI()
# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/tasks/", response_model=TaskResponse, status_code=201)
async 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])
async def read_tasks(status: str = None, db: Session = Depends(get_db)):
if status:
tasks = db.query(Task).filter_by(status=status).all()
else:
tasks = db.query(Task).all()
return tasks
@app.get("/tasks/{task_id}", response_model=TaskResponse)
async def read_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="Task not found")
return task
@app.put("/tasks/{task_id}", response_model=TaskResponse)
async def update_task(task_id: int, task_update: TaskCreate, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
for key, value in task_update.model_dump().items():
setattr(task, key, value)
db.commit()
db.refresh(task)
return task
@app.delete("/tasks/{task_id}", status_code=204)
async def delete_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="Task not found")
db.delete(task)
db.commit()
return

View File

@@ -0,0 +1,18 @@
from sqlalchemy import create_engine, Column, Integer, String, Date
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 Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
description = Column(String(1000))
due_date = Column(Date, nullable=False)
status = Column(String(20), default="pending")
Base.metadata.create_all(bind=engine)

View File

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

View File

@@ -0,0 +1,123 @@
{
"run_id": "v8_cache_fix",
"steps": [
{
"step": "requirements",
"agent": "client",
"attempts": [
{
"attempt": 1,
"elapsed": 7.7,
"errors": [],
"code_length": 1361
}
],
"final_errors": [],
"passed": true
},
{
"step": "models.py",
"agent": "data",
"attempts": [
{
"attempt": 1,
"elapsed": 5.8,
"errors": [],
"code_length": 707
}
],
"final_errors": [],
"passed": true
},
{
"step": "schemas.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 5.2,
"errors": [],
"code_length": 464
}
],
"final_errors": [],
"passed": true
},
{
"step": "main.py",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 14.1,
"errors": [],
"code_length": 1960
}
],
"final_errors": [],
"passed": true
},
{
"step": "pyproject.toml",
"agent": "coder",
"attempts": [
{
"attempt": 1,
"elapsed": 8.8,
"errors": [
"Sisältää poetry-konfiguraation — käytä VAIN [project] (PEP 621) + uv",
"build-backend käyttää poetryä — poista tai vaihda"
],
"code_length": 815
},
{
"attempt": 2,
"elapsed": 3.6,
"errors": [],
"code_length": 311
}
],
"final_errors": [],
"passed": true
},
{
"step": "test_main.py",
"agent": "qa",
"attempts": [
{
"attempt": 1,
"elapsed": 19.8,
"errors": [],
"code_length": 2050
}
],
"final_errors": [],
"passed": true
},
{
"step": "Dockerfile",
"agent": "tester",
"attempts": [
{
"attempt": 1,
"elapsed": 4.6,
"errors": [],
"code_length": 338
}
],
"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 1):\n=============== short test summary info ============================\nFAILED test_main.py::test_create_task - sqlalchemy.exc.OperationalError: (sql...\nFAILED test_main.py::test_read_tasks - sqlalchemy.exc.OperationalError: (sqli...\nFAILED test_main.py::test_read_task - sqlalchemy.exc.OperationalError: (sqlit...\nFAILED test_main.py::test_read_task_not_found - sqlalchemy.exc.OperationalErr...\nFAILED test_main.py::test_update_task - sqlalchemy.exc.OperationalError: (sql...\nFAILED test_main.py::test_delete_task - sqlalchemy.exc.OperationalError: (sql...\n======================== 6 failed, 2 warnings in 2.50s =========================\n Network v8_cache_fix_default Creating\n Network v8_cache_fix_default Created\nwarning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.\n\n",
"POST /todos/ ja /tasks/ molemmat failasivat: HTTP Error 422: Unprocessable Entity"
]
}
}

View File

@@ -0,0 +1,32 @@
**PROJECT NAME:** TodoSovellus
**GOAL:** Create a simple task management application for individuals to keep track of their daily tasks and manage them efficiently.
**CORE FEATURES:**
1. **Add Task**: Users can add new tasks with a title, description, due date, and status (e.g., pending, completed).
2. **View Tasks**: Users can view all tasks or filter by status (pending, completed).
3. **Edit Task**: Users can update the details of an existing task.
4. **Delete Task**: Users can remove tasks that are no longer needed.
5. **Mark as Completed**: Users can mark a task as completed.
6. **Filter Tasks**: Users can filter tasks based on their status (pending, completed).
7. **Task Details**: Users can view detailed information about each task.
**DATA MODEL:**
1. **Tasks**
- id (integer, primary key)
- title (string)
- description (text)
- due_date (date)
- status (enum: pending, completed)
**API ENDPOINTS:**
1. **GET /tasks**: Retrieve all tasks.
2. **POST /tasks**: Create a new task.
3. **GET /tasks/{id}**: Retrieve details of a specific task by ID.
4. **PUT /tasks/{id}**: Update an existing task.
5. **DELETE /tasks/{id}**: Delete a task.
6. **GET /tasks/status/{status}**: Filter tasks by status (pending, completed).
**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 TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=255)
description: str = Field(None, max_length=1000)
due_date: date = Field(...)
status: str = Field("pending", min_length=1, max_length=20)
class TaskResponse(BaseModel):
id: int
title: str
description: str
due_date: date
status: str
class Config:
from_attributes = True

View File

@@ -0,0 +1,56 @@
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_task():
response = client.post("/tasks/", json={"title": "Test Task", "due_date": "2023-12-31"})
assert response.status_code == 201
assert response.json()["title"] == "Test Task"
assert response.json()["status"] == "pending"
def test_read_tasks():
client.post("/tasks/", json={"title": "Task 1", "due_date": "2023-12-31"})
client.post("/tasks/", json={"title": "Task 2", "due_date": "2024-01-01"})
response = client.get("/tasks/")
assert response.status_code == 200
assert len(response.json()) == 2
def test_read_task():
task = client.post("/tasks/", json={"title": "Test Task", "due_date": "2023-12-31"}).json()
response = client.get(f"/tasks/{task['id']}")
assert response.status_code == 200
assert response.json()["id"] == task["id"]
def test_read_task_not_found():
response = client.get("/tasks/999")
assert response.status_code == 404
def test_update_task():
task = client.post("/tasks/", json={"title": "Test Task", "due_date": "2023-12-31"}).json()
response = client.put(f"/tasks/{task['id']}", json={"status": "completed"})
assert response.status_code == 200
assert response.json()["status"] == "completed"
def test_delete_task():
task = client.post("/tasks/", json={"title": "Test Task", "due_date": "2023-12-31"}).json()
response = client.delete(f"/tasks/{task['id']}")
assert response.status_code == 204
response = client.get(f"/tasks/{task['id']}")
assert response.status_code == 404