diff --git a/.gitignore b/.gitignore index ac527eb..fc6be24 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,7 @@ Cargo.lock *.log # Wanha versio -temp/ \ No newline at end of file +temp/ + +# Muut +zipit/** \ No newline at end of file diff --git a/projektit/projektiopinnot-1-datan-hallinta-ttm23sai b/projektit/projektiopinnot-1-datan-hallinta-ttm23sai new file mode 160000 index 0000000..c20e918 --- /dev/null +++ b/projektit/projektiopinnot-1-datan-hallinta-ttm23sai @@ -0,0 +1 @@ +Subproject commit c20e918b34001511bb85db363c4e6bdeb6e83144 diff --git a/zipit/loop_runs/baseline_v1/Dockerfile b/zipit/loop_runs/baseline_v1/Dockerfile deleted file mode 100644 index 24a4454..0000000 --- a/zipit/loop_runs/baseline_v1/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -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"] \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/main.py b/zipit/loop_runs/baseline_v1/main.py deleted file mode 100644 index ab66691..0000000 --- a/zipit/loop_runs/baseline_v1/main.py +++ /dev/null @@ -1,76 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/models.py b/zipit/loop_runs/baseline_v1/models.py deleted file mode 100644 index 693708c..0000000 --- a/zipit/loop_runs/baseline_v1/models.py +++ /dev/null @@ -1,23 +0,0 @@ -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) \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/pyproject.toml b/zipit/loop_runs/baseline_v1/pyproject.toml deleted file mode 100644 index 3f2981c..0000000 --- a/zipit/loop_runs/baseline_v1/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[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" \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/report.json b/zipit/loop_runs/baseline_v1/report.json deleted file mode 100644 index 54cc724..0000000 --- a/zipit/loop_runs/baseline_v1/report.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "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" -} \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/requirements b/zipit/loop_runs/baseline_v1/requirements deleted file mode 100644 index 42a4380..0000000 --- a/zipit/loop_runs/baseline_v1/requirements +++ /dev/null @@ -1,34 +0,0 @@ -**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. \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/schemas.py b/zipit/loop_runs/baseline_v1/schemas.py deleted file mode 100644 index 90d1bb3..0000000 --- a/zipit/loop_runs/baseline_v1/schemas.py +++ /dev/null @@ -1,16 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/baseline_v1/test_main.py b/zipit/loop_runs/baseline_v1/test_main.py deleted file mode 100644 index b1d4f70..0000000 --- a/zipit/loop_runs/baseline_v1/test_main.py +++ /dev/null @@ -1,59 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/Dockerfile b/zipit/loop_runs/v2_strict/Dockerfile deleted file mode 100644 index a49e703..0000000 --- a/zipit/loop_runs/v2_strict/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -```Dockerfile -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 models.py schemas.py main.py test_main.py . - -USER nonrootuser - -EXPOSE 8000 - -CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] -``` \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/main.py b/zipit/loop_runs/v2_strict/main.py deleted file mode 100644 index 5f0d128..0000000 --- a/zipit/loop_runs/v2_strict/main.py +++ /dev/null @@ -1,64 +0,0 @@ -from fastapi import FastAPI, HTTPException, Depends, Query -from sqlalchemy.orm import Session -from typing import List, Optional -from datetime import date - -from models import Task, engine -from schemas import TaskCreate, TaskResponse - -app = FastAPI() - -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -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 get_tasks(status: Optional[str] = Query(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 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="Task not found") - return task - -@app.put("/tasks/{task_id}", response_model=TaskResponse) -async 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="Task 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) -async 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="Task not found") - - db.delete(db_task) - db.commit() - return \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/models.py b/zipit/loop_runs/v2_strict/models.py deleted file mode 100644 index 5018450..0000000 --- a/zipit/loop_runs/v2_strict/models.py +++ /dev/null @@ -1,18 +0,0 @@ -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:///./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(255), nullable=False) - description = Column(Text) - due_date = Column(Date) - status = Column(Enum('pending', 'completed'), default='pending') - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/pyproject.toml b/zipit/loop_runs/v2_strict/pyproject.toml deleted file mode 100644 index 64b1e2b..0000000 --- a/zipit/loop_runs/v2_strict/pyproject.toml +++ /dev/null @@ -1,13 +0,0 @@ -[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" -] \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/report.json b/zipit/loop_runs/v2_strict/report.json deleted file mode 100644 index 80884cb..0000000 --- a/zipit/loop_runs/v2_strict/report.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "run_id": "v2_strict", - "steps": [ - { - "step": "requirements", - "agent": "client", - "attempts": [ - { - "attempt": 1, - "elapsed": 6.6, - "errors": [], - "code_length": 1190 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "models.py", - "agent": "data", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.3, - "errors": [ - "SQLite create_engine puuttuu connect_args={'check_same_thread': False}" - ], - "code_length": 699 - }, - { - "attempt": 2, - "elapsed": 4.4, - "errors": [], - "code_length": 712 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "schemas.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.2, - "errors": [], - "code_length": 494 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "main.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 14.4, - "errors": [], - "code_length": 2036 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "pyproject.toml", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 3.3, - "errors": [], - "code_length": 267 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "test_main.py", - "agent": "qa", - "attempts": [ - { - "attempt": 1, - "elapsed": 16.4, - "errors": [], - "code_length": 2071 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "Dockerfile", - "agent": "tester", - "attempts": [ - { - "attempt": 1, - "elapsed": 3.5, - "errors": [], - "code_length": 314 - } - ], - "final_errors": [], - "passed": true - } - ], - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "summary": "7/7 passed" -} \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/requirements b/zipit/loop_runs/v2_strict/requirements deleted file mode 100644 index 0b983ac..0000000 --- a/zipit/loop_runs/v2_strict/requirements +++ /dev/null @@ -1,32 +0,0 @@ -**PROJECT NAME:** Todo-sovellus - -**GOAL:** Create a simple task management application for individuals to keep track of their daily tasks and manage them efficiently. - -**CORE FEATURES:** -1. Add new tasks with title, description, due date, and status. -2. View all tasks in a list format. -3. Filter tasks by status (e.g., pending, completed). -4. Update task details such as title, description, due date, and status. -5. Delete individual tasks. -6. Mark tasks as complete or incomplete. -7. Search for tasks by keywords in the title or description. - -**DATA MODEL:** -1. **Task** - - id (integer, primary key) - - title (string, required) - - description (text, optional) - - due_date (date, optional) - - status (enum ['pending', 'completed'], default='pending') - -**API ENDPOINTS:** -1. **GET /tasks** - Retrieve all tasks. -2. **POST /tasks** - Create a new task. -3. **GET /tasks/{id}** - Retrieve a specific task by ID. -4. **PUT /tasks/{id}** - Update an existing task. -5. **DELETE /tasks/{id}** - Delete a specific task. -6. **GET /tasks/status/{status}** - Filter tasks by status. - -**CONSTRAINTS:** -- Must use SQLite as the database. -- No authentication is required for accessing endpoints. \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/schemas.py b/zipit/loop_runs/v2_strict/schemas.py deleted file mode 100644 index 2074e86..0000000 --- a/zipit/loop_runs/v2_strict/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -from pydantic import BaseModel, Field -from datetime import date - -class TaskCreate(BaseModel): - title: str = Field(..., min_length=1, max_length=255) - description: str | None = Field(None, max_length=255) - due_date: date | None = Field(None) - status: str = Field('pending', regex='^(pending|completed)$') - -class TaskResponse(BaseModel): - id: int - title: str - description: str | None - due_date: date | None - status: str - - class Config: - from_attributes = True \ No newline at end of file diff --git a/zipit/loop_runs/v2_strict/test_main.py b/zipit/loop_runs/v2_strict/test_main.py deleted file mode 100644 index 20ff061..0000000 --- a/zipit/loop_runs/v2_strict/test_main.py +++ /dev/null @@ -1,57 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app -from models import Base - -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", "description": "This is a test task"}) - assert response.status_code == 201 - assert response.json()["title"] == "Test Task" - assert response.json()["description"] == "This is a test task" - -def test_get_tasks(): - response = client.get("/tasks/") - assert response.status_code == 200 - assert len(response.json()) > 0 - -def test_get_task_by_id(): - response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - task_id = response.json()["id"] - response = client.get(f"/tasks/{task_id}") - assert response.status_code == 200 - assert response.json()["id"] == task_id - -def test_get_task_by_id_not_found(): - response = client.get("/tasks/999") - assert response.status_code == 404 - -def test_update_task(): - response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - task_id = response.json()["id"] - updated_data = {"title": "Updated Test Task"} - response = client.put(f"/tasks/{task_id}", json=updated_data) - assert response.status_code == 200 - assert response.json()["title"] == "Updated Test Task" - -def test_delete_task(): - response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - 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 \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/Dockerfile b/zipit/loop_runs/v3_docker/Dockerfile deleted file mode 100644 index 24a4454..0000000 --- a/zipit/loop_runs/v3_docker/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -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"] \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/docker-compose.yml b/zipit/loop_runs/v3_docker/docker-compose.yml deleted file mode 100644 index 72e6f5d..0000000 --- a/zipit/loop_runs/v3_docker/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -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 diff --git a/zipit/loop_runs/v3_docker/main.py b/zipit/loop_runs/v3_docker/main.py deleted file mode 100644 index 2e77983..0000000 --- a/zipit/loop_runs/v3_docker/main.py +++ /dev/null @@ -1,64 +0,0 @@ -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() \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/models.py b/zipit/loop_runs/v3_docker/models.py deleted file mode 100644 index 438cd35..0000000 --- a/zipit/loop_runs/v3_docker/models.py +++ /dev/null @@ -1,18 +0,0 @@ -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) \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/pyproject.toml b/zipit/loop_runs/v3_docker/pyproject.toml deleted file mode 100644 index 511617c..0000000 --- a/zipit/loop_runs/v3_docker/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[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" -] \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/report.json b/zipit/loop_runs/v3_docker/report.json deleted file mode 100644 index fa00961..0000000 --- a/zipit/loop_runs/v3_docker/report.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "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" - ] - } -} \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/requirements b/zipit/loop_runs/v3_docker/requirements deleted file mode 100644 index 722ff0f..0000000 --- a/zipit/loop_runs/v3_docker/requirements +++ /dev/null @@ -1,30 +0,0 @@ -**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. \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/schemas.py b/zipit/loop_runs/v3_docker/schemas.py deleted file mode 100644 index 8bbcdf6..0000000 --- a/zipit/loop_runs/v3_docker/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v3_docker/test_main.py b/zipit/loop_runs/v3_docker/test_main.py deleted file mode 100644 index e5a5f53..0000000 --- a/zipit/loop_runs/v3_docker/test_main.py +++ /dev/null @@ -1,57 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/Dockerfile b/zipit/loop_runs/v4_docker/Dockerfile deleted file mode 100644 index 4ab404a..0000000 --- a/zipit/loop_runs/v4_docker/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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 -USER appuser -EXPOSE 8000 -CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/main.py b/zipit/loop_runs/v4_docker/main.py deleted file mode 100644 index 017617f..0000000 --- a/zipit/loop_runs/v4_docker/main.py +++ /dev/null @@ -1,58 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from typing import List, Optional - -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) -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 read_todos(status: Optional[str] = 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) -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) -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="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/{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="Todo not found") - db.delete(db_todo) - db.commit() \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/models.py b/zipit/loop_runs/v4_docker/models.py deleted file mode 100644 index b715d91..0000000 --- a/zipit/loop_runs/v4_docker/models.py +++ /dev/null @@ -1,18 +0,0 @@ -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=True) - due_date = Column(Date, nullable=True) - status = Column(Enum('pending', 'completed'), nullable=False) - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/pyproject.toml b/zipit/loop_runs/v4_docker/pyproject.toml deleted file mode 100644 index 5cd25d0..0000000 --- a/zipit/loop_runs/v4_docker/pyproject.toml +++ /dev/null @@ -1,13 +0,0 @@ -[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" -] \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/report.json b/zipit/loop_runs/v4_docker/report.json deleted file mode 100644 index edbbe10..0000000 --- a/zipit/loop_runs/v4_docker/report.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "run_id": "v4_docker", - "steps": [ - { - "step": "requirements", - "agent": "client", - "attempts": [ - { - "attempt": 1, - "elapsed": 6.6, - "errors": [], - "code_length": 1106 - } - ], - "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": 696 - }, - { - "attempt": 2, - "elapsed": 4.7, - "errors": [], - "code_length": 740 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "schemas.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.1, - "errors": [ - "SyntaxError: invalid syntax (rivi 8)" - ], - "code_length": 472 - }, - { - "attempt": 2, - "elapsed": 2.9, - "errors": [ - "SyntaxError: invalid syntax (rivi 8)" - ], - "code_length": 372 - }, - { - "attempt": 3, - "elapsed": 3.5, - "errors": [ - "SyntaxError: invalid syntax (rivi 8)" - ], - "code_length": 472 - } - ], - "final_errors": [ - "SyntaxError: invalid syntax (rivi 8)" - ], - "passed": false - }, - { - "step": "main.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 14.6, - "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": 1984 - }, - { - "attempt": 2, - "elapsed": 14.0, - "errors": [], - "code_length": 1911 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "pyproject.toml", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 3.6, - "errors": [], - "code_length": 264 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "test_main.py", - "agent": "qa", - "attempts": [ - { - "attempt": 1, - "elapsed": 15.9, - "errors": [], - "code_length": 1779 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "Dockerfile", - "agent": "tester", - "attempts": [ - { - "attempt": 1, - "elapsed": 4.0, - "errors": [], - "code_length": 291 - } - ], - "final_errors": [], - "passed": true - } - ], - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "summary": "6/7 passed" -} \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/requirements b/zipit/loop_runs/v4_docker/requirements deleted file mode 100644 index 3a146c5..0000000 --- a/zipit/loop_runs/v4_docker/requirements +++ /dev/null @@ -1,30 +0,0 @@ -**PROJECT NAME:** Todo-sovellus - -**GOAL:** The software is a simple task manager 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 or filter by status (e.g., pending, completed). -3. Update existing todos (change title, description, due date, status). -4. Delete todos that are no longer needed. -5. Mark a todo as complete. -6. View the total number of todos and count by status. - -**DATA MODEL:** -1. **Todo** - - id (integer, primary key) - - title (string, required) - - description (text, optional) - - due_date (date, optional) - - status (enum: 'pending', 'completed') - -**API ENDPOINTS:** -1. **GET /todos** - Retrieve all todos. -2. **GET /todos?status=...** - Filter todos by status (e.g., `/todos?status=pending`). -3. **POST /todos** - Create a new todo. -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. \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/schemas.py b/zipit/loop_runs/v4_docker/schemas.py deleted file mode 100644 index 5c54db7..0000000 --- a/zipit/loop_runs/v4_docker/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -from pydantic import BaseModel, Field -from datetime import date - -class TodoCreate(BaseModel): - title: str = Field(..., max_length=255) - description: str | None = Field(None, max_length=255) - due_date: date | None = Field(None) - status: str = Field(..., in=["pending", "completed"]) - -class TodoResponse(BaseModel): - id: int - title: str - description: str | None - due_date: date | None - status: str - - class Config: - from_attributes = True \ No newline at end of file diff --git a/zipit/loop_runs/v4_docker/test_main.py b/zipit/loop_runs/v4_docker/test_main.py deleted file mode 100644 index feaad4e..0000000 --- a/zipit/loop_runs/v4_docker/test_main.py +++ /dev/null @@ -1,59 +0,0 @@ -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", "status": "pending"}) - 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" - assert todo["status"] == "pending" - -def test_get_todo_by_id_not_found(): - response = client.get("/todos/999") - assert response.status_code == 404 - -def test_update_todo(): - response = client.put("/todos/1", json={"title": "Updated Todo", "status": "completed"}) - assert response.status_code == 200 - todo = response.json() - assert todo["id"] == 1 - assert todo["title"] == "Updated Todo" - assert todo["status"] == "completed" - -def test_delete_todo(): - response = client.delete("/todos/1") - assert response.status_code == 204 - response = client.get("/todos/1") - assert response.status_code == 404 \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/Dockerfile b/zipit/loop_runs/v5_feedback/Dockerfile deleted file mode 100644 index 4ab404a..0000000 --- a/zipit/loop_runs/v5_feedback/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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 -USER appuser -EXPOSE 8000 -CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/main.py b/zipit/loop_runs/v5_feedback/main.py deleted file mode 100644 index e623a01..0000000 --- a/zipit/loop_runs/v5_feedback/main.py +++ /dev/null @@ -1,74 +0,0 @@ -from fastapi import FastAPI, HTTPException, Depends, Query -from sqlalchemy.orm import Session -from typing import List, Optional -from pydantic.v1 import model_dump - -from models import Task, User, Base, engine -from schemas import TaskCreate, TaskResponse -from sqlalchemy.exc import SQLAlchemyError - -app = FastAPI() - -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(**model_dump(task)) - db.add(db_task) - try: - db.commit() - db.refresh(db_task) - except SQLAlchemyError as e: - raise HTTPException(status_code=400, detail=str(e)) - return db_task - -@app.get("/tasks/", response_model=List[TaskResponse]) -async def read_tasks( - status: Optional[str] = Query(None), - db: Session = Depends(get_db) -): - query = db.query(Task) - if status: - query = query.filter(Task.status == status) - tasks = query.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: 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="Task not found") - - for key, value in model_dump(task).items(): - setattr(db_task, key, value) - - try: - db.commit() - db.refresh(db_task) - except SQLAlchemyError as e: - raise HTTPException(status_code=400, detail=str(e)) - return db_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") - - try: - db.delete(task) - db.commit() - except SQLAlchemyError as e: - raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/models.py b/zipit/loop_runs/v5_feedback/models.py deleted file mode 100644 index b096259..0000000 --- a/zipit/loop_runs/v5_feedback/models.py +++ /dev/null @@ -1,25 +0,0 @@ -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:///./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(255), nullable=False) - description = Column(Text, nullable=True) - due_date = Column(Date, nullable=True) - status = Column(Enum('pending', 'completed'), default='pending') - -class User(Base): - __tablename__ = "users" - id = Column(Integer, primary_key=True, index=True) - username = Column(String(255), unique=True, nullable=False) - email = Column(String(255), unique=True, nullable=False) - password_hash = Column(String(255), nullable=False) - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/pyproject.toml b/zipit/loop_runs/v5_feedback/pyproject.toml deleted file mode 100644 index 5f9576c..0000000 --- a/zipit/loop_runs/v5_feedback/pyproject.toml +++ /dev/null @@ -1,13 +0,0 @@ -[project] -name = "todo-app" -version = "0.1.0" -description = "A simple task management API" -authors = [ - { name="Your Name", email="your.email@example.com" } -] - -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy" -] \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/report.json b/zipit/loop_runs/v5_feedback/report.json deleted file mode 100644 index dbaae68..0000000 --- a/zipit/loop_runs/v5_feedback/report.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "run_id": "v5_feedback", - "steps": [ - { - "step": "requirements", - "agent": "client", - "attempts": [ - { - "attempt": 1, - "elapsed": 9.1, - "errors": [], - "code_length": 1560 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "models.py", - "agent": "data", - "attempts": [ - { - "attempt": 1, - "elapsed": 7.5, - "errors": [ - "SQLite create_engine puuttuu connect_args={'check_same_thread': False}" - ], - "code_length": 982 - }, - { - "attempt": 2, - "elapsed": 7.6, - "errors": [], - "code_length": 1025 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "schemas.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 6.2, - "errors": [ - "Schema puuttuu kenttiä jotka ovat modelissa: {'password_hash', 'email', 'username'}" - ], - "code_length": 494 - }, - { - "attempt": 2, - "elapsed": 4.6, - "errors": [ - "Schema puuttuu kenttiä jotka ovat modelissa: {'password_hash', 'email', 'username'}" - ], - "code_length": 494 - }, - { - "attempt": 3, - "elapsed": 4.0, - "errors": [ - "Schema puuttuu kenttiä jotka ovat modelissa: {'password_hash', 'email', 'username'}" - ], - "code_length": 494 - } - ], - "final_errors": [ - "Schema puuttuu kenttiä jotka ovat modelissa: {'password_hash', 'email', 'username'}" - ], - "passed": false - }, - { - "step": "main.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 18.9, - "errors": [ - "Kutsutaan 'create_engine()' mutta sitä ei ole importattu eikä määritelty", - "Kutsutaan 'sessionmaker()' mutta sitä ei ole importattu eikä määritelty" - ], - "code_length": 2531 - }, - { - "attempt": 2, - "elapsed": 18.7, - "errors": [ - "Kutsutaan 'SessionLocal()' mutta sitä ei ole importattu eikä määritelty" - ], - "code_length": 2337 - }, - { - "attempt": 3, - "elapsed": 18.4, - "errors": [ - "Kutsutaan 'SessionLocal()' mutta sitä ei ole importattu eikä määritelty" - ], - "code_length": 2337 - } - ], - "final_errors": [ - "Kutsutaan 'SessionLocal()' mutta sitä ei ole importattu eikä määritelty" - ], - "passed": false - }, - { - "step": "pyproject.toml", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 7.4, - "errors": [ - "Sisältää poetry-konfiguraation — käytä VAIN [project] (PEP 621) + uv", - "build-backend käyttää poetryä — poista tai vaihda" - ], - "code_length": 647 - }, - { - "attempt": 2, - "elapsed": 2.9, - "errors": [], - "code_length": 238 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "test_main.py", - "agent": "qa", - "attempts": [ - { - "attempt": 1, - "elapsed": 14.1, - "errors": [], - "code_length": 1567 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "Dockerfile", - "agent": "tester", - "attempts": [ - { - "attempt": 1, - "elapsed": 4.1, - "errors": [], - "code_length": 291 - } - ], - "final_errors": [], - "passed": true - } - ], - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "summary": "5/7 passed" -} \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/requirements b/zipit/loop_runs/v5_feedback/requirements deleted file mode 100644 index 3618630..0000000 --- a/zipit/loop_runs/v5_feedback/requirements +++ /dev/null @@ -1,40 +0,0 @@ -**PROJECT NAME:** Todo-sovellus - -**GOAL:** Create a simple task management application for individuals to keep track of their daily tasks and manage them efficiently. - -**CORE FEATURES:** -1. Add new tasks with title, description, due date, and status. -2. View all tasks with filtering by status (e.g., pending, completed). -3. Update existing tasks including changing the title, description, due date, and status. -4. Delete tasks that are no longer needed. -5. Mark tasks as completed or pending. -6. Search for tasks by keywords in the title or description. -7. Export task list to a CSV file. - -**DATA MODEL:** -1. **Task** - - id (integer, primary key) - - title (string, required) - - description (text, optional) - - due_date (date, optional) - - status (enum ['pending', 'completed'], default 'pending') - -2. **User** - - id (integer, primary key) - - username (string, unique, required) - - email (string, unique, required) - - password_hash (string, required) - -**API ENDPOINTS:** -1. **GET /tasks** - Retrieve all tasks with optional filters by status. -2. **POST /tasks** - Create a new task. -3. **GET /tasks/{task_id}** - Retrieve a specific task by ID. -4. **PUT /tasks/{task_id}** - Update an existing task. -5. **DELETE /tasks/{task_id}** - Delete a task. -6. **POST /users/register** - Register a new user. -7. **POST /users/login** - Authenticate and return a token for authorized access. - -**CONSTRAINTS:** -- Must use SQLite as the database. -- No authentication is required for accessing CRUD operations. -- Implement unit tests for all API endpoints. \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/schemas.py b/zipit/loop_runs/v5_feedback/schemas.py deleted file mode 100644 index 2074e86..0000000 --- a/zipit/loop_runs/v5_feedback/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -from pydantic import BaseModel, Field -from datetime import date - -class TaskCreate(BaseModel): - title: str = Field(..., min_length=1, max_length=255) - description: str | None = Field(None, max_length=255) - due_date: date | None = Field(None) - status: str = Field('pending', regex='^(pending|completed)$') - -class TaskResponse(BaseModel): - id: int - title: str - description: str | None - due_date: date | None - status: str - - class Config: - from_attributes = True \ No newline at end of file diff --git a/zipit/loop_runs/v5_feedback/test_main.py b/zipit/loop_runs/v5_feedback/test_main.py deleted file mode 100644 index 31df6e9..0000000 --- a/zipit/loop_runs/v5_feedback/test_main.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app -from models import Base - -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", "description": "This is a test task"}) - assert response.status_code == 201 - assert response.json()["title"] == "Test Task" - assert response.json()["description"] == "This is a test task" - -def test_read_tasks(): - response = client.get("/tasks/") - assert response.status_code == 200 - assert len(response.json()) == 1 - -def test_get_task_by_id(): - response = client.get("/tasks/1") - assert response.status_code == 200 - assert response.json()["id"] == 1 - -def test_get_task_by_id_not_found(): - response = client.get("/tasks/999") - assert response.status_code == 404 - -def test_update_task(): - response = client.put("/tasks/1", json={"title": "Updated Task"}) - assert response.status_code == 200 - assert response.json()["title"] == "Updated Task" - -def test_delete_task(): - response = client.delete("/tasks/1") - assert response.status_code == 204 - - response = client.get("/tasks/1") - assert response.status_code == 404 \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/Dockerfile b/zipit/loop_runs/v6_stable/Dockerfile deleted file mode 100644 index 4ab404a..0000000 --- a/zipit/loop_runs/v6_stable/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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 -USER appuser -EXPOSE 8000 -CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/main.py b/zipit/loop_runs/v6_stable/main.py deleted file mode 100644 index 3585d53..0000000 --- a/zipit/loop_runs/v6_stable/main.py +++ /dev/null @@ -1,62 +0,0 @@ -from fastapi import FastAPI, HTTPException, Depends -from sqlalchemy.orm import Session -from typing import List - -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) -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 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) -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) -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="Task 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="Task not found") - - db.delete(db_task) - db.commit() - return \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/models.py b/zipit/loop_runs/v6_stable/models.py deleted file mode 100644 index 91b8624..0000000 --- a/zipit/loop_runs/v6_stable/models.py +++ /dev/null @@ -1,23 +0,0 @@ -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:///./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 TaskStatus(Enum): - pending = 'pending' - completed = 'completed' - -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) - status = Column(Enum(TaskStatus), default=TaskStatus.pending) - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/pyproject.toml b/zipit/loop_runs/v6_stable/pyproject.toml deleted file mode 100644 index 79c49ac..0000000 --- a/zipit/loop_runs/v6_stable/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -[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", - "python-dotenv", - "pydantic" -] \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/report.json b/zipit/loop_runs/v6_stable/report.json deleted file mode 100644 index fe147d9..0000000 --- a/zipit/loop_runs/v6_stable/report.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "run_id": "v6_stable", - "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.9, - "errors": [ - "SQLite create_engine puuttuu connect_args={'check_same_thread': False}", - "Luokka 'TaskStatus' perii Enum:in mutta 'from enum import Enum' puuttuu (SQLAlchemy Enum ei ole Python Enum)" - ], - "code_length": 751 - }, - { - "attempt": 2, - "elapsed": 5.6, - "errors": [ - "Luokka 'TaskStatus' perii Enum:in mutta 'from enum import Enum' puuttuu (SQLAlchemy Enum ei ole Python Enum)" - ], - "code_length": 794 - }, - { - "attempt": 3, - "elapsed": 5.5, - "errors": [ - "Luokka 'TaskStatus' perii Enum:in mutta 'from enum import Enum' puuttuu (SQLAlchemy Enum ei ole Python Enum)" - ], - "code_length": 794 - } - ], - "final_errors": [ - "Luokka 'TaskStatus' perii Enum:in mutta 'from enum import Enum' puuttuu (SQLAlchemy Enum ei ole Python Enum)" - ], - "passed": false - }, - { - "step": "schemas.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.4, - "errors": [], - "code_length": 481 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "main.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 14.1, - "errors": [], - "code_length": 1914 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "pyproject.toml", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 3.6, - "errors": [], - "code_length": 304 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "test_main.py", - "agent": "qa", - "attempts": [ - { - "attempt": 1, - "elapsed": 16.9, - "errors": [], - "code_length": 2108 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "Dockerfile", - "agent": "tester", - "attempts": [ - { - "attempt": 1, - "elapsed": 4.0, - "errors": [], - "code_length": 291 - } - ], - "final_errors": [], - "passed": true - } - ], - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "summary": "6/7 passed" -} \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/requirements b/zipit/loop_runs/v6_stable/requirements deleted file mode 100644 index 222f517..0000000 --- a/zipit/loop_runs/v6_stable/requirements +++ /dev/null @@ -1,32 +0,0 @@ -**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. \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/schemas.py b/zipit/loop_runs/v6_stable/schemas.py deleted file mode 100644 index 9825bec..0000000 --- a/zipit/loop_runs/v6_stable/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -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, min_length=0, max_length=1000) - due_date: date = Field(None) - status: str = Field('pending', regex='^(pending|completed)$') - -class TaskResponse(BaseModel): - id: int - title: str - description: str - due_date: date - status: str - - class Config: - from_attributes = True \ No newline at end of file diff --git a/zipit/loop_runs/v6_stable/test_main.py b/zipit/loop_runs/v6_stable/test_main.py deleted file mode 100644 index f0b4349..0000000 --- a/zipit/loop_runs/v6_stable/test_main.py +++ /dev/null @@ -1,58 +0,0 @@ -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", "description": "This is a test task"}) - assert response.status_code == 201 - assert response.json()["title"] == "Test Task" - assert response.json()["status"] == "pending" - -def test_read_tasks(): - response = client.get("/tasks/") - assert response.status_code == 200 - assert len(response.json()) == 1 - -def test_get_task_by_id(): - task_response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - task_id = task_response.json()["id"] - response = client.get(f"/tasks/{task_id}") - assert response.status_code == 200 - assert response.json()["id"] == task_id - -def test_get_task_by_id_not_found(): - response = client.get("/tasks/999") - assert response.status_code == 404 - -def test_update_task(): - task_response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - task_id = task_response.json()["id"] - update_data = {"title": "Updated Test Task"} - response = client.put(f"/tasks/{task_id}", json=update_data) - assert response.status_code == 200 - assert response.json()["title"] == "Updated Test Task" - -def test_delete_task(): - task_response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"}) - task_id = task_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 \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/Dockerfile b/zipit/loop_runs/v7_enum_fix/Dockerfile deleted file mode 100644 index 4ab404a..0000000 --- a/zipit/loop_runs/v7_enum_fix/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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 -USER appuser -EXPOSE 8000 -CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/docker-compose.yml b/zipit/loop_runs/v7_enum_fix/docker-compose.yml deleted file mode 100644 index 72e6f5d..0000000 --- a/zipit/loop_runs/v7_enum_fix/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -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 diff --git a/zipit/loop_runs/v7_enum_fix/main.py b/zipit/loop_runs/v7_enum_fix/main.py deleted file mode 100644 index 1677451..0000000 --- a/zipit/loop_runs/v7_enum_fix/main.py +++ /dev/null @@ -1,63 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/models.py b/zipit/loop_runs/v7_enum_fix/models.py deleted file mode 100644 index a6605fe..0000000 --- a/zipit/loop_runs/v7_enum_fix/models.py +++ /dev/null @@ -1,18 +0,0 @@ -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) \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/pyproject.toml b/zipit/loop_runs/v7_enum_fix/pyproject.toml deleted file mode 100644 index b77ac6f..0000000 --- a/zipit/loop_runs/v7_enum_fix/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[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" -] \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/report.json b/zipit/loop_runs/v7_enum_fix/report.json deleted file mode 100644 index e818e10..0000000 --- a/zipit/loop_runs/v7_enum_fix/report.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "run_id": "v7_enum_fix", - "steps": [ - { - "step": "requirements", - "agent": "client", - "attempts": [ - { - "attempt": 1, - "elapsed": 7.6, - "errors": [], - "code_length": 1361 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "models.py", - "agent": "data", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.9, - "errors": [], - "code_length": 707 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "schemas.py", - "agent": "coder", - "attempts": [ - { - "attempt": 1, - "elapsed": 5.4, - "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.6, - "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": 20.3, - "errors": [], - "code_length": 2050 - } - ], - "final_errors": [], - "passed": true - }, - { - "step": "Dockerfile", - "agent": "tester", - "attempts": [ - { - "attempt": 1, - "elapsed": 4.5, - "errors": [], - "code_length": 291 - } - ], - "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 v7_enum_fix_default Creating\n Network v7_enum_fix_default Created\nerror: Failed to initialize cache at `/tmp/uv-cache`\n Caused by: failed to open file `/tmp/uv-cache/sdists-v9/.git`: Permission denied (os error 13)\n\n", - "Kontti ei käynnistynyt 15s:ssa" - ] - } -} \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/requirements b/zipit/loop_runs/v7_enum_fix/requirements deleted file mode 100644 index 222f517..0000000 --- a/zipit/loop_runs/v7_enum_fix/requirements +++ /dev/null @@ -1,32 +0,0 @@ -**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. \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/schemas.py b/zipit/loop_runs/v7_enum_fix/schemas.py deleted file mode 100644 index acdac2e..0000000 --- a/zipit/loop_runs/v7_enum_fix/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v7_enum_fix/test_main.py b/zipit/loop_runs/v7_enum_fix/test_main.py deleted file mode 100644 index 1b20f3b..0000000 --- a/zipit/loop_runs/v7_enum_fix/test_main.py +++ /dev/null @@ -1,56 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/Dockerfile b/zipit/loop_runs/v8_cache_fix/Dockerfile deleted file mode 100644 index 2f99b8b..0000000 --- a/zipit/loop_runs/v8_cache_fix/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/docker-compose.yml b/zipit/loop_runs/v8_cache_fix/docker-compose.yml deleted file mode 100644 index 72e6f5d..0000000 --- a/zipit/loop_runs/v8_cache_fix/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -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 diff --git a/zipit/loop_runs/v8_cache_fix/main.py b/zipit/loop_runs/v8_cache_fix/main.py deleted file mode 100644 index 1677451..0000000 --- a/zipit/loop_runs/v8_cache_fix/main.py +++ /dev/null @@ -1,63 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/models.py b/zipit/loop_runs/v8_cache_fix/models.py deleted file mode 100644 index a6605fe..0000000 --- a/zipit/loop_runs/v8_cache_fix/models.py +++ /dev/null @@ -1,18 +0,0 @@ -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) \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/pyproject.toml b/zipit/loop_runs/v8_cache_fix/pyproject.toml deleted file mode 100644 index b77ac6f..0000000 --- a/zipit/loop_runs/v8_cache_fix/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[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" -] \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/report.json b/zipit/loop_runs/v8_cache_fix/report.json deleted file mode 100644 index 80dc6c7..0000000 --- a/zipit/loop_runs/v8_cache_fix/report.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "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" - ] - } -} \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/requirements b/zipit/loop_runs/v8_cache_fix/requirements deleted file mode 100644 index 222f517..0000000 --- a/zipit/loop_runs/v8_cache_fix/requirements +++ /dev/null @@ -1,32 +0,0 @@ -**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. \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/schemas.py b/zipit/loop_runs/v8_cache_fix/schemas.py deleted file mode 100644 index acdac2e..0000000 --- a/zipit/loop_runs/v8_cache_fix/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/loop_runs/v8_cache_fix/test_main.py b/zipit/loop_runs/v8_cache_fix/test_main.py deleted file mode 100644 index 1b20f3b..0000000 --- a/zipit/loop_runs/v8_cache_fix/test_main.py +++ /dev/null @@ -1,56 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/projekti.zip b/zipit/projekti.zip deleted file mode 100644 index 9ac4039..0000000 Binary files a/zipit/projekti.zip and /dev/null differ diff --git a/zipit/projekti_clean/Dockerfile b/zipit/projekti_clean/Dockerfile deleted file mode 100644 index 59d302a..0000000 --- a/zipit/projekti_clean/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# 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"] \ No newline at end of file diff --git a/zipit/projekti_clean/README.md b/zipit/projekti_clean/README.md deleted file mode 100644 index 6e24329..0000000 --- a/zipit/projekti_clean/README.md +++ /dev/null @@ -1,50 +0,0 @@ -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" \ No newline at end of file diff --git a/zipit/projekti_clean/agents.yaml b/zipit/projekti_clean/agents.yaml deleted file mode 100644 index cf8f969..0000000 --- a/zipit/projekti_clean/agents.yaml +++ /dev/null @@ -1,150 +0,0 @@ -# Agents — Kipinä Agentic Studio → CrewAI - -client: - role: >- - Client - goal: >- - product owner who turns vague ideas into clear, actionable software requirements - backstory: | - You are a product owner who turns vague ideas into clear, actionable software requirements. - - GIVEN a short project description from the user, produce a structured brief: - - 1. PROJECT NAME: a short, descriptive name - 2. GOAL: one sentence explaining what the software does and who it's for - 3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) - 4. DATA MODEL: list the main entities and their key fields (include field types) - 5. API ENDPOINTS: list the REST endpoints (method + path + purpose) - 6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - - RULES: - - Be specific: "User can filter todos by status" not "todo management" - - Use plain English, no code - - Maximum 400 words total - llm: qwen-coder - -data: - role: >- - Data Engineer - goal: >- - database architect specializing in SQLAlchemy and relational databases - backstory: | - You are a database architect specializing in SQLAlchemy and relational databases. - - YOUR RESPONSIBILITIES: - 1. Design normalized database schemas with proper column types and constraints - 2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships - 3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py) - 4. Use String(length) not bare String for SQLite compatibility - 5. Add nullable=False for required fields, unique=True where appropriate - 6. Use Column(Integer, primary_key=True, index=True) for IDs - - ALWAYS INCLUDE: - - from sqlalchemy import create_engine, Column, Integer, String - - from sqlalchemy.ext.declarative import declarative_base - - from sqlalchemy.orm import sessionmaker - - DATABASE_URL, engine, SessionLocal, Base - llm: qwen-coder - -coder: - role: >- - Coder - goal: >- - expert Python developer - backstory: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file - 2. Import from other project files: from models import User, SessionLocal - 3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) - 4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Add explanations or comments like "# Add routes here" - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Forget to import from other project files - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - llm: qwen-coder - -qa: - role: >- - QA - goal: >- - QA engineer responsible for code review and automated testing - backstory: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - pytest as the test framework - - FastAPI TestClient for API endpoint testing - - SQLAlchemy in-memory SQLite for test database isolation - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - ALWAYS: from fastapi.testclient import TestClient - llm: qwen-coder - -tester: - role: >- - DevOps - goal: >- - DevOps engineer specializing in containerization and deployment - backstory: | - You are a DevOps engineer specializing in containerization and deployment. - - YOUR RESPONSIBILITIES: - 1. Write production-ready Dockerfiles - 2. Use multi-stage builds when appropriate - 3. Follow security best practices (non-root user, minimal base image) - 4. Configure health checks and proper signal handling - - DOCKERFILE RULES: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - Copy pyproject.toml first, then uv sync, then copy source - - Expose appropriate ports - - Use uv run for CMD - - Write ONLY the requested files, no explanations. - llm: qwen-coder - -observer: - role: >- - Observer - goal: >- - independent technical observer and risk analyst - backstory: | - 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" - llm: qwen-coder diff --git a/zipit/projekti_clean/crew.py b/zipit/projekti_clean/crew.py deleted file mode 100644 index edcee05..0000000 --- a/zipit/projekti_clean/crew.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit - -CrewAI crew — generated by Kipinä Agentic Studio. -Run: crewai run -""" - -from crewai import Agent, Crew, Process, Task -from crewai.project import CrewBase, agent, crew, task - - -@CrewBase -class ProjectCrew: - """Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit""" - - agents_config = "agents.yaml" - tasks_config = "tasks.yaml" - - @agent - def client(self) -> Agent: - return Agent(config=self.agents_config["client"]) - - @agent - def data(self) -> Agent: - return Agent(config=self.agents_config["data"]) - - @agent - def coder(self) -> Agent: - return Agent(config=self.agents_config["coder"]) - - @agent - def qa(self) -> Agent: - return Agent(config=self.agents_config["qa"]) - - @agent - def tester(self) -> Agent: - return Agent(config=self.agents_config["tester"]) - - @agent - def observer(self) -> Agent: - return Agent(config=self.agents_config["observer"]) - - @task - def step_0_requirements(self) -> Task: - return Task(config=self.tasks_config["step_0_requirements"]) - - @task - def step_1_models_py(self) -> Task: - return Task(config=self.tasks_config["step_1_models_py"]) - - @task - def step_2_schemas_py(self) -> Task: - return Task(config=self.tasks_config["step_2_schemas_py"]) - - @task - def step_3_main_py(self) -> Task: - return Task(config=self.tasks_config["step_3_main_py"]) - - @task - def step_4_pyproject_toml(self) -> Task: - return Task(config=self.tasks_config["step_4_pyproject_toml"]) - - @task - def step_5_review(self) -> Task: - return Task(config=self.tasks_config["step_5_review"]) - - @task - def step_6_test_main_py(self) -> Task: - return Task(config=self.tasks_config["step_6_test_main_py"]) - - @task - def step_7_dockerfile(self) -> Task: - return Task(config=self.tasks_config["step_7_dockerfile"]) - - @task - def step_8_readme_md(self) -> Task: - return Task(config=self.tasks_config["step_8_readme_md"]) - - @crew - def crew(self) -> Crew: - return Crew( - agents=self.agents, - tasks=self.tasks, - process=Process.sequential, - verbose=True, - ) diff --git a/zipit/projekti_clean/main.py b/zipit/projekti_clean/main.py deleted file mode 100644 index 092369f..0000000 --- a/zipit/projekti_clean/main.py +++ /dev/null @@ -1,56 +0,0 @@ -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() \ No newline at end of file diff --git a/zipit/projekti_clean/models.py b/zipit/projekti_clean/models.py deleted file mode 100644 index 8fc8722..0000000 --- a/zipit/projekti_clean/models.py +++ /dev/null @@ -1,16 +0,0 @@ -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') \ No newline at end of file diff --git a/zipit/projekti_clean/prompts/0_client_requirements.md b/zipit/projekti_clean/prompts/0_client_requirements.md deleted file mode 100644 index d9d578d..0000000 --- a/zipit/projekti_clean/prompts/0_client_requirements.md +++ /dev/null @@ -1,64 +0,0 @@ -# 0 — Client (client) — requirements - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a product owner who turns vague ideas into clear, actionable software requirements. - -GIVEN a short project description from the user, produce a structured brief: - -1. PROJECT NAME: a short, descriptive name -2. GOAL: one sentence explaining what the software does and who it's for -3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) -4. DATA MODEL: list the main entities and their key fields (include field types) -5. API ENDPOINTS: list the REST endpoints (method + path + purpose) -6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - -RULES: -- Be specific: "User can filter todos by status" not "todo management" -- Use plain English, no code -- Maximum 400 words total -``` - -## Syöte - -``` -Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -``` - -## Tulos - -``` -1. **PROJECT NAME:** Todo-sovellus - -2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - -3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - -4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - - description (text) - - due_date (date) - - status (enum: 'pending', 'completed') - -5. **API ENDPOINTS:** - - GET /todos: Retrieve all todos. - - GET /todos/{id}: Retrieve a single todo by ID. - - POST /todos: Create a new todo. - - PUT /todos/{id}: Update an existing todo. - - DELETE /todos/{id}: Delete a todo. - -6. **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication required for accessing endpoints. - - Implement unit tests and integration tests to ensure functionality. -``` diff --git a/zipit/projekti_clean/prompts/1_data_models.py.md b/zipit/projekti_clean/prompts/1_data_models.py.md deleted file mode 100644 index 59f33bf..0000000 --- a/zipit/projekti_clean/prompts/1_data_models.py.md +++ /dev/null @@ -1,122 +0,0 @@ -# 1 — Data Engineer (data) — models.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a database architect specializing in SQLAlchemy and relational databases. - -YOUR RESPONSIBILITIES: -1. Design normalized database schemas with proper column types and constraints -2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships -3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py) -4. Use String(length) not bare String for SQLite compatibility -5. Add nullable=False for required fields, unique=True where appropriate -6. Use Column(Integer, primary_key=True, index=True) for IDs - -ALWAYS INCLUDE: -- from sqlalchemy import create_engine, Column, Integer, String -- from sqlalchemy.ext.declarative import declarative_base -- from sqlalchemy.orm import sessionmaker -- DATABASE_URL, engine, SessionLocal, Base -``` - -## Syöte - -``` -You are a database architect specializing in SQLAlchemy and relational databases. - -YOUR RESPONSIBILITIES: -1. Design normalized database schemas with proper column types and constraints -2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships -3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py) -4. Use String(length) not bare String for SQLite compatibility -5. Add nullable=False for required fields, unique=True where appropriate -6. Use Column(Integer, primary_key=True, index=True) for IDs - -ALWAYS INCLUDE: -- from sqlalchemy import create_engine, Column, Integer, String -- from sqlalchemy.ext.declarative import declarative_base -- from sqlalchemy.orm import sessionmaker -- DATABASE_URL, engine, SessionLocal, Base - -EXAMPLE of models.py (for a different project, adapt to this one): -``` -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class Item(Base): - __tablename__ = "items" - id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False) - description = Column(String(500)) -``` - -PROJECT REQUIREMENTS (from product owner): -1. **PROJECT NAME:** Todo-sovellus - -2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - -3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - -4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - - description (text) - - due_date (date) - - status (enum: 'pending', 'completed') - -5. **API ENDPOINTS:** - - GET /todos: Retrieve all todos. - - GET /todos/{id}: Retrieve a single todo by ID. - - POST /todos: Create a new todo. - - PUT /todos/{id}: Update an existing todo. - - DELETE /todos/{id}: Delete a todo. - -6. **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication required for accessing endpoints. - - Implement unit tests and integration tests to ensure functionality. - -NOW write "models.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Define the SQLAlchemy model based on the project description. Always include: -- engine with check_same_thread=False for SQLite -- SessionLocal with autocommit=False -- Base = declarative_base() -- Model class with __tablename__, primary key, and fields -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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') -``` diff --git a/zipit/projekti_clean/prompts/2_coder_schemas.py.md b/zipit/projekti_clean/prompts/2_coder_schemas.py.md deleted file mode 100644 index 8d64a25..0000000 --- a/zipit/projekti_clean/prompts/2_coder_schemas.py.md +++ /dev/null @@ -1,142 +0,0 @@ -# 2 — Coder (coder) — schemas.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of schemas.py (for a different project, adapt to this one): -``` -from pydantic import BaseModel - -class ItemCreate(BaseModel): - name: str - description: str | None = None - -class ItemResponse(ItemCreate): - id: int - - class Config: - from_attributes = True -``` - -Already written files in THIS project: ---- 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') - -PROJECT REQUIREMENTS (from product owner): -1. **PROJECT NAME:** Todo-sovellus - -2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - -3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - -4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - - description (text) - - due_date (date) - - status (enum: 'pending', 'completed') - -5. **API ENDPOINTS:** - - GET /todos: Retrieve all todos. - - GET /todos/{id}: Retrieve a single todo by ID. - - POST /todos: Create a new todo. - - PUT /todos/{id}: Update an existing todo. - - DELETE /todos/{id}: Delete a todo. - -6. **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication required for accessing endpoints. - - Implement unit tests and integration tests to ensure functionality. - -NOW write "schemas.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Create Pydantic schemas that match the SQLAlchemy model: -- Create schema: fields without id (user provides these) -- Response schema: inherits from Create, adds id -- Add class Config with from_attributes = True (required for SQLAlchemy ORM) -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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 -``` diff --git a/zipit/projekti_clean/prompts/3_coder_main.py.md b/zipit/projekti_clean/prompts/3_coder_main.py.md deleted file mode 100644 index 3b3679e..0000000 --- a/zipit/projekti_clean/prompts/3_coder_main.py.md +++ /dev/null @@ -1,244 +0,0 @@ -# 3 — Coder (coder) — main.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of main.py (for a different project, adapt to this one): -``` -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, Item -from schemas import ItemCreate, ItemResponse - -Base.metadata.create_all(bind=engine) -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/items/", response_model=ItemResponse, status_code=201) -def create_item(item: ItemCreate, db: Session = Depends(get_db)): - db_item = Item(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/items/", response_model=list[ItemResponse]) -def list_items(db: Session = Depends(get_db)): - return db.query(Item).all() - -@app.get("/items/{item_id}", response_model=ItemResponse) -def get_item(item_id: int, db: Session = Depends(get_db)): - item = db.query(Item).filter(Item.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Not found") - return item - -@app.put("/items/{item_id}", response_model=ItemResponse) -def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)): - db_item = db.query(Item).filter(Item.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/items/{item_id}", status_code=204) -def delete_item(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Item).filter(Item.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Not found") - db.delete(db_item) - db.commit() -``` - -Already written files in THIS project: ---- 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 - -PROJECT REQUIREMENTS (from product owner): -1. **PROJECT NAME:** Todo-sovellus - -2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - -3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - -4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - - description (text) - - due_date (date) - - status (enum: 'pending', 'completed') - -5. **API ENDPOINTS:** - - GET /todos: Retrieve all todos. - - GET /todos/{id}: Retrieve a single todo by ID. - - POST /todos: Create a new todo. - - PUT /todos/{id}: Update an existing todo. - - DELETE /todos/{id}: Delete a todo. - -6. **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication required for accessing endpoints. - - Implement unit tests and integration tests to ensure functionality. - -NOW write "main.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Create the FastAPI app with all CRUD endpoints: -- Import from models.py and schemas.py (use exact class names) -- create_all(bind=engine) at module level -- get_db dependency with yield pattern -- POST (201), GET list, GET by id, PUT, DELETE (204) -- Use response_model for type safety -- Use model_dump() not dict() (Pydantic v2) -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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() -``` diff --git a/zipit/projekti_clean/prompts/4_coder_pyproject.toml.md b/zipit/projekti_clean/prompts/4_coder_pyproject.toml.md deleted file mode 100644 index 4189324..0000000 --- a/zipit/projekti_clean/prompts/4_coder_pyproject.toml.md +++ /dev/null @@ -1,212 +0,0 @@ -# 4 — Coder (coder) — pyproject.toml - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file -2. Import from other project files: from models import User, SessionLocal -3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) -4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Add explanations or comments like "# Add routes here" -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Forget to import from other project files -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of pyproject.toml (for a different project, adapt to this one): -``` -[project] -name = "myapp" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" -``` - -Already written files in THIS project: ---- 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() - -PROJECT REQUIREMENTS (from product owner): -1. **PROJECT NAME:** Todo-sovellus - -2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - -3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - -4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - - description (text) - - due_date (date) - - status (enum: 'pending', 'completed') - -5. **API ENDPOINTS:** - - GET /todos: Retrieve all todos. - - GET /todos/{id}: Retrieve a single todo by ID. - - POST /todos: Create a new todo. - - PUT /todos/{id}: Update an existing todo. - - DELETE /todos/{id}: Delete a todo. - -6. **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication required for accessing endpoints. - - Implement unit tests and integration tests to ensure functionality. - -NOW write "pyproject.toml" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Use [project] format (PEP 621, compatible with uv). List dependencies under [project.dependencies]. Add [project.scripts] with dev command. Never use requirements.txt or Poetry format. Run with: uv run uvicorn main:app --reload -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -[project] -name = "todo-sovellus" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" -``` diff --git a/zipit/projekti_clean/prompts/5_qa_review.md b/zipit/projekti_clean/prompts/5_qa_review.md deleted file mode 100644 index 5110aaf..0000000 --- a/zipit/projekti_clean/prompts/5_qa_review.md +++ /dev/null @@ -1,174 +0,0 @@ -# 5 — QA (qa) — review - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- pytest as the test framework -- FastAPI TestClient for API endpoint testing -- SQLAlchemy in-memory SQLite for test database isolation -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- ALWAYS: from fastapi.testclient import TestClient -``` - -## Syöte - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- pytest as the test framework -- FastAPI TestClient for API endpoint testing -- SQLAlchemy in-memory SQLite for test database isolation -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- ALWAYS: from fastapi.testclient import TestClient - -Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description". - ---- 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" -``` - -## Tulos - -``` -LGTM -``` diff --git a/zipit/projekti_clean/prompts/6_qa_test_main.py.md b/zipit/projekti_clean/prompts/6_qa_test_main.py.md deleted file mode 100644 index 9ef098d..0000000 --- a/zipit/projekti_clean/prompts/6_qa_test_main.py.md +++ /dev/null @@ -1,291 +0,0 @@ -# 6 — QA (qa) — test_main.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- pytest as the test framework -- FastAPI TestClient for API endpoint testing -- SQLAlchemy in-memory SQLite for test database isolation -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- ALWAYS: from fastapi.testclient import TestClient -``` - -## Syöte - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- pytest as the test framework -- FastAPI TestClient for API endpoint testing -- SQLAlchemy in-memory SQLite for test database isolation -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- ALWAYS: from fastapi.testclient import TestClient - -Write pytest tests for this project: - ---- 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" - -Write a complete test_main.py file with TestClient. -``` - -## Tulos - -``` -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 -``` diff --git a/zipit/projekti_clean/prompts/7_tester_dockerfile.md b/zipit/projekti_clean/prompts/7_tester_dockerfile.md deleted file mode 100644 index 2c32150..0000000 --- a/zipit/projekti_clean/prompts/7_tester_dockerfile.md +++ /dev/null @@ -1,93 +0,0 @@ -# 7 — DevOps (tester) — Dockerfile - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a DevOps engineer specializing in containerization and deployment. - -YOUR RESPONSIBILITIES: -1. Write production-ready Dockerfiles -2. Use multi-stage builds when appropriate -3. Follow security best practices (non-root user, minimal base image) -4. Configure health checks and proper signal handling - -DOCKERFILE RULES: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- Copy pyproject.toml first, then uv sync, then copy source -- Expose appropriate ports -- Use uv run for CMD - -Write ONLY the requested files, no explanations. -``` - -## Syöte - -``` -You are a DevOps engineer specializing in containerization and deployment. - -YOUR RESPONSIBILITIES: -1. Write production-ready Dockerfiles -2. Use multi-stage builds when appropriate -3. Follow security best practices (non-root user, minimal base image) -4. Configure health checks and proper signal handling - -DOCKERFILE RULES: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- Copy pyproject.toml first, then uv sync, then copy source -- Expose appropriate ports -- Use uv run for CMD - -Write ONLY the requested files, no explanations. - -Write a Dockerfile for this Python FastAPI project. - -Project files: models.py, schemas.py, main.py, pyproject.toml, test_main.py - -Requirements: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- Copy pyproject.toml first, then uv sync, then copy source -- Expose port 8000 -- CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000 - -Write ONLY the Dockerfile, no explanations. -``` - -## Tulos - -``` -# 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"] -``` diff --git a/zipit/projekti_clean/prompts/8_observer_readme.md.md b/zipit/projekti_clean/prompts/8_observer_readme.md.md deleted file mode 100644 index f2fb540..0000000 --- a/zipit/projekti_clean/prompts/8_observer_readme.md.md +++ /dev/null @@ -1,404 +0,0 @@ -# 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" -``` diff --git a/zipit/projekti_clean/pyproject.toml b/zipit/projekti_clean/pyproject.toml deleted file mode 100644 index 66cf389..0000000 --- a/zipit/projekti_clean/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name = "todo-sovellus" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" \ No newline at end of file diff --git a/zipit/projekti_clean/schemas.py b/zipit/projekti_clean/schemas.py deleted file mode 100644 index 8cce12f..0000000 --- a/zipit/projekti_clean/schemas.py +++ /dev/null @@ -1,13 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/projekti_clean/tasks.yaml b/zipit/projekti_clean/tasks.yaml deleted file mode 100644 index 870a206..0000000 --- a/zipit/projekti_clean/tasks.yaml +++ /dev/null @@ -1,442 +0,0 @@ -# Tasks — Kipinä Agentic Studio → CrewAI - -step_0_requirements: - description: | - Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit - expected_output: >- - requirements - agent: client - -step_1_models_py: - description: | - You are a database architect specializing in SQLAlchemy and relational databases. - - YOUR RESPONSIBILITIES: - 1. Design normalized database schemas with proper column types and constraints - 2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships - 3. Set up engine, SessionLocal, and Base in the same file (models.py or database.py) - 4. Use String(length) not bare String for SQLite compatibility - 5. Add nullable=False for required fields, unique=True where appropriate - 6. Use Column(Integer, primary_key=True, index=True) for IDs - - ALWAYS INCLUDE: - - from sqlalchemy import create_engine, Column, Integer, String - - from sqlalchemy.ext.declarative import declarative_base - - from sqlalchemy.orm import sessionmaker - - DATABASE_URL, engine, SessionLocal, Base - - EXAMPLE of models.py (for a different project, adapt to this one): - ``` - from sqlalchemy import create_engine, Column, Integer, String - from sqlalchemy.ext.declarative import declarative_base - from sqlalchemy.orm import sessionmaker - - DATABASE_URL = "sqlite:///./app.db" - engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) - SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - Base = declarative_base() - - class Item(Base): - __tablename__ = "items" - id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False) - description = Column(String(500)) - ``` - - PROJECT REQUIREMENTS (from product owner): - 1. **PROJECT NAME:** Todo-sovellus - - 2. **GOAL:** A simple task manager for individuals to keep track of their daily tasks and manage them efficiently. - - 3. **CORE FEATURES:** - - Create new todos with title, description, due date. - - Read/View all todos or a single todo by ID. - - Update existing todos (title, description, status). - - Delete completed or unnecessary todos. - - Filter todos by status (e.g., pending, completed). - - 4. **DATA MODEL:** - - Todo - - id (integer, primary key) - - title (string) - # ... (truncated) - expected_output: >- - models.py - agent: data - -step_2_schemas_py: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file - 2. Import from other project files: from models import User, SessionLocal - 3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) - 4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Add explanations or comments like "# Add routes here" - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Forget to import from other project files - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of schemas.py (for a different project, adapt to this one): - ``` - from pydantic import BaseModel - - class ItemCreate(BaseModel): - name: str - description: str | None = None - - class ItemResponse(ItemCreate): - id: int - - class Config: - from_attributes = True - ``` - - Already written files in THIS project: - --- 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) - # ... (truncated) - expected_output: >- - schemas.py - agent: coder - -step_3_main_py: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file - 2. Import from other project files: from models import User, SessionLocal - 3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) - 4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Add explanations or comments like "# Add routes here" - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Forget to import from other project files - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of main.py (for a different project, adapt to this one): - ``` - from fastapi import FastAPI, Depends, HTTPException - from sqlalchemy.orm import Session - from models import Base, engine, SessionLocal, Item - from schemas import ItemCreate, ItemResponse - - Base.metadata.create_all(bind=engine) - app = FastAPI() - - def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - @app.post("/items/", response_model=ItemResponse, status_code=201) - def create_item(item: ItemCreate, db: Session = Depends(get_db)): - db_item = Item(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - - @app.get("/items/", response_model=list[ItemResponse]) - def list_items(db: Session = Depends(get_db)): - return db.query(Item).all() - - @app.get("/items/{item_id}", response_model=ItemResponse) - # ... (truncated) - expected_output: >- - main.py - agent: coder - -step_4_pyproject_toml: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file - 2. Import from other project files: from models import User, SessionLocal - 3. Pydantic schemas use different names than SQLAlchemy models: UserCreate, UserResponse (not User) - 4. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 5. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 6. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 7. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 8. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Add explanations or comments like "# Add routes here" - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Forget to import from other project files - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of pyproject.toml (for a different project, adapt to this one): - ``` - [project] - name = "myapp" - version = "0.1.0" - requires-python = ">=3.11" - dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - ] - - [project.scripts] - dev = "uvicorn main:app --reload" - ``` - - Already written files in THIS project: - --- 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) - # ... (truncated) - expected_output: >- - pyproject.toml - agent: coder - -step_5_review: - description: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - pytest as the test framework - - FastAPI TestClient for API endpoint testing - - SQLAlchemy in-memory SQLite for test database isolation - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - ALWAYS: from fastapi.testclient import TestClient - - Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description". - - --- 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 - # ... (truncated) - expected_output: >- - review - agent: qa - -step_6_test_main_py: - description: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - pytest as the test framework - - FastAPI TestClient for API endpoint testing - - SQLAlchemy in-memory SQLite for test database isolation - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - ALWAYS: from fastapi.testclient import TestClient - - Write pytest tests for this project: - - --- 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 - # ... (truncated) - expected_output: >- - test_main.py - agent: qa - -step_7_dockerfile: - description: | - You are a DevOps engineer specializing in containerization and deployment. - - YOUR RESPONSIBILITIES: - 1. Write production-ready Dockerfiles - 2. Use multi-stage builds when appropriate - 3. Follow security best practices (non-root user, minimal base image) - 4. Configure health checks and proper signal handling - - DOCKERFILE RULES: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - Copy pyproject.toml first, then uv sync, then copy source - - Expose appropriate ports - - Use uv run for CMD - - Write ONLY the requested files, no explanations. - - Write a Dockerfile for this Python FastAPI project. - - Project files: models.py, schemas.py, main.py, pyproject.toml, test_main.py - - Requirements: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - Copy pyproject.toml first, then uv sync, then copy source - - Expose port 8000 - - CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000 - - Write ONLY the Dockerfile, no explanations. - expected_output: >- - Dockerfile - agent: tester - -step_8_readme_md: - description: | - 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 - ``` - # ... (truncated) - expected_output: >- - README.md - agent: observer diff --git a/zipit/projekti_clean/test_main.py b/zipit/projekti_clean/test_main.py deleted file mode 100644 index 72aca63..0000000 --- a/zipit/projekti_clean/test_main.py +++ /dev/null @@ -1,116 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/rest-api-kyttjhallinnalle.zip b/zipit/rest-api-kyttjhallinnalle.zip deleted file mode 100644 index 000ee07..0000000 Binary files a/zipit/rest-api-kyttjhallinnalle.zip and /dev/null differ diff --git a/zipit/rest_api_check/Dockerfile b/zipit/rest_api_check/Dockerfile deleted file mode 100644 index 60a1cf4..0000000 --- a/zipit/rest_api_check/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] diff --git a/zipit/rest_api_check/README.md b/zipit/rest_api_check/README.md deleted file mode 100644 index 2d5b9ca..0000000 --- a/zipit/rest_api_check/README.md +++ /dev/null @@ -1,56 +0,0 @@ -**VERDICT: ORANGE** - -## Overview -This project is a REST API for user management using SQLite as the database backend, built with FastAPI. - -## Files -| File | Purpose | -|------|---------| -| models.py | Defines the data model for users. | -| schemas.py | Contains Pydantic schema definitions for input and output validation. | -| main.py | Implements the FastAPI application with endpoints for user operations. | -| test_main.py | Includes unit tests for the API endpoints using TestClient from fastapi.testclient. | -| pyproject.toml | Project metadata, dependencies, and build configuration. | -| Dockerfile | Defines a Docker image for running the API, including environment setup and dependency installation. | - -## Quick Start -git clone -cd project -uv sync -uv run uvicorn main:app --reload - -## Docker -docker build -t rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla . -docker run -p 8000:8000 rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla - -## API Endpoints -| Method | Path | Description | -|--------|------|-------------| -| POST | /users/ | Create a new user. | -| GET | /users/ | List all users. | -| GET | /users/{id} | Retrieve a specific user by ID. | -| PUT | /users/{id} | Update an existing user. | -| DELETE | /users/{id} | Delete a user. | - -## Architecture -The project uses FastAPI for the web framework, SQLAlchemy as the ORM, and SQLite as the database. The code is organized into `models.py` (data models), `schemas.py` (Pydantic schemas), and `main.py` (FastAPI application). Testing is done with `test_main.py`. A Dockerfile is provided to containerize the application. - -## Risk Assessment -| Severity | Issue | -|----------|-------| -| **HIGH** | SQL injection risks: The API directly uses user input in SQL queries without validation or parameterized statements. This can lead to SQL injection vulnerabilities if an attacker manages to inject malicious SQL code through the input fields. | -| **MEDIUM** | Input validation is minimal: While Pydantic schemas are used for data validation, they only validate types and lengths but do not sanitize inputs (e.g., no escaping of special characters). This could potentially lead to security issues if an attacker bypasses these checks. | -| **MEDIUM** | Authentication and authorization: The API does not implement any authentication or authorization mechanisms. Any user can create, read, update, or delete users without proper credentials. | -| **LOW** | Error handling: Basic error handling is implemented using HTTPException for specific cases (e.g., resource not found). However, more comprehensive logging and exception handling could improve the robustness of the application. | -| **MEDIUM** | Database connection management: The database session is created once per request in `get_db` but is not explicitly closed or committed/rolled back in all cases, which can lead to potential memory leaks or data inconsistencies if an error occurs during a transaction. | - -### Most Important Findings: -1. **SQL Injection Risks**: Direct user input usage in SQL queries without parameterized statements. -2. **Minimal Input Validation**: Pydantic schemas only validate types and lengths but do not sanitize inputs. -3. **Lack of Authentication and Authorization**: Anybody can perform CRUD operations on users. -4. **Error Handling Improvements Needed**: More comprehensive error handling, logging, and transaction management are required. - -### Overall Assessment: -**NEEDS WORK: SQL injection risks, minimal input validation, lack of authentication/authorization, and basic error handling improvements are critical issues that need to be addressed before the project can be considered secure and robust.** - -The architecture is logical but lacks necessary security features, and some parts of the code could benefit from better practices in terms of error management and database connection handling. \ No newline at end of file diff --git a/zipit/rest_api_check/agents.yaml b/zipit/rest_api_check/agents.yaml deleted file mode 100644 index 76c2baf..0000000 --- a/zipit/rest_api_check/agents.yaml +++ /dev/null @@ -1,115 +0,0 @@ -# Agents — Kipinä Agentic Studio → CrewAI - -client: - role: >- - Client - goal: >- - product owner who turns vague ideas into clear, actionable software requirements - backstory: | - You are a product owner who turns vague ideas into clear, actionable software requirements. - - GIVEN a short project description from the user, produce a structured brief: - - 1. PROJECT NAME: a short, descriptive name - 2. GOAL: one sentence explaining what the software does and who it's for - 3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) - 4. DATA MODEL: list the main entities and their key fields (include field types) - 5. API ENDPOINTS: list the REST endpoints (method + path + purpose) - 6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - - RULES: - - Be specific: "User can filter todos by status" not "todo management" - - Use plain English, no code - - Maximum 400 words total - llm: qwen-coder - -manager: - role: >- - Manager - goal: >- - software architect - backstory: | - You are a software architect. Given a project description, output a JSON specification. - Output ONLY valid JSON, no explanations. Follow this exact schema: - { - "project_name": "short-name", - "description": "One sentence", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - {"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false, "default": null}, - {"name": "description", "sa_type": "Text", "py_type": "str | None", "nullable": true, "default": null}, - {"name": "status", "sa_type": "String(20)", "py_type": "str", "nullable": false, "default": "pending"} - ] - } - ], - "extra_imports": ["from datetime import date"] - } - RULES: - - sa_type: SQLAlchemy column type (String(N), Text, Integer, Date, DateTime, Boolean, Float) - - py_type: Python type hint (str, int, float, bool, date, datetime, str | None, etc.) - - Do NOT use Enum — use String(20) with a default value for status fields - - nullable: true = optional field - - default: null = no default, otherwise a string/number value - - extra_imports: stdlib imports needed in schemas.py (e.g. "from datetime import date") - - entity name: PascalCase singular, table_name: snake_case plural - - Keep it simple: 1-3 entities, 3-7 fields each - llm: qwen-coder - -data: - role: >- - Data Engineer - goal: >- - (template pipeline — ei LLM-promptia) - backstory: | - (template pipeline — ei LLM-promptia) - llm: template - -coder: - role: >- - Coder - goal: >- - (template pipeline — ei LLM-promptia) - backstory: | - (template pipeline — ei LLM-promptia) - llm: template - -qa: - role: >- - QA - goal: >- - (template pipeline — ei LLM-promptia) - backstory: | - (template pipeline — ei LLM-promptia) - llm: template - -tester: - role: >- - DevOps - goal: >- - (template pipeline — ei LLM-promptia) - backstory: | - (template pipeline — ei LLM-promptia) - llm: template - -observer: - role: >- - Observer - goal: >- - independent technical observer and risk analyst - backstory: | - 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" - llm: qwen-coder diff --git a/zipit/rest_api_check/crew.py b/zipit/rest_api_check/crew.py deleted file mode 100644 index a5c8782..0000000 --- a/zipit/rest_api_check/crew.py +++ /dev/null @@ -1,93 +0,0 @@ -"""REST API käyttäjähallinnalle SQLite-tietokannalla - -CrewAI crew — generated by Kipinä Agentic Studio. -Run: crewai run -""" - -from crewai import Agent, Crew, Process, Task -from crewai.project import CrewBase, agent, crew, task - - -@CrewBase -class ProjectCrew: - """REST API käyttäjähallinnalle SQLite-tietokannalla""" - - agents_config = "agents.yaml" - tasks_config = "tasks.yaml" - - @agent - def client(self) -> Agent: - return Agent(config=self.agents_config["client"]) - - @agent - def manager(self) -> Agent: - return Agent(config=self.agents_config["manager"]) - - @agent - def data(self) -> Agent: - return Agent(config=self.agents_config["data"]) - - @agent - def coder(self) -> Agent: - return Agent(config=self.agents_config["coder"]) - - @agent - def qa(self) -> Agent: - return Agent(config=self.agents_config["qa"]) - - @agent - def tester(self) -> Agent: - return Agent(config=self.agents_config["tester"]) - - @agent - def observer(self) -> Agent: - return Agent(config=self.agents_config["observer"]) - - @task - def step_0_requirements(self) -> Task: - return Task(config=self.tasks_config["step_0_requirements"]) - - @task - def step_1_json_speksi(self) -> Task: - return Task(config=self.tasks_config["step_1_json_speksi"]) - - @task - def step_2_models_py(self) -> Task: - return Task(config=self.tasks_config["step_2_models_py"]) - - @task - def step_3_schemas_py(self) -> Task: - return Task(config=self.tasks_config["step_3_schemas_py"]) - - @task - def step_4_main_py(self) -> Task: - return Task(config=self.tasks_config["step_4_main_py"]) - - @task - def step_5_test_main_py(self) -> Task: - return Task(config=self.tasks_config["step_5_test_main_py"]) - - @task - def step_6_pyproject_toml(self) -> Task: - return Task(config=self.tasks_config["step_6_pyproject_toml"]) - - @task - def step_7_dockerfile(self) -> Task: - return Task(config=self.tasks_config["step_7_dockerfile"]) - - @task - def step_8_validointi(self) -> Task: - return Task(config=self.tasks_config["step_8_validointi"]) - - @task - def step_9_readme_md(self) -> Task: - return Task(config=self.tasks_config["step_9_readme_md"]) - - @crew - def crew(self) -> Crew: - return Crew( - agents=self.agents, - tasks=self.tasks, - process=Process.sequential, - verbose=True, - ) diff --git a/zipit/rest_api_check/docker-compose.yml b/zipit/rest_api_check/docker-compose.yml deleted file mode 100644 index d7c5fae..0000000 --- a/zipit/rest_api_check/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - build: . - ports: - - "18765:8000" diff --git a/zipit/rest_api_check/main.py b/zipit/rest_api_check/main.py deleted file mode 100644 index 12f2deb..0000000 --- a/zipit/rest_api_check/main.py +++ /dev/null @@ -1,52 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, User -from schemas import UserCreate, UserResponse - -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/users/", response_model=UserResponse, status_code=201) -def create_user(item: UserCreate, db: Session = Depends(get_db)): - db_item = User(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/users/", response_model=list[UserResponse]) -def list_users(db: Session = Depends(get_db)): - return db.query(User).all() - -@app.get("/users/{item_id}", response_model=UserResponse) -def get_user(item_id: int, db: Session = Depends(get_db)): - item = db.query(User).filter(User.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="User not found") - return item - -@app.put("/users/{item_id}", response_model=UserResponse) -def update_user(item_id: int, item: UserCreate, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/users/{item_id}", status_code=204) -def delete_user(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - db.delete(db_item) - db.commit() - diff --git a/zipit/rest_api_check/models.py b/zipit/rest_api_check/models.py deleted file mode 100644 index 00f2e37..0000000 --- a/zipit/rest_api_check/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class User(Base): - __tablename__ = "users" - id = Column(Integer, primary_key=True, index=True) - UserID = Column(Integer, nullable=False) - Username = Column(String(255), nullable=False) - Email = Column(String(255), nullable=False) - PasswordHash = Column(String(255), nullable=False) - -Base.metadata.create_all(bind=engine) diff --git a/zipit/rest_api_check/prompts/0_client_requirements.md b/zipit/rest_api_check/prompts/0_client_requirements.md deleted file mode 100644 index 184ba04..0000000 --- a/zipit/rest_api_check/prompts/0_client_requirements.md +++ /dev/null @@ -1,84 +0,0 @@ -# 0 — Client (client) — requirements - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a product owner who turns vague ideas into clear, actionable software requirements. - -GIVEN a short project description from the user, produce a structured brief: - -1. PROJECT NAME: a short, descriptive name -2. GOAL: one sentence explaining what the software does and who it's for -3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) -4. DATA MODEL: list the main entities and their key fields (include field types) -5. API ENDPOINTS: list the REST endpoints (method + path + purpose) -6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - -RULES: -- Be specific: "User can filter todos by status" not "todo management" -- Use plain English, no code -- Maximum 400 words total -``` - -## Syöte - -``` -REST API käyttäjähallinnalle SQLite-tietokannalla -``` - -## Tulos - -``` -**PROJECT NAME:** User Management REST API - -**GOAL:** To develop a RESTful API for managing users using an SQLite database. - -**CORE FEATURES:** -1. Register new users with username, email, and password. -2. Authenticate users to gain access to the system. -3. Update user profile information (email, password). -4. Delete existing users. -5. Retrieve all registered users. -6. Retrieve a specific user by ID. -7. Search for users based on partial usernames or emails. - -**DATA MODEL:** -- **User:** - - UserID (Integer, Primary Key) - - Username (String, Unique) - - Email (String, Unique) - - PasswordHash (String) - -**API ENDPOINTS:** -1. **POST /users/register**: Register a new user. - - Request Body: - { - "username": "johndoe", - "email": "john@example.com", - "password": "securepassword" - } -2. **POST /auth/login**: Authenticate a user and return a token. - - Request Body: - { - "username": "johndoe", - "password": "securepassword" - } -3. **PUT /users/{UserID}**: Update a specific user's profile. - - Path Parameter: UserID (Integer) - - Request Body: - { - "email": "newjohn@example.com", - "password": "newsecurepassword" - } -4. **DELETE /users/{UserID}**: Delete a specific user. - - Path Parameter: UserID (Integer) -5. **GET /users**: Retrieve all registered users. -6. **GET /users/{UserID}**: Retrieve a specific user by ID. -7. **GET /users/search?query={searchTerm}**: Search for users based on partial usernames or emails. - -**CONSTRAINTS:** -- Must use SQLite as the database. -- No authentication is needed to view all users, but authentication is required to update or delete any user. -``` diff --git a/zipit/rest_api_check/prompts/1_manager_json-speksi.md b/zipit/rest_api_check/prompts/1_manager_json-speksi.md deleted file mode 100644 index f9aef77..0000000 --- a/zipit/rest_api_check/prompts/1_manager_json-speksi.md +++ /dev/null @@ -1,112 +0,0 @@ -# 1 — Manager (manager) — JSON-speksi - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a software architect. Given a project description, output a JSON specification. -Output ONLY valid JSON, no explanations. Follow this exact schema: -{ - "project_name": "short-name", - "description": "One sentence", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - {"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false, "default": null}, - {"name": "description", "sa_type": "Text", "py_type": "str | None", "nullable": true, "default": null}, - {"name": "status", "sa_type": "String(20)", "py_type": "str", "nullable": false, "default": "pending"} - ] - } - ], - "extra_imports": ["from datetime import date"] -} -RULES: -- sa_type: SQLAlchemy column type (String(N), Text, Integer, Date, DateTime, Boolean, Float) -- py_type: Python type hint (str, int, float, bool, date, datetime, str | None, etc.) -- Do NOT use Enum — use String(20) with a default value for status fields -- nullable: true = optional field -- default: null = no default, otherwise a string/number value -- extra_imports: stdlib imports needed in schemas.py (e.g. "from datetime import date") -- entity name: PascalCase singular, table_name: snake_case plural -- Keep it simple: 1-3 entities, 3-7 fields each -``` - -## Syöte - -``` -**PROJECT NAME:** User Management REST API - -**GOAL:** To develop a RESTful API for managing users using an SQLite database. - -**CORE FEATURES:** -1. Register new users with username, email, and password. -2. Authenticate users to gain access to the system. -3. Update user profile information (email, password). -4. Delete existing users. -5. Retrieve all registered users. -6. Retrieve a specific user by ID. -7. Search for users based on partial usernames or emails. - -**DATA MODEL:** -- **User:** - - UserID (Integer, Primary Key) - - Username (String, Unique) - - Email (String, Unique) - - PasswordHash (String) - -**API ENDPOINTS:** -1. **POST /users/register**: Register a new user. - - Request Body: - { - "username": "johndoe", - "email": "john@example.com", - "password": "securepassword" - } -2. **POST /auth/login**: Authenticate a user and return a token. - - Request Body: - { - "username": "johndoe", - "password": "securepassword" - } -3. **PUT /users/{UserID}**: Update a specific user's profile. - - Path Parameter: UserID (Integer) - - Request Body: - { - "email": "newjohn@example.com", - "password": "newsecurepassword" - } -4. **DELETE /users/{UserID}**: Delete a specific user. - - Path Parameter: UserID (Integer) -5. **GET /users**: Retrieve all registered users. -6. **GET /users/{UserID}**: Retrieve a specific user by ID. -7. **GET /users/search?query={searchTerm}**: Search for users based on partial usernames or emails. - -**CONSTRAINTS:** -- Must use SQLite as the database. -- No authentication is needed to view all users, but authentication is required to update or delete any user. -``` - -## Tulos - -``` -{ - "project_name": "UserManagementAPI", - "description": "RESTful API for managing users using an SQLite database.", - "entities": [ - { - "name": "User", - "table_name": "users", - "fields": [ - {"name": "UserID", "sa_type": "Integer", "py_type": "int", "nullable": false, "default": null}, - {"name": "Username", "sa_type": "String(255)", "py_type": "str", "nullable": false, "unique": true}, - {"name": "Email", "sa_type": "String(255)", "py_type": "str", "nullable": false, "unique": true}, - {"name": "PasswordHash", "sa_type": "String(255)", "py_type": "str", "nullable": false} - ] - } - ], - "extra_imports": [] -} -``` diff --git a/zipit/rest_api_check/prompts/2_data_models.py.md b/zipit/rest_api_check/prompts/2_data_models.py.md deleted file mode 100644 index 60db858..0000000 --- a/zipit/rest_api_check/prompts/2_data_models.py.md +++ /dev/null @@ -1,39 +0,0 @@ -# 2 — Data Engineer (data) — models.py - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class User(Base): - __tablename__ = "users" - id = Column(Integer, primary_key=True, index=True) - UserID = Column(Integer, nullable=False) - Username = Column(String(255), nullable=False) - Email = Column(String(255), nullable=False) - PasswordHash = Column(String(255), nullable=False) - -Base.metadata.create_all(bind=engine) - -``` diff --git a/zipit/rest_api_check/prompts/3_coder_schemas.py.md b/zipit/rest_api_check/prompts/3_coder_schemas.py.md deleted file mode 100644 index 7cbd4db..0000000 --- a/zipit/rest_api_check/prompts/3_coder_schemas.py.md +++ /dev/null @@ -1,35 +0,0 @@ -# 3 — Coder (coder) — schemas.py - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -from pydantic import BaseModel - -class UserCreate(BaseModel): - UserID: int - Username: str - Email: str - PasswordHash: str - -class UserResponse(UserCreate): - id: int - - class Config: - from_attributes = True - - -``` diff --git a/zipit/rest_api_check/prompts/4_coder_main.py.md b/zipit/rest_api_check/prompts/4_coder_main.py.md deleted file mode 100644 index 40647a9..0000000 --- a/zipit/rest_api_check/prompts/4_coder_main.py.md +++ /dev/null @@ -1,73 +0,0 @@ -# 4 — Coder (coder) — main.py - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, User -from schemas import UserCreate, UserResponse - -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/users/", response_model=UserResponse, status_code=201) -def create_user(item: UserCreate, db: Session = Depends(get_db)): - db_item = User(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/users/", response_model=list[UserResponse]) -def list_users(db: Session = Depends(get_db)): - return db.query(User).all() - -@app.get("/users/{item_id}", response_model=UserResponse) -def get_user(item_id: int, db: Session = Depends(get_db)): - item = db.query(User).filter(User.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="User not found") - return item - -@app.put("/users/{item_id}", response_model=UserResponse) -def update_user(item_id: int, item: UserCreate, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/users/{item_id}", status_code=204) -def delete_user(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - db.delete(db_item) - db.commit() - - -``` diff --git a/zipit/rest_api_check/prompts/5_qa_test_main.py.md b/zipit/rest_api_check/prompts/5_qa_test_main.py.md deleted file mode 100644 index 45fc58d..0000000 --- a/zipit/rest_api_check/prompts/5_qa_test_main.py.md +++ /dev/null @@ -1,78 +0,0 @@ -# 5 — QA (qa) — test_main.py - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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_user(): - response = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 201 - assert 'id' in response.json() - -def test_list_users(): - client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - response = client.get('/users/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_user_by_id(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.get(f'/users/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_user_not_found(): - response = client.get('/users/99999') - assert response.status_code == 404 - -def test_update_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.put(f'/users/{item_id}', json={"UserID":1,"Username":"Updated Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 200 - -def test_delete_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.delete(f'/users/{item_id}') - assert response.status_code == 204 - response = client.get(f'/users/{item_id}') - assert response.status_code == 404 - - -``` diff --git a/zipit/rest_api_check/prompts/6_coder_pyproject.toml.md b/zipit/rest_api_check/prompts/6_coder_pyproject.toml.md deleted file mode 100644 index 3679d7a..0000000 --- a/zipit/rest_api_check/prompts/6_coder_pyproject.toml.md +++ /dev/null @@ -1,32 +0,0 @@ -# 6 — Coder (coder) — pyproject.toml - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -[project] -name = "usermanagementapi" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] - -``` diff --git a/zipit/rest_api_check/prompts/7_tester_dockerfile.md b/zipit/rest_api_check/prompts/7_tester_dockerfile.md deleted file mode 100644 index bd51de2..0000000 --- a/zipit/rest_api_check/prompts/7_tester_dockerfile.md +++ /dev/null @@ -1,32 +0,0 @@ -# 7 — DevOps (tester) — Dockerfile - -**Malli:** `template` - -## System Prompt - -``` -(template pipeline — ei LLM-promptia) -``` - -## Syöte - -``` -Generated from spec: ["User"] -``` - -## Tulos - -``` -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"] - -``` diff --git a/zipit/rest_api_check/prompts/8_qa_validointi.md b/zipit/rest_api_check/prompts/8_qa_validointi.md deleted file mode 100644 index 418626b..0000000 --- a/zipit/rest_api_check/prompts/8_qa_validointi.md +++ /dev/null @@ -1,21 +0,0 @@ -# 8 — QA (qa) — validointi - -**Malli:** `mekaaninen` - -## System Prompt - -``` -(mekaaninen validointi — ei LLM:ää) -``` - -## Syöte - -``` -validateProjectCode(files) -``` - -## Tulos - -``` -OK — 0 issues -``` diff --git a/zipit/rest_api_check/prompts/9_observer_readme.md.md b/zipit/rest_api_check/prompts/9_observer_readme.md.md deleted file mode 100644 index 929f969..0000000 --- a/zipit/rest_api_check/prompts/9_observer_readme.md.md +++ /dev/null @@ -1,336 +0,0 @@ -# 9 — 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: REST API käyttäjähallinnalle SQLite-tietokannalla - -FIRST LINE must be exactly one of: -VERDICT: GREEN -VERDICT: ORANGE -VERDICT: RED - -Then write this report: - -# REST API käyttäjähallinnalle SQLite-tietokannalla - -## Overview -One paragraph describing what this project does. - -## Files -| File | Purpose | -|------|---------| -| models.py | ... | -| schemas.py | ... | -| main.py | ... | -| test_main.py | ... | -| pyproject.toml | ... | -| Dockerfile | ... | - -## Quick Start -```bash -git clone -cd project -uv sync -uv run uvicorn main:app --reload -``` - -## Docker -```bash -docker build -t rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla . -docker run -p 8000:8000 rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla -``` - -## 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 -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class User(Base): - __tablename__ = "users" - id = Column(Integer, primary_key=True, index=True) - UserID = Column(Integer, nullable=False) - Username = Column(String(255), nullable=False) - Email = Column(String(255), nullable=False) - PasswordHash = Column(String(255), nullable=False) - -Base.metadata.create_all(bind=engine) - - ---- schemas.py --- -from pydantic import BaseModel - -class UserCreate(BaseModel): - UserID: int - Username: str - Email: str - PasswordHash: str - -class UserResponse(UserCreate): - 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, User -from schemas import UserCreate, UserResponse - -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/users/", response_model=UserResponse, status_code=201) -def create_user(item: UserCreate, db: Session = Depends(get_db)): - db_item = User(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/users/", response_model=list[UserResponse]) -def list_users(db: Session = Depends(get_db)): - return db.query(User).all() - -@app.get("/users/{item_id}", response_model=UserResponse) -def get_user(item_id: int, db: Session = Depends(get_db)): - item = db.query(User).filter(User.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="User not found") - return item - -@app.put("/users/{item_id}", response_model=UserResponse) -def update_user(item_id: int, item: UserCreate, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/users/{item_id}", status_code=204) -def delete_user(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - db.delete(db_item) - db.commit() - - - ---- test_main.py --- -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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_user(): - response = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 201 - assert 'id' in response.json() - -def test_list_users(): - client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - response = client.get('/users/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_user_by_id(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.get(f'/users/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_user_not_found(): - response = client.get('/users/99999') - assert response.status_code == 404 - -def test_update_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.put(f'/users/{item_id}', json={"UserID":1,"Username":"Updated Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 200 - -def test_delete_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.delete(f'/users/{item_id}') - assert response.status_code == 204 - response = client.get(f'/users/{item_id}') - assert response.status_code == 404 - - - ---- pyproject.toml --- -[project] -name = "usermanagementapi" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] - - ---- Dockerfile --- -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"] - -``` - -## Tulos - -``` -**VERDICT: ORANGE** - -## Overview -This project is a REST API for user management using SQLite as the database backend, built with FastAPI. - -## Files -| File | Purpose | -|------|---------| -| models.py | Defines the data model for users. | -| schemas.py | Contains Pydantic schema definitions for input and output validation. | -| main.py | Implements the FastAPI application with endpoints for user operations. | -| test_main.py | Includes unit tests for the API endpoints using TestClient from fastapi.testclient. | -| pyproject.toml | Project metadata, dependencies, and build configuration. | -| Dockerfile | Defines a Docker image for running the API, including environment setup and dependency installation. | - -## Quick Start -git clone -cd project -uv sync -uv run uvicorn main:app --reload - -## Docker -docker build -t rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla . -docker run -p 8000:8000 rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla - -## API Endpoints -| Method | Path | Description | -|--------|------|-------------| -| POST | /users/ | Create a new user. | -| GET | /users/ | List all users. | -| GET | /users/{id} | Retrieve a specific user by ID. | -| PUT | /users/{id} | Update an existing user. | -| DELETE | /users/{id} | Delete a user. | - -## Architecture -The project uses FastAPI for the web framework, SQLAlchemy as the ORM, and SQLite as the database. The code is organized into `models.py` (data models), `schemas.py` (Pydantic schemas), and `main.py` (FastAPI application). Testing is done with `test_main.py`. A Dockerfile is provided to containerize the application. - -## Risk Assessment -| Severity | Issue | -|----------|-------| -| **HIGH** | SQL injection risks: The API directly uses user input in SQL queries without validation or parameterized statements. This can lead to SQL injection vulnerabilities if an attacker manages to inject malicious SQL code through the input fields. | -| **MEDIUM** | Input validation is minimal: While Pydantic schemas are used for data validation, they only validate types and lengths but do not sanitize inputs (e.g., no escaping of special characters). This could potentially lead to security issues if an attacker bypasses these checks. | -| **MEDIUM** | Authentication and authorization: The API does not implement any authentication or authorization mechanisms. Any user can create, read, update, or delete users without proper credentials. | -| **LOW** | Error handling: Basic error handling is implemented using HTTPException for specific cases (e.g., resource not found). However, more comprehensive logging and exception handling could improve the robustness of the application. | -| **MEDIUM** | Database connection management: The database session is created once per request in `get_db` but is not explicitly closed or committed/rolled back in all cases, which can lead to potential memory leaks or data inconsistencies if an error occurs during a transaction. | - -### Most Important Findings: -1. **SQL Injection Risks**: Direct user input usage in SQL queries without parameterized statements. -2. **Minimal Input Validation**: Pydantic schemas only validate types and lengths but do not sanitize inputs. -3. **Lack of Authentication and Authorization**: Anybody can perform CRUD operations on users. -4. **Error Handling Improvements Needed**: More comprehensive error handling, logging, and transaction management are required. - -### Overall Assessment: -**NEEDS WORK: SQL injection risks, minimal input validation, lack of authentication/authorization, and basic error handling improvements are critical issues that need to be addressed before the project can be considered secure and robust.** - -The architecture is logical but lacks necessary security features, and some parts of the code could benefit from better practices in terms of error management and database connection handling. -``` diff --git a/zipit/rest_api_check/pyproject.toml b/zipit/rest_api_check/pyproject.toml deleted file mode 100644 index 79727c8..0000000 --- a/zipit/rest_api_check/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "usermanagementapi" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] diff --git a/zipit/rest_api_check/schemas.py b/zipit/rest_api_check/schemas.py deleted file mode 100644 index ee309ea..0000000 --- a/zipit/rest_api_check/schemas.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel - -class UserCreate(BaseModel): - UserID: int - Username: str - Email: str - PasswordHash: str - -class UserResponse(UserCreate): - id: int - - class Config: - from_attributes = True - diff --git a/zipit/rest_api_check/tasks.yaml b/zipit/rest_api_check/tasks.yaml deleted file mode 100644 index cfb646f..0000000 --- a/zipit/rest_api_check/tasks.yaml +++ /dev/null @@ -1,170 +0,0 @@ -# Tasks — Kipinä Agentic Studio → CrewAI - -step_0_requirements: - description: | - REST API käyttäjähallinnalle SQLite-tietokannalla - expected_output: >- - requirements - agent: client - -step_1_json_speksi: - description: | - **PROJECT NAME:** User Management REST API - - **GOAL:** To develop a RESTful API for managing users using an SQLite database. - - **CORE FEATURES:** - 1. Register new users with username, email, and password. - 2. Authenticate users to gain access to the system. - 3. Update user profile information (email, password). - 4. Delete existing users. - 5. Retrieve all registered users. - 6. Retrieve a specific user by ID. - 7. Search for users based on partial usernames or emails. - - **DATA MODEL:** - - **User:** - - UserID (Integer, Primary Key) - - Username (String, Unique) - - Email (String, Unique) - - PasswordHash (String) - - **API ENDPOINTS:** - 1. **POST /users/register**: Register a new user. - - Request Body: - { - "username": "johndoe", - "email": "john@example.com", - "password": "securepassword" - } - 2. **POST /auth/login**: Authenticate a user and return a token. - - Request Body: - { - "username": "johndoe", - "password": "securepassword" - } - 3. **PUT /users/{UserID}**: Update a specific user's profile. - - Path Parameter: UserID (Integer) - - Request Body: - { - "email": "newjohn@example.com", - "password": "newsecurepassword" - } - 4. **DELETE /users/{UserID}**: Delete a specific user. - - Path Parameter: UserID (Integer) - 5. **GET /users**: Retrieve all registered users. - 6. **GET /users/{UserID}**: Retrieve a specific user by ID. - 7. **GET /users/search?query={searchTerm}**: Search for users based on partial usernames or emails. - - **CONSTRAINTS:** - - Must use SQLite as the database. - - No authentication is needed to view all users, but authentication is required to update or delete any user. - expected_output: >- - JSON-speksi - agent: manager - -step_2_models_py: - description: | - Generated from spec: ["User"] - expected_output: >- - models.py - agent: data - -step_3_schemas_py: - description: | - Generated from spec: ["User"] - expected_output: >- - schemas.py - agent: coder - -step_4_main_py: - description: | - Generated from spec: ["User"] - expected_output: >- - main.py - agent: coder - -step_5_test_main_py: - description: | - Generated from spec: ["User"] - expected_output: >- - test_main.py - agent: qa - -step_6_pyproject_toml: - description: | - Generated from spec: ["User"] - expected_output: >- - pyproject.toml - agent: coder - -step_7_dockerfile: - description: | - Generated from spec: ["User"] - expected_output: >- - Dockerfile - agent: tester - -step_8_validointi: - description: | - validateProjectCode(files) - expected_output: >- - validointi - agent: qa - -step_9_readme_md: - description: | - 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: REST API käyttäjähallinnalle SQLite-tietokannalla - - FIRST LINE must be exactly one of: - VERDICT: GREEN - VERDICT: ORANGE - VERDICT: RED - - Then write this report: - - # REST API käyttäjähallinnalle SQLite-tietokannalla - - ## Overview - One paragraph describing what this project does. - - ## Files - | File | Purpose | - |------|---------| - | models.py | ... | - | schemas.py | ... | - | main.py | ... | - | test_main.py | ... | - | pyproject.toml | ... | - | Dockerfile | ... | - - ## Quick Start - ```bash - git clone - cd project - uv sync - uv run uvicorn main:app --reload - ``` - - ## Docker - ```bash - docker build -t rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla . - docker run -p 8000:8000 rest-api-k-ytt-j-hallinnalle-sqlite-tietokannalla - ``` - # ... (truncated) - expected_output: >- - README.md - agent: observer diff --git a/zipit/rest_api_check/test_main.py b/zipit/rest_api_check/test_main.py deleted file mode 100644 index b0eb79c..0000000 --- a/zipit/rest_api_check/test_main.py +++ /dev/null @@ -1,57 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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_user(): - response = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 201 - assert 'id' in response.json() - -def test_list_users(): - client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - response = client.get('/users/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_user_by_id(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.get(f'/users/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_user_not_found(): - response = client.get('/users/99999') - assert response.status_code == 404 - -def test_update_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.put(f'/users/{item_id}', json={"UserID":1,"Username":"Updated Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}) - assert response.status_code == 200 - -def test_delete_user(): - created = client.post('/users/', json={"UserID":1,"Username":"Test Username","Email":"Test Email","PasswordHash":"Test PasswordHash"}).json() - item_id = created['id'] - response = client.delete(f'/users/{item_id}') - assert response.status_code == 204 - response = client.get(f'/users/{item_id}') - assert response.status_code == 404 - diff --git a/zipit/template_pipeline.py b/zipit/template_pipeline.py deleted file mode 100644 index def27ef..0000000 --- a/zipit/template_pipeline.py +++ /dev/null @@ -1,652 +0,0 @@ -#!/usr/bin/env python3 -"""Kipinä Template Pipeline — rakennuspalaset + LLM-täydennys. - -LLM generoi vain JSON-speksin (entiteetit, kentät, tyypit). -Koodi kootaan mekaanisesti valmiista pohjista. -""" - -import json, re, ast, sys, time, textwrap, subprocess -from pathlib import Path -from urllib.request import urlopen, Request - -OLLAMA = "http://localhost:11434" -MODEL = "qwen2.5-coder:7b-instruct-q4_K_M" -OUTDIR = Path(__file__).parent / "template_runs" -MAX_RETRIES = 3 - -# ── Ollama-kutsu ────────────────────────────────────────────── - -def llm(system: str, user: str, temperature: float = 0.1) -> str: - body = json.dumps({ - "model": MODEL, - "messages": [ - {"role": "system", "content": system}, - {"role": "user", "content": user}, - ], - "stream": False, - "options": {"temperature": temperature, "num_predict": 4096}, - }).encode() - req = Request(f"{OLLAMA}/api/chat", data=body, headers={"Content-Type": "application/json"}) - resp = json.loads(urlopen(req, timeout=120).read()) - return resp["message"]["content"] - - -def extract_json(text: str) -> dict | None: - """Poimi JSON LLM-vastauksesta.""" - # Etsi JSON-blokki fenceistä - m = re.search(r"```(?:json)?\s*\n(.*?)```", text, re.DOTALL) - if m: - text = m.group(1).strip() - # Etsi ensimmäinen { ... } blokki - depth = 0 - start = None - for i, c in enumerate(text): - if c == '{': - if depth == 0: - start = i - depth += 1 - elif c == '}': - depth -= 1 - if depth == 0 and start is not None: - try: - return json.loads(text[start:i+1]) - except json.JSONDecodeError: - continue - return None - - -# ══════════════════════════════════════════════════════════════ -# VAIHE 1: LLM generoi JSON-speksin -# ══════════════════════════════════════════════════════════════ - -SPEC_SYSTEM = textwrap.dedent("""\ - You are a software architect. Given a project description, output a JSON specification. - - Output ONLY valid JSON, no explanations. Follow this exact schema: - - ```json - { - "project_name": "short-name", - "description": "One sentence about the project", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - {"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false, "default": null}, - {"name": "description", "sa_type": "Text", "py_type": "str | None", "nullable": true, "default": null}, - {"name": "due_date", "sa_type": "Date", "py_type": "date | None", "nullable": true, "default": null}, - {"name": "status", "sa_type": "String(20)", "py_type": "str", "nullable": false, "default": "pending"} - ] - } - ], - "extra_imports": ["from datetime import date"] - } - ``` - - RULES: - - sa_type: SQLAlchemy column type (String(N), Text, Integer, Date, DateTime, Boolean, Float) - - py_type: Python type hint for Pydantic (str, int, float, bool, date, datetime, str | None, etc.) - - Do NOT use Enum — use String(20) with a default value for status fields - - nullable: true means the field is optional - - default: null means no default, otherwise a string/number value - - extra_imports: stdlib imports needed in schemas.py (e.g. "from datetime import date" if Date fields exist) - - entity name: PascalCase singular (Todo, User, Product) - - table_name: snake_case plural (todos, users, products) - - Keep it simple: 1-2 entities max, 3-7 fields each""") - - -def generate_spec(project_description: str) -> dict | None: - """Pyydä LLM:ltä JSON-speksi.""" - for attempt in range(1, MAX_RETRIES + 1): - print(f" Speksi yritys {attempt}/{MAX_RETRIES}...", end=" ", flush=True) - t0 = time.time() - raw = llm(SPEC_SYSTEM, project_description) - elapsed = time.time() - t0 - print(f"({elapsed:.1f}s)", end=" ") - - spec = extract_json(raw) - if spec and "entities" in spec and len(spec["entities"]) > 0: - # Validoi rakenne - valid = True - for entity in spec["entities"]: - if not entity.get("name") or not entity.get("fields"): - valid = False - for field in entity.get("fields", []): - if not field.get("name") or not field.get("sa_type") or not field.get("py_type"): - valid = False - if valid: - print("✅") - return spec - - print("❌ virheellinen JSON") - - return None - - -# ══════════════════════════════════════════════════════════════ -# VAIHE 2: Koodigenerointi templateista -# ══════════════════════════════════════════════════════════════ - -def generate_models(spec: dict) -> str: - """Generoi models.py templatesta.""" - # Kerää tarvittavat SQLAlchemy-tyypit - sa_types = set() - for entity in spec["entities"]: - for field in entity["fields"]: - base_type = re.match(r"(\w+)", field["sa_type"]).group(1) - sa_types.add(base_type) - sa_types.add("Integer") - sa_imports = ", ".join(sorted(sa_types)) - - lines = [ - f"from sqlalchemy import create_engine, Column, Integer, {sa_imports}", - "from sqlalchemy.ext.declarative import declarative_base", - "from sqlalchemy.orm import sessionmaker", - "", - 'DATABASE_URL = "sqlite:///./app.db"', - 'engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})', - "SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)", - "Base = declarative_base()", - "", - ] - - for entity in spec["entities"]: - lines.append(f"class {entity['name']}(Base):") - lines.append(f' __tablename__ = "{entity["table_name"]}"') - lines.append(f" id = Column(Integer, primary_key=True, index=True)") - - for field in entity["fields"]: - parts = [f"Column({field['sa_type']}"] - if not field.get("nullable", True): - parts.append("nullable=False") - if field.get("default") is not None: - default = field["default"] - if isinstance(default, str): - parts.append(f'default="{default}"') - else: - parts.append(f"default={default}") - col_def = ", ".join(parts) + ")" - lines.append(f" {field['name']} = {col_def}") - - lines.append("") - - lines.append("Base.metadata.create_all(bind=engine)") - return "\n".join(lines) - - -def generate_schemas(spec: dict) -> str: - """Generoi schemas.py templatesta.""" - lines = ["from pydantic import BaseModel"] - - # Extra imports (esim. date, datetime) - for imp in spec.get("extra_imports", []): - lines.append(imp) - - lines.append("") - - for entity in spec["entities"]: - name = entity["name"] - - # Create schema - lines.append(f"class {name}Create(BaseModel):") - for field in entity["fields"]: - py_type = field["py_type"] - default = field.get("default") - if default is not None: - if isinstance(default, str): - lines.append(f' {field["name"]}: {py_type} = "{default}"') - else: - lines.append(f' {field["name"]}: {py_type} = {default}') - elif field.get("nullable", True) and "None" in py_type: - lines.append(f' {field["name"]}: {py_type} = None') - else: - lines.append(f' {field["name"]}: {py_type}') - lines.append("") - - # Response schema - lines.append(f"class {name}Response({name}Create):") - lines.append(" id: int") - lines.append("") - lines.append(" class Config:") - lines.append(" from_attributes = True") - lines.append("") - - return "\n".join(lines) - - -def generate_main(spec: dict) -> str: - """Generoi main.py templatesta.""" - entities = spec["entities"] - - # Imports - model_names = ", ".join(e["name"] for e in entities) - create_names = ", ".join(f'{e["name"]}Create' for e in entities) - response_names = ", ".join(f'{e["name"]}Response' for e in entities) - - lines = [ - "from fastapi import FastAPI, Depends, HTTPException", - "from sqlalchemy.orm import Session", - f"from models import Base, engine, SessionLocal, {model_names}", - f"from schemas import {create_names}, {response_names}", - "", - "app = FastAPI()", - "", - "def get_db():", - " db = SessionLocal()", - " try:", - " yield db", - " finally:", - " db.close()", - "", - ] - - for entity in entities: - name = entity["name"] - lower = name.lower() - table = entity["table_name"] - - lines.extend([ - f'@app.post("/{table}/", response_model={name}Response, status_code=201)', - f"def create_{lower}(item: {name}Create, db: Session = Depends(get_db)):", - f" db_item = {name}(**item.model_dump())", - f" db.add(db_item)", - f" db.commit()", - f" db.refresh(db_item)", - f" return db_item", - "", - f'@app.get("/{table}/", response_model=list[{name}Response])', - f"def list_{lower}s(db: Session = Depends(get_db)):", - f" return db.query({name}).all()", - "", - f'@app.get("/{table}/{{item_id}}", response_model={name}Response)', - f"def get_{lower}(item_id: int, db: Session = Depends(get_db)):", - f" item = db.query({name}).filter({name}.id == item_id).first()", - f" if not item:", - f' raise HTTPException(status_code=404, detail="{name} not found")', - f" return item", - "", - f'@app.put("/{table}/{{item_id}}", response_model={name}Response)', - f"def update_{lower}(item_id: int, item: {name}Create, db: Session = Depends(get_db)):", - f" db_item = db.query({name}).filter({name}.id == item_id).first()", - f" if not db_item:", - f' raise HTTPException(status_code=404, detail="{name} not found")', - f" for key, value in item.model_dump().items():", - f" setattr(db_item, key, value)", - f" db.commit()", - f" db.refresh(db_item)", - f" return db_item", - "", - f'@app.delete("/{table}/{{item_id}}", status_code=204)', - f"def delete_{lower}(item_id: int, db: Session = Depends(get_db)):", - f" db_item = db.query({name}).filter({name}.id == item_id).first()", - f" if not db_item:", - f' raise HTTPException(status_code=404, detail="{name} not found")', - f" db.delete(db_item)", - f" db.commit()", - "", - ]) - - return "\n".join(lines) - - -def generate_tests(spec: dict) -> str: - """Generoi test_main.py templatesta.""" - entities = spec["entities"] - - lines = [ - "import pytest", - "from fastapi.testclient import TestClient", - "from sqlalchemy import create_engine", - "from sqlalchemy.orm import sessionmaker", - "from main import app, get_db", - "from models import Base", - "", - 'TEST_DB = "sqlite:///./test.db"', - 'test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False})', - "TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine)", - "Base.metadata.create_all(bind=test_engine)", - "", - "def override_get_db():", - " db = TestSession()", - " try:", - " yield db", - " finally:", - " db.close()", - "", - "app.dependency_overrides[get_db] = override_get_db", - "client = TestClient(app)", - "", - ] - - for entity in entities: - name = entity["name"] - lower = name.lower() - table = entity["table_name"] - - # Rakenna esimerkki-JSON testidatasta - test_data = {} - for field in entity["fields"]: - if "str" in field["py_type"]: - test_data[field["name"]] = f"Test {field['name']}" - elif "int" in field["py_type"]: - test_data[field["name"]] = 1 - elif "float" in field["py_type"]: - test_data[field["name"]] = 1.0 - elif "bool" in field["py_type"]: - test_data[field["name"]] = True - elif "date" in field["py_type"].lower(): - test_data[field["name"]] = "2024-01-15" - if field.get("default") is not None: - test_data[field["name"]] = field["default"] - # Poista None-arvoiset optional-kentät testidatasta - test_data = {k: v for k, v in test_data.items() if v is not None} - - test_json = json.dumps(test_data) - - update_data = dict(test_data) - first_str_field = next((f["name"] for f in entity["fields"] if "str" in f["py_type"] and f["name"] != "status"), None) - if first_str_field: - update_data[first_str_field] = f"Updated {first_str_field}" - update_json = json.dumps(update_data) - - lines.extend([ - f"def test_create_{lower}():", - f" response = client.post('/{table}/', json={test_json})", - f" assert response.status_code == 201", - f" data = response.json()", - f' assert "id" in data', - "", - f"def test_list_{lower}s():", - f" client.post('/{table}/', json={test_json})", - f" response = client.get('/{table}/')", - f" assert response.status_code == 200", - f" assert len(response.json()) >= 1", - "", - f"def test_get_{lower}_by_id():", - f" created = client.post('/{table}/', json={test_json}).json()", - f" item_id = created['id']", - f" response = client.get(f'/{table}/{{item_id}}')", - f" assert response.status_code == 200", - f" assert response.json()['id'] == item_id", - "", - f"def test_get_{lower}_not_found():", - f" response = client.get('/{table}/99999')", - f" assert response.status_code == 404", - "", - f"def test_update_{lower}():", - f" created = client.post('/{table}/', json={test_json}).json()", - f" item_id = created['id']", - f" response = client.put(f'/{table}/{{item_id}}', json={update_json})", - f" assert response.status_code == 200", - "", - f"def test_delete_{lower}():", - f" created = client.post('/{table}/', json={test_json}).json()", - f" item_id = created['id']", - f" response = client.delete(f'/{table}/{{item_id}}')", - f" assert response.status_code == 204", - f" response = client.get(f'/{table}/{{item_id}}')", - f" assert response.status_code == 404", - "", - ]) - - return "\n".join(lines) - - -def generate_pyproject(spec: dict) -> str: - """Generoi pyproject.toml templatesta.""" - name = spec.get("project_name", "app").lower().replace(" ", "-") - return textwrap.dedent(f"""\ - [project] - name = "{name}" - version = "0.1.0" - requires-python = ">=3.11" - dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", - ] - """) - - -def generate_dockerfile() -> str: - """Generoi Dockerfile templatesta — aina sama.""" - return textwrap.dedent("""\ - 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"] - """) - - -# ══════════════════════════════════════════════════════════════ -# VAIHE 3: Validointi -# ══════════════════════════════════════════════════════════════ - -def validate_python(code: str, filename: str) -> list[str]: - """Syntaksitarkistus.""" - try: - ast.parse(code, filename=filename) - return [] - except SyntaxError as e: - lines = code.splitlines() - bad = lines[e.lineno - 1] if e.lineno and e.lineno <= len(lines) else "?" - return [f"SyntaxError rivi {e.lineno}: {e.msg}. Koodi: `{bad.strip()}`"] - - -# ══════════════════════════════════════════════════════════════ -# VAIHE 4: Docker-testaus -# ══════════════════════════════════════════════════════════════ - -def docker_test(run_dir: Path, spec: dict) -> dict: - """Rakenna ja testaa generoitu projekti Dockerilla.""" - results = {"build": False, "pytest": False, "api": False, "errors": []} - - compose = textwrap.dedent("""\ - services: - app: - build: . - ports: - - "18765:8000" - """) - (run_dir / "docker-compose.yml").write_text(compose) - - # Cleanup - subprocess.run(["docker", "compose", "down", "--remove-orphans", "-t", "2"], - cwd=run_dir, capture_output=True, timeout=15) - - # 1. Build - print(" 🔨 Docker build...", end=" ", flush=True) - r = subprocess.run(["docker", "compose", "build", "--no-cache"], - cwd=run_dir, capture_output=True, text=True, timeout=120) - if r.returncode != 0: - results["errors"].append(f"Build failed:\n{r.stderr[-500:]}") - print("❌") - print(f" {r.stderr[-200:]}") - return results - results["build"] = True - print("✅") - - # 2. Pytest - print(" 🧪 pytest...", end=" ", flush=True) - r = subprocess.run( - ["docker", "compose", "run", "--rm", "--no-deps", "app", - "uv", "run", "pytest", "test_main.py", "-v", "--tb=short"], - cwd=run_dir, capture_output=True, text=True, timeout=90) - if r.returncode == 0: - results["pytest"] = True - # Laske testit - passed = len(re.findall(r" PASSED", r.stdout)) - print(f"✅ ({passed} tests passed)") - else: - output = r.stdout + r.stderr - results["errors"].append(f"pytest exit {r.returncode}:\n{output[-800:]}") - failed = re.findall(r"FAILED (.+?)(?:\s|$)", output) - print(f"❌ ({len(failed)} failed)") - for f in failed[:5]: - print(f" FAILED: {f}") - - # 3. API smoke test - print(" 🌐 API smoke test...", end=" ", flush=True) - subprocess.run(["docker", "compose", "up", "-d"], - cwd=run_dir, capture_output=True, timeout=30) - - api_ok = False - for _ in range(15): - time.sleep(1) - try: - resp = urlopen("http://localhost:18765/docs", timeout=3) - if resp.status == 200: - api_ok = True - break - except Exception: - continue - - if api_ok: - entity = spec["entities"][0] - table = entity["table_name"] - test_data = {} - for field in entity["fields"]: - if "str" in field["py_type"]: - test_data[field["name"]] = f"API test" - elif "int" in field["py_type"]: - test_data[field["name"]] = 1 - elif "date" in field["py_type"].lower(): - test_data[field["name"]] = "2024-01-15" - if field.get("default") is not None: - test_data[field["name"]] = field["default"] - test_data = {k: v for k, v in test_data.items() if v is not None} - - try: - req = Request( - f"http://localhost:18765/{table}/", - data=json.dumps(test_data).encode(), - headers={"Content-Type": "application/json"}, - method="POST" - ) - resp = urlopen(req, timeout=5) - if resp.status == 201: - body = json.loads(resp.read()) - if "id" in body: - results["api"] = True - print(f"✅ POST /{table}/ → 201, id={body['id']}") - else: - print("❌ POST 201 mutta ei id:tä") - else: - print(f"❌ POST → {resp.status}") - except Exception as e: - results["errors"].append(f"API: {e}") - print(f"❌ {e}") - else: - results["errors"].append("Kontti ei käynnistynyt 15s:ssa") - print("❌ timeout") - - # Cleanup - subprocess.run(["docker", "compose", "down", "-t", "2"], - cwd=run_dir, capture_output=True, timeout=15) - - return results - - -# ══════════════════════════════════════════════════════════════ -# PIPELINE -# ══════════════════════════════════════════════════════════════ - -def run_template_pipeline(run_id: str, project_description: str) -> dict: - run_dir = OUTDIR / run_id - run_dir.mkdir(parents=True, exist_ok=True) - results = {"run_id": run_id, "model": MODEL, "description": project_description} - - print(f"\n{'='*60}") - print(f" Template Pipeline: {project_description}") - print(f"{'='*60}") - - # 1. JSON-speksi LLM:ltä - print(f"\n── Vaihe 1: JSON-speksi ──") - spec = generate_spec(project_description) - if not spec: - print(" ❌ Speksin generointi epäonnistui") - results["error"] = "spec generation failed" - return results - - (run_dir / "spec.json").write_text(json.dumps(spec, indent=2, ensure_ascii=False)) - results["spec"] = spec - - for entity in spec["entities"]: - print(f" 📦 {entity['name']} ({entity['table_name']}): {len(entity['fields'])} kenttää") - - # 2. Koodigenerointi templateista - print(f"\n── Vaihe 2: Koodigenerointi ──") - files = { - "models.py": generate_models(spec), - "schemas.py": generate_schemas(spec), - "main.py": generate_main(spec), - "test_main.py": generate_tests(spec), - "pyproject.toml": generate_pyproject(spec), - "Dockerfile": generate_dockerfile(), - } - - all_valid = True - for filename, code in files.items(): - (run_dir / filename).write_text(code) - if filename.endswith(".py"): - errors = validate_python(code, filename) - if errors: - print(f" ❌ {filename}: {errors}") - all_valid = False - else: - print(f" ✅ {filename} ({len(code.splitlines())} riviä)") - else: - print(f" ✅ {filename} ({len(code.splitlines())} riviä)") - - results["files"] = {k: len(v) for k, v in files.items()} - results["valid"] = all_valid - - if not all_valid: - print("\n ⏭️ Docker-testi ohitettu — validointivirheitä") - return results - - # 3. Docker-testaus - print(f"\n── Vaihe 3: Docker ──") - docker_results = docker_test(run_dir, spec) - results["docker"] = docker_results - - # 4. Yhteenveto - print(f"\n{'='*60}") - print(f" YHTEENVETO: {run_id}") - print(f"{'='*60}") - print(f" Speksi: ✅") - print(f" Syntaksi: {'✅' if all_valid else '❌'}") - print(f" Build: {'✅' if docker_results['build'] else '❌'}") - print(f" Pytest: {'✅' if docker_results['pytest'] else '❌'}") - print(f" API: {'✅' if docker_results['api'] else '❌'}") - - score = sum([all_valid, docker_results["build"], docker_results["pytest"], docker_results["api"]]) - print(f" Score: {score}/4") - - (run_dir / "report.json").write_text(json.dumps(results, indent=2, ensure_ascii=False)) - return results - - -# ── Main ────────────────────────────────────────────────────── - -if __name__ == "__main__": - description = "Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit" - run_id = f"tmpl_{int(time.time())}" - - args = sys.argv[1:] - if args: - run_id = args[0] - if len(args) > 1: - description = " ".join(args[1:]) - - run_template_pipeline(run_id, description) diff --git a/zipit/template_runs/tmpl_blog/Dockerfile b/zipit/template_runs/tmpl_blog/Dockerfile deleted file mode 100644 index 60a1cf4..0000000 --- a/zipit/template_runs/tmpl_blog/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] diff --git a/zipit/template_runs/tmpl_blog/docker-compose.yml b/zipit/template_runs/tmpl_blog/docker-compose.yml deleted file mode 100644 index d7c5fae..0000000 --- a/zipit/template_runs/tmpl_blog/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - build: . - ports: - - "18765:8000" diff --git a/zipit/template_runs/tmpl_blog/main.py b/zipit/template_runs/tmpl_blog/main.py deleted file mode 100644 index a345ea7..0000000 --- a/zipit/template_runs/tmpl_blog/main.py +++ /dev/null @@ -1,127 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, User, Article, Comment -from schemas import UserCreate, ArticleCreate, CommentCreate, UserResponse, ArticleResponse, CommentResponse - -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/users/", response_model=UserResponse, status_code=201) -def create_user(item: UserCreate, db: Session = Depends(get_db)): - db_item = User(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/users/", response_model=list[UserResponse]) -def list_users(db: Session = Depends(get_db)): - return db.query(User).all() - -@app.get("/users/{item_id}", response_model=UserResponse) -def get_user(item_id: int, db: Session = Depends(get_db)): - item = db.query(User).filter(User.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="User not found") - return item - -@app.put("/users/{item_id}", response_model=UserResponse) -def update_user(item_id: int, item: UserCreate, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/users/{item_id}", status_code=204) -def delete_user(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(User).filter(User.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="User not found") - db.delete(db_item) - db.commit() - -@app.post("/articles/", response_model=ArticleResponse, status_code=201) -def create_article(item: ArticleCreate, db: Session = Depends(get_db)): - db_item = Article(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/articles/", response_model=list[ArticleResponse]) -def list_articles(db: Session = Depends(get_db)): - return db.query(Article).all() - -@app.get("/articles/{item_id}", response_model=ArticleResponse) -def get_article(item_id: int, db: Session = Depends(get_db)): - item = db.query(Article).filter(Article.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Article not found") - return item - -@app.put("/articles/{item_id}", response_model=ArticleResponse) -def update_article(item_id: int, item: ArticleCreate, db: Session = Depends(get_db)): - db_item = db.query(Article).filter(Article.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Article not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/articles/{item_id}", status_code=204) -def delete_article(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Article).filter(Article.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Article not found") - db.delete(db_item) - db.commit() - -@app.post("/comments/", response_model=CommentResponse, status_code=201) -def create_comment(item: CommentCreate, db: Session = Depends(get_db)): - db_item = Comment(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/comments/", response_model=list[CommentResponse]) -def list_comments(db: Session = Depends(get_db)): - return db.query(Comment).all() - -@app.get("/comments/{item_id}", response_model=CommentResponse) -def get_comment(item_id: int, db: Session = Depends(get_db)): - item = db.query(Comment).filter(Comment.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Comment not found") - return item - -@app.put("/comments/{item_id}", response_model=CommentResponse) -def update_comment(item_id: int, item: CommentCreate, db: Session = Depends(get_db)): - db_item = db.query(Comment).filter(Comment.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Comment not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/comments/{item_id}", status_code=204) -def delete_comment(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Comment).filter(Comment.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Comment not found") - db.delete(db_item) - db.commit() diff --git a/zipit/template_runs/tmpl_blog/models.py b/zipit/template_runs/tmpl_blog/models.py deleted file mode 100644 index c07325c..0000000 --- a/zipit/template_runs/tmpl_blog/models.py +++ /dev/null @@ -1,31 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, Integer, String, Text -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class User(Base): - __tablename__ = "users" - id = Column(Integer, primary_key=True, index=True) - username = Column(String(255), nullable=False) - email = Column(String(255), nullable=False) - password_hash = Column(Text, nullable=False) - -class Article(Base): - __tablename__ = "articles" - id = Column(Integer, primary_key=True, index=True) - title = Column(String(255), nullable=False) - content = Column(Text, nullable=False) - author_id = Column(Integer, nullable=False) - -class Comment(Base): - __tablename__ = "comments" - id = Column(Integer, primary_key=True, index=True) - content = Column(Text, nullable=False) - article_id = Column(Integer, nullable=False) - author_id = Column(Integer, nullable=False) - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/template_runs/tmpl_blog/pyproject.toml b/zipit/template_runs/tmpl_blog/pyproject.toml deleted file mode 100644 index 9a454e6..0000000 --- a/zipit/template_runs/tmpl_blog/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "blogialusta" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] diff --git a/zipit/template_runs/tmpl_blog/report.json b/zipit/template_runs/tmpl_blog/report.json deleted file mode 100644 index 0de840c..0000000 --- a/zipit/template_runs/tmpl_blog/report.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "run_id": "tmpl_blog", - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "description": "Blogialusta: käyttäjät voivat luoda artikkeleita ja kommentoida", - "spec": { - "project_name": "blogialusta", - "description": "A blogging platform where users can create articles and comment on them.", - "entities": [ - { - "name": "User", - "table_name": "users", - "fields": [ - { - "name": "username", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "email", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "password_hash", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - } - ] - }, - { - "name": "Article", - "table_name": "articles", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "content", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "author_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - } - ] - }, - { - "name": "Comment", - "table_name": "comments", - "fields": [ - { - "name": "content", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "article_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "author_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] - }, - "files": { - "models.py": 1175, - "schemas.py": 613, - "main.py": 4781, - "test_main.py": 5488, - "pyproject.toml": 180, - "Dockerfile": 339 - }, - "valid": true, - "docker": { - "build": true, - "pytest": true, - "api": true, - "errors": [] - } -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_blog/schemas.py b/zipit/template_runs/tmpl_blog/schemas.py deleted file mode 100644 index 0649a74..0000000 --- a/zipit/template_runs/tmpl_blog/schemas.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel -from datetime import date - -class UserCreate(BaseModel): - username: str - email: str - password_hash: str - -class UserResponse(UserCreate): - id: int - - class Config: - from_attributes = True - -class ArticleCreate(BaseModel): - title: str - content: str - author_id: int - -class ArticleResponse(ArticleCreate): - id: int - - class Config: - from_attributes = True - -class CommentCreate(BaseModel): - content: str - article_id: int - author_id: int - -class CommentResponse(CommentCreate): - id: int - - class Config: - from_attributes = True diff --git a/zipit/template_runs/tmpl_blog/spec.json b/zipit/template_runs/tmpl_blog/spec.json deleted file mode 100644 index d1f3c4c..0000000 --- a/zipit/template_runs/tmpl_blog/spec.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "project_name": "blogialusta", - "description": "A blogging platform where users can create articles and comment on them.", - "entities": [ - { - "name": "User", - "table_name": "users", - "fields": [ - { - "name": "username", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "email", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "password_hash", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - } - ] - }, - { - "name": "Article", - "table_name": "articles", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "content", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "author_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - } - ] - }, - { - "name": "Comment", - "table_name": "comments", - "fields": [ - { - "name": "content", - "sa_type": "Text", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "article_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "author_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_blog/test_main.py b/zipit/template_runs/tmpl_blog/test_main.py deleted file mode 100644 index 6a0c8c8..0000000 --- a/zipit/template_runs/tmpl_blog/test_main.py +++ /dev/null @@ -1,132 +0,0 @@ -import pytest -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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_user(): - response = client.post('/users/', json={"username": "Test username", "email": "Test email", "password_hash": "Test password_hash"}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_users(): - client.post('/users/', json={"username": "Test username", "email": "Test email", "password_hash": "Test password_hash"}) - response = client.get('/users/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_user_by_id(): - created = client.post('/users/', json={"username": "Test username", "email": "Test email", "password_hash": "Test password_hash"}).json() - item_id = created['id'] - response = client.get(f'/users/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_user_not_found(): - response = client.get('/users/99999') - assert response.status_code == 404 - -def test_update_user(): - created = client.post('/users/', json={"username": "Test username", "email": "Test email", "password_hash": "Test password_hash"}).json() - item_id = created['id'] - response = client.put(f'/users/{item_id}', json={"username": "Updated username", "email": "Test email", "password_hash": "Test password_hash"}) - assert response.status_code == 200 - -def test_delete_user(): - created = client.post('/users/', json={"username": "Test username", "email": "Test email", "password_hash": "Test password_hash"}).json() - item_id = created['id'] - response = client.delete(f'/users/{item_id}') - assert response.status_code == 204 - response = client.get(f'/users/{item_id}') - assert response.status_code == 404 - -def test_create_article(): - response = client.post('/articles/', json={"title": "Test title", "content": "Test content", "author_id": 1}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_articles(): - client.post('/articles/', json={"title": "Test title", "content": "Test content", "author_id": 1}) - response = client.get('/articles/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_article_by_id(): - created = client.post('/articles/', json={"title": "Test title", "content": "Test content", "author_id": 1}).json() - item_id = created['id'] - response = client.get(f'/articles/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_article_not_found(): - response = client.get('/articles/99999') - assert response.status_code == 404 - -def test_update_article(): - created = client.post('/articles/', json={"title": "Test title", "content": "Test content", "author_id": 1}).json() - item_id = created['id'] - response = client.put(f'/articles/{item_id}', json={"title": "Updated title", "content": "Test content", "author_id": 1}) - assert response.status_code == 200 - -def test_delete_article(): - created = client.post('/articles/', json={"title": "Test title", "content": "Test content", "author_id": 1}).json() - item_id = created['id'] - response = client.delete(f'/articles/{item_id}') - assert response.status_code == 204 - response = client.get(f'/articles/{item_id}') - assert response.status_code == 404 - -def test_create_comment(): - response = client.post('/comments/', json={"content": "Test content", "article_id": 1, "author_id": 1}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_comments(): - client.post('/comments/', json={"content": "Test content", "article_id": 1, "author_id": 1}) - response = client.get('/comments/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_comment_by_id(): - created = client.post('/comments/', json={"content": "Test content", "article_id": 1, "author_id": 1}).json() - item_id = created['id'] - response = client.get(f'/comments/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_comment_not_found(): - response = client.get('/comments/99999') - assert response.status_code == 404 - -def test_update_comment(): - created = client.post('/comments/', json={"content": "Test content", "article_id": 1, "author_id": 1}).json() - item_id = created['id'] - response = client.put(f'/comments/{item_id}', json={"content": "Updated content", "article_id": 1, "author_id": 1}) - assert response.status_code == 200 - -def test_delete_comment(): - created = client.post('/comments/', json={"content": "Test content", "article_id": 1, "author_id": 1}).json() - item_id = created['id'] - response = client.delete(f'/comments/{item_id}') - assert response.status_code == 204 - response = client.get(f'/comments/{item_id}') - assert response.status_code == 404 diff --git a/zipit/template_runs/tmpl_inventory/Dockerfile b/zipit/template_runs/tmpl_inventory/Dockerfile deleted file mode 100644 index 60a1cf4..0000000 --- a/zipit/template_runs/tmpl_inventory/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] diff --git a/zipit/template_runs/tmpl_inventory/docker-compose.yml b/zipit/template_runs/tmpl_inventory/docker-compose.yml deleted file mode 100644 index d7c5fae..0000000 --- a/zipit/template_runs/tmpl_inventory/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - build: . - ports: - - "18765:8000" diff --git a/zipit/template_runs/tmpl_inventory/main.py b/zipit/template_runs/tmpl_inventory/main.py deleted file mode 100644 index 413434b..0000000 --- a/zipit/template_runs/tmpl_inventory/main.py +++ /dev/null @@ -1,127 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, Product, StorageLocation, Transfer -from schemas import ProductCreate, StorageLocationCreate, TransferCreate, ProductResponse, StorageLocationResponse, TransferResponse - -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/products/", response_model=ProductResponse, status_code=201) -def create_product(item: ProductCreate, db: Session = Depends(get_db)): - db_item = Product(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/products/", response_model=list[ProductResponse]) -def list_products(db: Session = Depends(get_db)): - return db.query(Product).all() - -@app.get("/products/{item_id}", response_model=ProductResponse) -def get_product(item_id: int, db: Session = Depends(get_db)): - item = db.query(Product).filter(Product.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Product not found") - return item - -@app.put("/products/{item_id}", response_model=ProductResponse) -def update_product(item_id: int, item: ProductCreate, db: Session = Depends(get_db)): - db_item = db.query(Product).filter(Product.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Product not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/products/{item_id}", status_code=204) -def delete_product(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Product).filter(Product.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Product not found") - db.delete(db_item) - db.commit() - -@app.post("/storage_locations/", response_model=StorageLocationResponse, status_code=201) -def create_storagelocation(item: StorageLocationCreate, db: Session = Depends(get_db)): - db_item = StorageLocation(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/storage_locations/", response_model=list[StorageLocationResponse]) -def list_storagelocations(db: Session = Depends(get_db)): - return db.query(StorageLocation).all() - -@app.get("/storage_locations/{item_id}", response_model=StorageLocationResponse) -def get_storagelocation(item_id: int, db: Session = Depends(get_db)): - item = db.query(StorageLocation).filter(StorageLocation.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="StorageLocation not found") - return item - -@app.put("/storage_locations/{item_id}", response_model=StorageLocationResponse) -def update_storagelocation(item_id: int, item: StorageLocationCreate, db: Session = Depends(get_db)): - db_item = db.query(StorageLocation).filter(StorageLocation.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="StorageLocation not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/storage_locations/{item_id}", status_code=204) -def delete_storagelocation(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(StorageLocation).filter(StorageLocation.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="StorageLocation not found") - db.delete(db_item) - db.commit() - -@app.post("/transfers/", response_model=TransferResponse, status_code=201) -def create_transfer(item: TransferCreate, db: Session = Depends(get_db)): - db_item = Transfer(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/transfers/", response_model=list[TransferResponse]) -def list_transfers(db: Session = Depends(get_db)): - return db.query(Transfer).all() - -@app.get("/transfers/{item_id}", response_model=TransferResponse) -def get_transfer(item_id: int, db: Session = Depends(get_db)): - item = db.query(Transfer).filter(Transfer.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Transfer not found") - return item - -@app.put("/transfers/{item_id}", response_model=TransferResponse) -def update_transfer(item_id: int, item: TransferCreate, db: Session = Depends(get_db)): - db_item = db.query(Transfer).filter(Transfer.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Transfer not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/transfers/{item_id}", status_code=204) -def delete_transfer(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Transfer).filter(Transfer.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Transfer not found") - db.delete(db_item) - db.commit() diff --git a/zipit/template_runs/tmpl_inventory/models.py b/zipit/template_runs/tmpl_inventory/models.py deleted file mode 100644 index e684137..0000000 --- a/zipit/template_runs/tmpl_inventory/models.py +++ /dev/null @@ -1,34 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, Integer, String, Text -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class Product(Base): - __tablename__ = "products" - id = Column(Integer, primary_key=True, index=True) - product_id = Column(Integer, nullable=False) - name = Column(String(255), nullable=False) - description = Column(Text) - category = Column(String(50), nullable=False) - -class StorageLocation(Base): - __tablename__ = "storage_locations" - id = Column(Integer, primary_key=True, index=True) - location_id = Column(Integer, nullable=False) - name = Column(String(255), nullable=False) - capacity = Column(Integer, nullable=False, default=0) - -class Transfer(Base): - __tablename__ = "transfers" - id = Column(Integer, primary_key=True, index=True) - transfer_id = Column(Integer, nullable=False) - product_id = Column(Integer, nullable=False) - from_location_id = Column(Integer, nullable=False) - to_location_id = Column(Integer, nullable=False) - quantity = Column(Integer, nullable=False, default=0) - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/template_runs/tmpl_inventory/pyproject.toml b/zipit/template_runs/tmpl_inventory/pyproject.toml deleted file mode 100644 index 5f58efe..0000000 --- a/zipit/template_runs/tmpl_inventory/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "warehouse_management" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] diff --git a/zipit/template_runs/tmpl_inventory/report.json b/zipit/template_runs/tmpl_inventory/report.json deleted file mode 100644 index a4a8730..0000000 --- a/zipit/template_runs/tmpl_inventory/report.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "run_id": "tmpl_inventory", - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "description": "Varastonhallinta: tuotteet, varastot, siirrot varastojen välillä", - "spec": { - "project_name": "warehouse_management", - "description": "A system for managing warehouse operations including products, storage locations, and transfers between locations.", - "entities": [ - { - "name": "Product", - "table_name": "products", - "fields": [ - { - "name": "product_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "name", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "category", - "sa_type": "String(50)", - "py_type": "str", - "nullable": false, - "default": null - } - ] - }, - { - "name": "StorageLocation", - "table_name": "storage_locations", - "fields": [ - { - "name": "location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "name", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "capacity", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": 0 - } - ] - }, - { - "name": "Transfer", - "table_name": "transfers", - "fields": [ - { - "name": "transfer_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "product_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "from_location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "to_location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "quantity", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": 0 - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] - }, - "files": { - "models.py": 1370, - "schemas.py": 743, - "main.py": 5146, - "test_main.py": 5898, - "pyproject.toml": 189, - "Dockerfile": 339 - }, - "valid": true, - "docker": { - "build": true, - "pytest": true, - "api": true, - "errors": [] - } -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_inventory/schemas.py b/zipit/template_runs/tmpl_inventory/schemas.py deleted file mode 100644 index 455fca6..0000000 --- a/zipit/template_runs/tmpl_inventory/schemas.py +++ /dev/null @@ -1,38 +0,0 @@ -from pydantic import BaseModel -from datetime import date - -class ProductCreate(BaseModel): - product_id: int - name: str - description: str | None = None - category: str - -class ProductResponse(ProductCreate): - id: int - - class Config: - from_attributes = True - -class StorageLocationCreate(BaseModel): - location_id: int - name: str - capacity: int = 0 - -class StorageLocationResponse(StorageLocationCreate): - id: int - - class Config: - from_attributes = True - -class TransferCreate(BaseModel): - transfer_id: int - product_id: int - from_location_id: int - to_location_id: int - quantity: int = 0 - -class TransferResponse(TransferCreate): - id: int - - class Config: - from_attributes = True diff --git a/zipit/template_runs/tmpl_inventory/spec.json b/zipit/template_runs/tmpl_inventory/spec.json deleted file mode 100644 index 2b464a5..0000000 --- a/zipit/template_runs/tmpl_inventory/spec.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "project_name": "warehouse_management", - "description": "A system for managing warehouse operations including products, storage locations, and transfers between locations.", - "entities": [ - { - "name": "Product", - "table_name": "products", - "fields": [ - { - "name": "product_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "name", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "category", - "sa_type": "String(50)", - "py_type": "str", - "nullable": false, - "default": null - } - ] - }, - { - "name": "StorageLocation", - "table_name": "storage_locations", - "fields": [ - { - "name": "location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "name", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "capacity", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": 0 - } - ] - }, - { - "name": "Transfer", - "table_name": "transfers", - "fields": [ - { - "name": "transfer_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "product_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "from_location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "to_location_id", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": null - }, - { - "name": "quantity", - "sa_type": "Integer", - "py_type": "int", - "nullable": false, - "default": 0 - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_inventory/test_main.py b/zipit/template_runs/tmpl_inventory/test_main.py deleted file mode 100644 index 89d4064..0000000 --- a/zipit/template_runs/tmpl_inventory/test_main.py +++ /dev/null @@ -1,132 +0,0 @@ -import pytest -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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_product(): - response = client.post('/products/', json={"product_id": 1, "name": "Test name", "description": "Test description", "category": "Test category"}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_products(): - client.post('/products/', json={"product_id": 1, "name": "Test name", "description": "Test description", "category": "Test category"}) - response = client.get('/products/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_product_by_id(): - created = client.post('/products/', json={"product_id": 1, "name": "Test name", "description": "Test description", "category": "Test category"}).json() - item_id = created['id'] - response = client.get(f'/products/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_product_not_found(): - response = client.get('/products/99999') - assert response.status_code == 404 - -def test_update_product(): - created = client.post('/products/', json={"product_id": 1, "name": "Test name", "description": "Test description", "category": "Test category"}).json() - item_id = created['id'] - response = client.put(f'/products/{item_id}', json={"product_id": 1, "name": "Updated name", "description": "Test description", "category": "Test category"}) - assert response.status_code == 200 - -def test_delete_product(): - created = client.post('/products/', json={"product_id": 1, "name": "Test name", "description": "Test description", "category": "Test category"}).json() - item_id = created['id'] - response = client.delete(f'/products/{item_id}') - assert response.status_code == 204 - response = client.get(f'/products/{item_id}') - assert response.status_code == 404 - -def test_create_storagelocation(): - response = client.post('/storage_locations/', json={"location_id": 1, "name": "Test name", "capacity": 0}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_storagelocations(): - client.post('/storage_locations/', json={"location_id": 1, "name": "Test name", "capacity": 0}) - response = client.get('/storage_locations/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_storagelocation_by_id(): - created = client.post('/storage_locations/', json={"location_id": 1, "name": "Test name", "capacity": 0}).json() - item_id = created['id'] - response = client.get(f'/storage_locations/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_storagelocation_not_found(): - response = client.get('/storage_locations/99999') - assert response.status_code == 404 - -def test_update_storagelocation(): - created = client.post('/storage_locations/', json={"location_id": 1, "name": "Test name", "capacity": 0}).json() - item_id = created['id'] - response = client.put(f'/storage_locations/{item_id}', json={"location_id": 1, "name": "Updated name", "capacity": 0}) - assert response.status_code == 200 - -def test_delete_storagelocation(): - created = client.post('/storage_locations/', json={"location_id": 1, "name": "Test name", "capacity": 0}).json() - item_id = created['id'] - response = client.delete(f'/storage_locations/{item_id}') - assert response.status_code == 204 - response = client.get(f'/storage_locations/{item_id}') - assert response.status_code == 404 - -def test_create_transfer(): - response = client.post('/transfers/', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_transfers(): - client.post('/transfers/', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}) - response = client.get('/transfers/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_transfer_by_id(): - created = client.post('/transfers/', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}).json() - item_id = created['id'] - response = client.get(f'/transfers/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_transfer_not_found(): - response = client.get('/transfers/99999') - assert response.status_code == 404 - -def test_update_transfer(): - created = client.post('/transfers/', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}).json() - item_id = created['id'] - response = client.put(f'/transfers/{item_id}', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}) - assert response.status_code == 200 - -def test_delete_transfer(): - created = client.post('/transfers/', json={"transfer_id": 1, "product_id": 1, "from_location_id": 1, "to_location_id": 1, "quantity": 0}).json() - item_id = created['id'] - response = client.delete(f'/transfers/{item_id}') - assert response.status_code == 204 - response = client.get(f'/transfers/{item_id}') - assert response.status_code == 404 diff --git a/zipit/template_runs/tmpl_v1/Dockerfile b/zipit/template_runs/tmpl_v1/Dockerfile deleted file mode 100644 index 60a1cf4..0000000 --- a/zipit/template_runs/tmpl_v1/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] diff --git a/zipit/template_runs/tmpl_v1/docker-compose.yml b/zipit/template_runs/tmpl_v1/docker-compose.yml deleted file mode 100644 index d7c5fae..0000000 --- a/zipit/template_runs/tmpl_v1/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - build: . - ports: - - "18765:8000" diff --git a/zipit/template_runs/tmpl_v1/main.py b/zipit/template_runs/tmpl_v1/main.py deleted file mode 100644 index d1cd61f..0000000 --- a/zipit/template_runs/tmpl_v1/main.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, Todo -from schemas import TodoCreate, TodoResponse - -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(item: TodoCreate, db: Session = Depends(get_db)): - db_item = Todo(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/todos/", response_model=list[TodoResponse]) -def list_todos(db: Session = Depends(get_db)): - return db.query(Todo).all() - -@app.get("/todos/{item_id}", response_model=TodoResponse) -def get_todo(item_id: int, db: Session = Depends(get_db)): - item = db.query(Todo).filter(Todo.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Todo not found") - return item - -@app.put("/todos/{item_id}", response_model=TodoResponse) -def update_todo(item_id: int, item: TodoCreate, db: Session = Depends(get_db)): - db_item = db.query(Todo).filter(Todo.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Todo not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/todos/{item_id}", status_code=204) -def delete_todo(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Todo).filter(Todo.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Todo not found") - db.delete(db_item) - db.commit() diff --git a/zipit/template_runs/tmpl_v1/models.py b/zipit/template_runs/tmpl_v1/models.py deleted file mode 100644 index 14e334f..0000000 --- a/zipit/template_runs/tmpl_v1/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, Date, Integer, String, Text -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.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) - due_date = Column(Date) - status = Column(String(20), nullable=False, default="pending") - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v1/pyproject.toml b/zipit/template_runs/tmpl_v1/pyproject.toml deleted file mode 100644 index 1e75d19..0000000 --- a/zipit/template_runs/tmpl_v1/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "todo-app" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] diff --git a/zipit/template_runs/tmpl_v1/report.json b/zipit/template_runs/tmpl_v1/report.json deleted file mode 100644 index 5f5fbbf..0000000 --- a/zipit/template_runs/tmpl_v1/report.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "run_id": "tmpl_v1", - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "description": "Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit", - "spec": { - "project_name": "todo-app", - "description": "A simple Todo application with CRUD endpoints using FastAPI and SQLite.", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "due_date", - "sa_type": "Date", - "py_type": "date | None", - "nullable": true, - "default": null - }, - { - "name": "status", - "sa_type": "String(20)", - "py_type": "str", - "nullable": false, - "default": "pending" - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] - }, - "files": { - "models.py": 712, - "schemas.py": 293, - "main.py": 1711, - "test_main.py": 2439, - "pyproject.toml": 177, - "Dockerfile": 339 - }, - "valid": true, - "docker": { - "build": true, - "pytest": false, - "api": true, - "errors": [ - "pytest exit 1:\nCreate):\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n=========================== short test summary info ============================\nFAILED test_main.py::test_create_todo - sqlalchemy.exc.OperationalError: (sql...\nFAILED test_main.py::test_list_todos - sqlalchemy.exc.OperationalError: (sqli...\nFAILED test_main.py::test_get_todo_by_id - sqlalchemy.exc.OperationalError: (...\nFAILED test_main.py::test_get_todo_not_found - sqlalchemy.exc.OperationalErro...\nFAILED test_main.py::test_update_todo - sqlalchemy.exc.OperationalError: (sql...\nFAILED test_main.py::test_delete_todo - sqlalchemy.exc.OperationalError: (sql...\n======================== 6 failed, 2 warnings in 2.54s =========================\n Network tmpl_v1_default Creating\n Network tmpl_v1_default Created\n\n" - ] - } -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v1/schemas.py b/zipit/template_runs/tmpl_v1/schemas.py deleted file mode 100644 index a453b16..0000000 --- a/zipit/template_runs/tmpl_v1/schemas.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel -from datetime import date - -class TodoCreate(BaseModel): - title: str - description: str | None = None - due_date: date | None = None - status: str = "pending" - -class TodoResponse(TodoCreate): - id: int - - class Config: - from_attributes = True diff --git a/zipit/template_runs/tmpl_v1/spec.json b/zipit/template_runs/tmpl_v1/spec.json deleted file mode 100644 index e1affa8..0000000 --- a/zipit/template_runs/tmpl_v1/spec.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "project_name": "todo-app", - "description": "A simple Todo application with CRUD endpoints using FastAPI and SQLite.", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "due_date", - "sa_type": "Date", - "py_type": "date | None", - "nullable": true, - "default": null - }, - { - "name": "status", - "sa_type": "String(20)", - "py_type": "str", - "nullable": false, - "default": "pending" - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v1/test_main.py b/zipit/template_runs/tmpl_v1/test_main.py deleted file mode 100644 index 9eaa05d..0000000 --- a/zipit/template_runs/tmpl_v1/test_main.py +++ /dev/null @@ -1,59 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}) -TestSession = sessionmaker(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 setup_function(): - """Luo taulut ennen jokaista testiä.""" - Base.metadata.create_all(bind=engine) - -def teardown_function(): - """Tyhjennä taulut jokaisen testin jälkeen.""" - Base.metadata.drop_all(bind=engine) - -def test_create_todo(): - response = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - assert response.status_code == 201 - assert response.json()["id"] is not None - -def test_list_todos(): - client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - response = client.get('/todos/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_todo_by_id(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - response = client.get(f"/todos/{created["id"]}") - assert response.status_code == 200 - assert response.json()["id"] == created["id"] - -def test_get_todo_not_found(): - response = client.get('/todos/99999') - assert response.status_code == 404 - -def test_update_todo(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - response = client.put(f"/todos/{created["id"]}", json={"title": "Updated title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - assert response.status_code == 200 - -def test_delete_todo(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - response = client.delete(f"/todos/{created["id"]}") - assert response.status_code == 204 - response = client.get(f"/todos/{created["id"]}") - assert response.status_code == 404 diff --git a/zipit/template_runs/tmpl_v2/Dockerfile b/zipit/template_runs/tmpl_v2/Dockerfile deleted file mode 100644 index 60a1cf4..0000000 --- a/zipit/template_runs/tmpl_v2/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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"] diff --git a/zipit/template_runs/tmpl_v2/docker-compose.yml b/zipit/template_runs/tmpl_v2/docker-compose.yml deleted file mode 100644 index d7c5fae..0000000 --- a/zipit/template_runs/tmpl_v2/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - build: . - ports: - - "18765:8000" diff --git a/zipit/template_runs/tmpl_v2/main.py b/zipit/template_runs/tmpl_v2/main.py deleted file mode 100644 index d1cd61f..0000000 --- a/zipit/template_runs/tmpl_v2/main.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, Todo -from schemas import TodoCreate, TodoResponse - -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(item: TodoCreate, db: Session = Depends(get_db)): - db_item = Todo(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/todos/", response_model=list[TodoResponse]) -def list_todos(db: Session = Depends(get_db)): - return db.query(Todo).all() - -@app.get("/todos/{item_id}", response_model=TodoResponse) -def get_todo(item_id: int, db: Session = Depends(get_db)): - item = db.query(Todo).filter(Todo.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Todo not found") - return item - -@app.put("/todos/{item_id}", response_model=TodoResponse) -def update_todo(item_id: int, item: TodoCreate, db: Session = Depends(get_db)): - db_item = db.query(Todo).filter(Todo.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Todo not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/todos/{item_id}", status_code=204) -def delete_todo(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Todo).filter(Todo.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Todo not found") - db.delete(db_item) - db.commit() diff --git a/zipit/template_runs/tmpl_v2/models.py b/zipit/template_runs/tmpl_v2/models.py deleted file mode 100644 index 14e334f..0000000 --- a/zipit/template_runs/tmpl_v2/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, Date, Integer, String, Text -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.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) - due_date = Column(Date) - status = Column(String(20), nullable=False, default="pending") - -Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v2/pyproject.toml b/zipit/template_runs/tmpl_v2/pyproject.toml deleted file mode 100644 index 1e75d19..0000000 --- a/zipit/template_runs/tmpl_v2/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "todo-app" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - "pytest", - "httpx", -] diff --git a/zipit/template_runs/tmpl_v2/report.json b/zipit/template_runs/tmpl_v2/report.json deleted file mode 100644 index 7c4c048..0000000 --- a/zipit/template_runs/tmpl_v2/report.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "run_id": "tmpl_v2", - "model": "qwen2.5-coder:7b-instruct-q4_K_M", - "description": "Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit", - "spec": { - "project_name": "todo-app", - "description": "A simple Todo application with CRUD endpoints using FastAPI and SQLite.", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "due_date", - "sa_type": "Date", - "py_type": "date | None", - "nullable": true, - "default": null - }, - { - "name": "status", - "sa_type": "String(20)", - "py_type": "str", - "nullable": false, - "default": "pending" - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] - }, - "files": { - "models.py": 712, - "schemas.py": 293, - "main.py": 1711, - "test_main.py": 2394, - "pyproject.toml": 177, - "Dockerfile": 339 - }, - "valid": true, - "docker": { - "build": true, - "pytest": true, - "api": true, - "errors": [] - } -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v2/schemas.py b/zipit/template_runs/tmpl_v2/schemas.py deleted file mode 100644 index a453b16..0000000 --- a/zipit/template_runs/tmpl_v2/schemas.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel -from datetime import date - -class TodoCreate(BaseModel): - title: str - description: str | None = None - due_date: date | None = None - status: str = "pending" - -class TodoResponse(TodoCreate): - id: int - - class Config: - from_attributes = True diff --git a/zipit/template_runs/tmpl_v2/spec.json b/zipit/template_runs/tmpl_v2/spec.json deleted file mode 100644 index e1affa8..0000000 --- a/zipit/template_runs/tmpl_v2/spec.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "project_name": "todo-app", - "description": "A simple Todo application with CRUD endpoints using FastAPI and SQLite.", - "entities": [ - { - "name": "Todo", - "table_name": "todos", - "fields": [ - { - "name": "title", - "sa_type": "String(255)", - "py_type": "str", - "nullable": false, - "default": null - }, - { - "name": "description", - "sa_type": "Text", - "py_type": "str | None", - "nullable": true, - "default": null - }, - { - "name": "due_date", - "sa_type": "Date", - "py_type": "date | None", - "nullable": true, - "default": null - }, - { - "name": "status", - "sa_type": "String(20)", - "py_type": "str", - "nullable": false, - "default": "pending" - } - ] - } - ], - "extra_imports": [ - "from datetime import date" - ] -} \ No newline at end of file diff --git a/zipit/template_runs/tmpl_v2/test_main.py b/zipit/template_runs/tmpl_v2/test_main.py deleted file mode 100644 index 2e6858e..0000000 --- a/zipit/template_runs/tmpl_v2/test_main.py +++ /dev/null @@ -1,58 +0,0 @@ -import pytest -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from main import app, get_db -from models import Base - -TEST_DB = "sqlite:///./test.db" -test_engine = create_engine(TEST_DB, connect_args={"check_same_thread": False}) -TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) -Base.metadata.create_all(bind=test_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 title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - assert response.status_code == 201 - data = response.json() - assert "id" in data - -def test_list_todos(): - client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - response = client.get('/todos/') - assert response.status_code == 200 - assert len(response.json()) >= 1 - -def test_get_todo_by_id(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - item_id = created['id'] - response = client.get(f'/todos/{item_id}') - assert response.status_code == 200 - assert response.json()['id'] == item_id - -def test_get_todo_not_found(): - response = client.get('/todos/99999') - assert response.status_code == 404 - -def test_update_todo(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - item_id = created['id'] - response = client.put(f'/todos/{item_id}', json={"title": "Updated title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}) - assert response.status_code == 200 - -def test_delete_todo(): - created = client.post('/todos/', json={"title": "Test title", "description": "Test description", "due_date": "2024-01-15", "status": "pending"}).json() - item_id = created['id'] - response = client.delete(f'/todos/{item_id}') - assert response.status_code == 204 - response = client.get(f'/todos/{item_id}') - assert response.status_code == 404 diff --git a/zipit/test_loop.py b/zipit/test_loop.py deleted file mode 100644 index b95a356..0000000 --- a/zipit/test_loop.py +++ /dev/null @@ -1,717 +0,0 @@ -#!/usr/bin/env python3 -"""Kipinä pipeline test loop — ajaa generoinnin, validoi, säätää prompteja ja uusii.""" - -import json, re, ast, sys, time, textwrap -from pathlib import Path -from urllib.request import urlopen, Request - -OLLAMA = "http://localhost:11434" -MODEL = "qwen2.5-coder:7b-instruct-q4_K_M" -OUTDIR = Path(__file__).parent / "loop_runs" -MAX_RETRIES = 3 - -# ── Ollama-kutsu ────────────────────────────────────────────── - -def llm(system: str, user: str, temperature: float = 0.2) -> str: - body = json.dumps({ - "model": MODEL, - "messages": [ - {"role": "system", "content": system}, - {"role": "user", "content": user}, - ], - "stream": False, - "options": {"temperature": temperature, "num_predict": 4096, "seed": 42}, - }).encode() - req = Request(f"{OLLAMA}/api/chat", data=body, headers={"Content-Type": "application/json"}) - resp = json.loads(urlopen(req, timeout=120).read()) - return resp["message"]["content"] - -# ── Koodin erottaminen LLM-vastauksesta ────────────────────── - -def extract_code(text: str) -> str: - """Poimi koodi markdown-fencestä tai palauta koko teksti.""" - # Etsi viimeinen/pisin code block - blocks = re.findall(r"```(?:python|py|toml|dockerfile|Dockerfile)?\s*\n(.*?)```", text, re.DOTALL) - if blocks: - # Palauta pisin blokki (vältä lyhyet esimerkit) - return max(blocks, key=len).strip() - # Jos ei fenceä, poista mahdolliset selitysrivit alusta/lopusta - lines = text.strip().splitlines() - code_lines = [l for l in lines if not l.startswith("Here") and not l.startswith("This") and not l.startswith("Note")] - return "\n".join(code_lines).strip() - -# ── Validointi ──────────────────────────────────────────────── - -def validate_python(code: str, filename: str, project_files: dict) -> list[str]: - """Tarkista syntaksi, importit, ja yleisimmät ongelmat.""" - errors = [] - - # 1. Syntaksitarkistus - try: - tree = ast.parse(code, filename=filename) - except SyntaxError as e: - lines = code.splitlines() - bad_line = lines[e.lineno - 1] if e.lineno and e.lineno <= len(lines) else "?" - errors.append(f"SyntaxError rivi {e.lineno}: {e.msg}. Virherivin koodi: `{bad_line.strip()}`. Korjaa tämä rivi.") - return errors # ei voi jatkaa - - # 2. Tarkista relatiiviset importit - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom) and node.level and node.level > 0: - errors.append(f"Relatiivinen import 'from .{node.module or ''}' — käytä absoluuttista: from {node.module} import ...") - - # 3. Kerää importit - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom) and node.module: - mod = node.module.split(".")[0] - # Tarkista projektin sisäiset importit - if mod in ("models", "schemas", "main"): - source_file = f"{mod}.py" - if source_file not in project_files: - errors.append(f"Importtaa '{mod}' mutta {source_file} ei ole vielä generoitu") - else: - # Tarkista että importatut nimet löytyvät - source_code = project_files[source_file] - for alias in node.names: - name = alias.name - if name not in source_code: - errors.append(f"Importtaa '{name}' moduulista '{mod}' mutta sitä ei löydy {source_file}:stä") - - # Tarkista paljaiden nimien käyttö ilman importtia - if isinstance(node, ast.Name) and node.id == "date": - has_import = any( - isinstance(n, ast.ImportFrom) and n.module == "datetime" - and any(a.name == "date" for a in n.names) - for n in ast.walk(tree) - ) - if not has_import and "date" in code and "due_date: date" in code: - errors.append("Käyttää 'date'-tyyppiä mutta 'from datetime import date' puuttuu") - - # 3. Tarkista ettei testi-tiedosto uudelleenmäärittele appia/modeleita - if filename == "test_main.py": - for node in ast.walk(tree): - if isinstance(node, ast.ClassDef) and node.name in ("Todo", "Base"): - errors.append(f"test_main.py uudelleenmäärittelee '{node.name}' — pitäisi importata models.py:stä") - if isinstance(node, ast.Assign): - for target in node.targets: - if isinstance(target, ast.Name) and target.id == "app": - if isinstance(node.value, ast.Call): - func = node.value.func - if isinstance(func, ast.Name) and func.id == "FastAPI": - errors.append("test_main.py luo oman FastAPI()-instanssin — pitäisi importata main.py:stä") - - # 4. Pydantic response_model ei saa olla SQLAlchemy-malli - if filename in ("test_main.py", "main.py"): - for node in ast.walk(tree): - if isinstance(node, ast.keyword) and node.arg == "response_model": - if isinstance(node.value, ast.Name) and node.value.id == "Todo": - errors.append(f"response_model=Todo käyttää SQLAlchemy-mallia — pitäisi olla TodoResponse (Pydantic)") - - # 5. models.py: SQLite tarvitsee connect_args - if filename == "models.py": - if "sqlite" in code.lower() and "check_same_thread" not in code: - errors.append("SQLite create_engine puuttuu connect_args={'check_same_thread': False}") - # Tarkista Enum-käyttö: pitää olla python enum, ei SQLAlchemy Enum base-luokkana - for node in ast.walk(tree): - if isinstance(node, ast.ClassDef): - for base in node.bases: - if isinstance(base, ast.Name) and base.id == "Enum" and node.name != "Base": - # Tarkista onko 'import enum' tai 'from enum import Enum' - has_enum_import = any( - (isinstance(n, ast.Import) and any(a.name == "enum" for a in n.names)) or - (isinstance(n, ast.ImportFrom) and n.module == "enum") - for n in ast.walk(tree) - ) - if not has_enum_import: - errors.append(f"Luokka '{node.name}' perii Enum:in mutta 'from enum import Enum' puuttuu (SQLAlchemy Enum ei ole Python Enum)") - - # 6. main.py: FastAPI route-ongelmat - if filename == "main.py": - # Tarkista query params reitissä (esim. "/search?q={query}") - for node in ast.walk(tree): - if isinstance(node, ast.Constant) and isinstance(node.value, str): - if "?" in node.value and "{" in node.value: - errors.append(f"Reitti '{node.value}' sisältää query parametrin — FastAPI:ssa query params tulevat funktion parametreina, ei reitissä") - - # Tarkista route ordering: /todos/count ja /todos/search ENNEN /todos/{{id}} - routes = [] - for node in ast.walk(tree): - if isinstance(node, ast.Call): - func = node.func - if isinstance(func, ast.Attribute) and func.attr in ("get", "post", "put", "delete", "patch"): - for arg in node.args: - if isinstance(arg, ast.Constant) and isinstance(arg.value, str): - routes.append(arg.value) - # Etsi {id} tai {todo_id} reitit ja tarkista tuleeko niiden jälkeen staattisia samalla prefix:llä - param_route_idx = {} - for i, r in enumerate(routes): - if re.search(r"\{[^}]+\}", r): - prefix = r.split("{")[0] - param_route_idx[prefix] = i - for i, r in enumerate(routes): - if not re.search(r"\{[^}]+\}", r): - for prefix, pidx in param_route_idx.items(): - if r.startswith(prefix) and r != prefix and i > pidx: - errors.append(f"Reitti '{r}' tulee parametrisoidun reitin jälkeen — FastAPI tulkitsee sen {{id}}:ksi. Siirrä staattinen reitti ensin.") - - # 7. Tarkista puuttuvat nimet: käytetäänkö Call:issa nimeä jota ei ole importattu tai määritelty - defined_names = set() - imported_names = set() - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom) and node.names: - for alias in node.names: - imported_names.add(alias.asname or alias.name) - if isinstance(node, ast.Import) and node.names: - for alias in node.names: - imported_names.add(alias.asname or alias.name) - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - defined_names.add(node.name) - if isinstance(node, ast.ClassDef): - defined_names.add(node.name) - if isinstance(node, ast.Assign): - for t in node.targets: - if isinstance(t, ast.Name): - defined_names.add(t.id) - - # Tunnetut builtinit ja globaalit - builtins = {"print", "len", "range", "int", "str", "float", "bool", "list", "dict", "set", - "tuple", "type", "None", "True", "False", "super", "property", "staticmethod", - "classmethod", "isinstance", "issubclass", "hasattr", "getattr", "setattr", - "Exception", "ValueError", "TypeError", "KeyError", "IndexError", "AttributeError", - "RuntimeError", "NotImplementedError", "StopIteration", "open", "enumerate", "zip", - "map", "filter", "sorted", "reversed", "any", "all", "min", "max", "sum", "abs"} - - # Tarkista top-level Call-kutsut joissa käytetään tuntematonta nimeä - for node in ast.walk(tree): - if isinstance(node, ast.Call): - func = node.func - if isinstance(func, ast.Name) and func.id not in defined_names | imported_names | builtins: - errors.append(f"Kutsutaan '{func.id}()' mutta sitä ei ole importattu eikä määritelty") - - # 8. Schema/model kenttien yhteensopivuus - if filename == "schemas.py" and "models.py" in project_files: - model_code = project_files["models.py"] - # Etsi model-luokan Column-kentät - model_fields = set(re.findall(r"(\w+)\s*=\s*Column\(", model_code)) - model_fields.discard("id") # id tulee automaattisesti - # Etsi schema-kentät (type-annotaatiot) - schema_fields = set() - in_response = False - for line in code.splitlines(): - if "class " in line and "Response" in line: - in_response = True - elif "class " in line: - in_response = False - if in_response and ":" in line and "class " not in line and "Config" not in line and "from_attributes" not in line: - field = line.strip().split(":")[0].strip() - if field and not field.startswith("#") and not field.startswith("def"): - schema_fields.add(field) - # Response schema pitäisi sisältää kaikki model-kentät (paitsi salaiset) - SENSITIVE_FIELDS = {"password_hash", "password", "hashed_password", "secret", "token", "api_key"} - missing = model_fields - schema_fields - SENSITIVE_FIELDS - if missing and schema_fields: # vain jos löydettiin kenttiä - # Salli jos Response perii Create:sta - create_fields = set() - in_create = False - for line in code.splitlines(): - if "class " in line and "Create" in line: - in_create = True - elif "class " in line: - in_create = False - if in_create and ":" in line and "class " not in line: - field = line.strip().split(":")[0].strip() - if field and not field.startswith("#") and not field.startswith("def"): - create_fields.add(field) - all_schema = schema_fields | create_fields | {"id"} - still_missing = model_fields - all_schema - SENSITIVE_FIELDS - if still_missing: - errors.append(f"Schema puuttuu kenttiä jotka ovat modelissa: {still_missing}") - - return errors - - -def validate_toml(code: str) -> list[str]: - """Tarkista pyproject.toml:n perusrakenne.""" - errors = [] - if "[project]" not in code: - errors.append("Puuttuu [project]-osio") - if "fastapi" not in code.lower(): - errors.append("Puuttuu fastapi riippuvuuksista") - if "sqlalchemy" not in code.lower(): - errors.append("Puuttuu sqlalchemy riippuvuuksista") - if "uvicorn" not in code.lower(): - errors.append("Puuttuu uvicorn riippuvuuksista") - if "[tool.poetry]" in code or "poetry" in code.lower(): - errors.append("Sisältää poetry-konfiguraation — käytä VAIN [project] (PEP 621) + uv") - if "build-backend" in code and "poetry" in code: - errors.append("build-backend käyttää poetryä — poista tai vaihda") - return errors - - -def validate_dockerfile(code: str) -> list[str]: - errors = [] - if "poetry" in code.lower(): - errors.append("Käyttää Poetryä — pitäisi käyttää uv:tä") - if "uv" not in code.lower(): - errors.append("Puuttuu uv-asennus") - if "EXPOSE" not in code: - errors.append("Puuttuu EXPOSE") - # Tarkista USER-järjestys: ei saa olla ennen uv sync - lines = code.strip().splitlines() - user_line = next((i for i, l in enumerate(lines) if l.strip().startswith("USER")), None) - sync_line = next((i for i, l in enumerate(lines) if "uv sync" in l), None) - if user_line is not None and sync_line is not None and user_line < sync_line: - errors.append("USER asetettu ennen 'uv sync' — uv tarvitsee cache-kirjoitusoikeuden. Siirrä USER uv sync:n jälkeen.") - # UV_CACHE_DIR tai --no-cache - if "UV_CACHE_DIR" not in code and "--no-cache" not in code: - errors.append("Puuttuu ENV UV_CACHE_DIR=/tmp/uv-cache tai --no-cache — uv cache -virhe ei-root käyttäjällä") - return errors - -# ── Promptien rakentajat ────────────────────────────────────── - -SYSTEM_PROMPTS = { - "client": textwrap.dedent("""\ - You are a product owner who turns vague ideas into clear, actionable software requirements. - - GIVEN a short project description from the user, produce a structured brief: - 1. PROJECT NAME: a short, descriptive name - 2. GOAL: one sentence explaining what the software does and who it's for - 3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) - 4. DATA MODEL: list the main entities and their key fields (include field types) - 5. API ENDPOINTS: list the REST endpoints (method + path + purpose) - 6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - - RULES: - - Be specific: "User can filter todos by status" not "todo management" - - Use plain English, no code - - Maximum 400 words total"""), - - "data": textwrap.dedent("""\ - You are a database architect specializing in SQLAlchemy and relational databases. - - YOUR RESPONSIBILITIES: - 1. Design normalized database schemas with proper column types and constraints - 2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships - 3. Set up engine, SessionLocal, and Base in the same file (models.py) - 4. Use String(length) not bare String for SQLite compatibility - 5. Add nullable=False for required fields, unique=True where appropriate - 6. Use Column(Integer, primary_key=True, index=True) for IDs - 7. SQLite: create_engine(url, connect_args={"check_same_thread": False}) - - ENUM HANDLING (IMPORTANT): - - For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible - - Do NOT define Python Enum classes — use plain strings instead - - Example: status = Column(String(20), default="pending") - - ALWAYS INCLUDE: - - from sqlalchemy import create_engine, Column, Integer, String - - from sqlalchemy.ext.declarative import declarative_base - - from sqlalchemy.orm import sessionmaker - - DATABASE_URL, engine, SessionLocal, Base - - create_engine with connect_args={"check_same_thread": False} - - Write ONLY the code, no explanations."""), - - "coder": textwrap.dedent("""\ - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) - 2. Import from other project files: from models import Todo, SessionLocal - 3. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) - 4. SQLAlchemy: create_engine(url, connect_args={"check_same_thread": False}) - 5. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 6. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Leave out any import (EVERY type you use must be imported) - - Use relative imports (from .models) — ALWAYS use absolute: from models import ... - - Add explanations or comments - - Leave placeholder code or TODO comments - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Write ONLY the code, no explanations."""), - - "qa": textwrap.dedent("""\ - You are a QA engineer. Write pytest tests for FastAPI projects. - - CRITICAL RULES FOR TESTS: - 1. ALWAYS import app from main.py: from main import app - 2. ALWAYS import models from models.py: from models import Base - 3. NEVER redefine the app, models, or routes in the test file - 4. Use in-memory SQLite for test isolation: sqlite:///:memory: - 5. Override the get_db dependency to use test database - 6. Use TestClient from fastapi.testclient - 7. Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - PATTERN: - ``` - 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) - ``` - - Write ONLY the code, no explanations."""), - - "tester": textwrap.dedent("""\ - You are a DevOps engineer. Write Dockerfiles for Python FastAPI projects. - - RULES: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync) - - Copy pyproject.toml first, then RUN uv sync, then COPY source files - - Set USER AFTER installing dependencies (uv sync needs write access) - - NEVER use pip, poetry, or requirements.txt - - Expose port 8000 - - CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - - EXACT PATTERN (follow this exactly): - ``` - 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"] - ``` - - Write ONLY the Dockerfile, no explanations."""), -} - - -def build_user_prompt(step: str, project_files: dict, requirements: str, error_feedback: str = "") -> str: - """Rakenna user-prompti kontekstilla ja mahdollisella virhepalautteella.""" - - context_files = "\n".join( - f"--- {name} ---\n{code}\n" for name, code in project_files.items() - ) - - error_section = "" - if error_feedback: - error_section = f"\n⚠️ PREVIOUS ATTEMPT HAD ERRORS — FIX THESE:\n{error_feedback}\n" - - if step == "requirements": - return "Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit" - - elif step == "models.py": - return f"""PROJECT REQUIREMENTS:\n{requirements}\n\n{error_section}Write models.py for this project. Write ONLY the code.""" - - elif step == "schemas.py": - return f"""Already written files:\n{context_files}\n\nPROJECT REQUIREMENTS:\n{requirements}\n\n{error_section}Write schemas.py. Create Pydantic schemas matching the SQLAlchemy model. Include ALL imports (from datetime import date if you use date type). Write ONLY the code.""" - - elif step == "main.py": - return f"""Already written files:\n{context_files}\n\nPROJECT REQUIREMENTS:\n{requirements}\n\n{error_section}Write main.py with all CRUD endpoints. Import from models.py and schemas.py. Write ONLY the code.""" - - elif step == "pyproject.toml": - return f"""Already written files:\n{context_files}\n\n{error_section}Write pyproject.toml for this project. Include fastapi, uvicorn[standard], sqlalchemy as dependencies. Write ONLY the file.""" - - elif step == "test_main.py": - return f"""Already written files:\n{context_files}\n\n{error_section}Write test_main.py. IMPORT app from main.py, IMPORT Base from models.py. NEVER redefine models or app. Use in-memory SQLite. Write ONLY the code.""" - - elif step == "Dockerfile": - return f"""Project files: {', '.join(project_files.keys())}\n\n{error_section}Write a Dockerfile. Use uv (NOT pip, NOT poetry). Write ONLY the Dockerfile.""" - - return "" - - -# ── Pipeline ────────────────────────────────────────────────── - -STEPS = [ - ("requirements", "client", None), - ("models.py", "data", "python"), - ("schemas.py", "coder", "python"), - ("main.py", "coder", "python"), - ("pyproject.toml","coder", "toml"), - ("test_main.py", "qa", "python"), - ("Dockerfile", "tester", "dockerfile"), -] - - -def run_pipeline(run_id: str) -> dict: - run_dir = OUTDIR / run_id - run_dir.mkdir(parents=True, exist_ok=True) - - project_files = {} - requirements = "" - results = {"run_id": run_id, "steps": [], "model": MODEL} - - for step_name, agent, file_type in STEPS: - print(f"\n{'='*60}") - print(f" Step: {step_name} (agent: {agent})") - print(f"{'='*60}") - - system = SYSTEM_PROMPTS[agent] - error_feedback = "" - final_code = "" - step_result = {"step": step_name, "agent": agent, "attempts": []} - - for attempt in range(1, MAX_RETRIES + 1): - print(f" Yritys {attempt}/{MAX_RETRIES}...", end=" ", flush=True) - - user_prompt = build_user_prompt(step_name, project_files, requirements, error_feedback) - - t0 = time.time() - raw = llm(system, user_prompt) - elapsed = time.time() - t0 - print(f"({elapsed:.1f}s)") - - if step_name == "requirements": - final_code = raw.strip() - errors = [] - else: - final_code = extract_code(raw) - if file_type == "python": - errors = validate_python(final_code, step_name, project_files) - elif file_type == "toml": - errors = validate_toml(final_code) - elif file_type == "dockerfile": - errors = validate_dockerfile(final_code) - else: - errors = [] - - # Deduplikoi virheet - errors = list(dict.fromkeys(errors)) - - attempt_data = { - "attempt": attempt, - "elapsed": round(elapsed, 1), - "errors": errors, - "code_length": len(final_code), - } - step_result["attempts"].append(attempt_data) - - if errors: - print(f" ❌ {len(errors)} virhettä:") - for e in errors: - print(f" - {e}") - error_feedback = "\n".join(f"- {e}" for e in errors) - # Lisää koko generoitu koodi virhefeedbackiin jotta malli näkee kontekstin - error_feedback += f"\n\nYOUR PREVIOUS OUTPUT (fix the errors above):\n```\n{final_code}\n```" - else: - print(f" ✅ OK") - break - - # Tallenna tulos - if step_name == "requirements": - requirements = final_code - else: - project_files[step_name] = final_code - - step_result["final_errors"] = errors - step_result["passed"] = len(errors) == 0 - results["steps"].append(step_result) - - # Kirjoita tiedosto - (run_dir / step_name.replace("/", "_")).write_text( - final_code if step_name != "requirements" else final_code, - encoding="utf-8" - ) - - # Yhteenveto - passed = sum(1 for s in results["steps"] if s["passed"]) - total = len(results["steps"]) - results["summary"] = f"{passed}/{total} passed" - - # Tallenna raportti - (run_dir / "report.json").write_text(json.dumps(results, indent=2, ensure_ascii=False)) - - print(f"\n{'='*60}") - print(f" TULOS: {passed}/{total} stepiä OK") - print(f" Output: {run_dir}") - print(f"{'='*60}") - - for s in results["steps"]: - status = "✅" if s["passed"] else "❌" - retries = len(s["attempts"]) - retry_info = f" ({retries} yritystä)" if retries > 1 else "" - errs = f" — {s['final_errors']}" if not s["passed"] else "" - print(f" {status} {s['step']}{retry_info}{errs}") - - return results - - -# ── Docker-testaus ──────────────────────────────────────────── - -import subprocess - -def docker_test(run_dir: Path) -> dict: - """Rakenna ja testaa generoitu projekti Dockerilla.""" - results = {"build": False, "pytest": False, "api": False, "errors": []} - - # Luo docker-compose.yml - compose = textwrap.dedent("""\ - 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 - """) - (run_dir / "docker-compose.yml").write_text(compose) - - # Varmista ettei vanha kontti roiku - subprocess.run(["docker", "compose", "down", "--remove-orphans", "-t", "2"], - cwd=run_dir, capture_output=True, timeout=15) - - # 1. Build - print(" 🔨 Docker build...", end=" ", flush=True) - r = subprocess.run(["docker", "compose", "build", "--no-cache"], - cwd=run_dir, capture_output=True, text=True, timeout=120) - if r.returncode != 0: - results["errors"].append(f"Build failed:\n{r.stderr[-500:]}") - print("❌") - return results - results["build"] = True - print("✅") - - # 2. Pytest kontissa - print(" 🧪 pytest...", end=" ", flush=True) - r = subprocess.run( - ["docker", "compose", "run", "--rm", "--no-deps", "app", - "uv", "run", "pytest", "test_main.py", "-v", "--tb=short"], - cwd=run_dir, capture_output=True, text=True, timeout=90) - pytest_output = r.stdout + r.stderr - if r.returncode == 0: - results["pytest"] = True - print("✅") - else: - results["errors"].append(f"pytest failed (exit {r.returncode}):\n{pytest_output[-800:]}") - print("❌") - - # 3. API smoke test - print(" 🌐 API smoke test...", end=" ", flush=True) - # Käynnistä kontti taustalle - subprocess.run(["docker", "compose", "up", "-d"], - cwd=run_dir, capture_output=True, timeout=30) - - # Odota terveyden tarkistusta (max 15s) - api_ok = False - for i in range(15): - time.sleep(1) - try: - from urllib.request import urlopen - resp = urlopen("http://localhost:18765/docs", timeout=3) - if resp.status == 200: - api_ok = True - break - except Exception: - continue - - if api_ok: - try: - import urllib.request - # POST - req = urllib.request.Request( - "http://localhost:18765/todos/", - data=json.dumps({"title": "Testi", "description": "Docker-testi"}).encode(), - headers={"Content-Type": "application/json"}, - method="POST" - ) - # Kokeile myös /tasks/ koska malli saattaa käyttää eri nimeä - try: - resp = urlopen(req, timeout=5) - post_ok = resp.status == 201 - except Exception: - req2 = urllib.request.Request( - "http://localhost:18765/tasks/", - data=json.dumps({"title": "Testi", "description": "Docker-testi"}).encode(), - headers={"Content-Type": "application/json"}, - method="POST" - ) - try: - resp = urlopen(req2, timeout=5) - post_ok = resp.status == 201 - except Exception as e: - post_ok = False - results["errors"].append(f"POST /todos/ ja /tasks/ molemmat failasivat: {e}") - - if post_ok: - results["api"] = True - print("✅") - else: - print("❌ POST failed") - except Exception as e: - results["errors"].append(f"API test error: {e}") - print(f"❌ {e}") - else: - results["errors"].append("Kontti ei käynnistynyt 15s:ssa") - print("❌ timeout") - - # Cleanup - subprocess.run(["docker", "compose", "down", "-t", "2"], - cwd=run_dir, capture_output=True, timeout=15) - - return results - - -# ── Main ────────────────────────────────────────────────────── - -if __name__ == "__main__": - run_id = f"run_{int(time.time())}" - skip_docker = False - args = sys.argv[1:] - if args and args[0] == "--no-docker": - skip_docker = True - args = args[1:] - if args: - run_id = args[0] - - results = run_pipeline(run_id) - - if not skip_docker and all(s["passed"] for s in results["steps"]): - run_dir = OUTDIR / run_id - print(f"\n{'='*60}") - print(f" Docker end-to-end test") - print(f"{'='*60}") - - # Lisää pytest riippuvuus pyproject.toml:iin jos puuttuu - toml_path = run_dir / "pyproject.toml" - toml_content = toml_path.read_text() - if "pytest" not in toml_content: - toml_content = toml_content.replace( - '"sqlalchemy"', - '"sqlalchemy",\n "pytest",\n "httpx"' - ) - toml_path.write_text(toml_content) - - docker_results = docker_test(run_dir) - results["docker"] = docker_results - - print(f"\n Docker: build={'✅' if docker_results['build'] else '❌'} " - f"pytest={'✅' if docker_results['pytest'] else '❌'} " - f"api={'✅' if docker_results['api'] else '❌'}") - if docker_results["errors"]: - for e in docker_results["errors"]: - print(f" ⚠️ {e[:200]}") - - # Päivitä raportti - (run_dir / "report.json").write_text(json.dumps(results, indent=2, ensure_ascii=False)) - elif not all(s["passed"] for s in results["steps"]): - print("\n ⏭️ Docker-testi ohitettu — staattinen validointi ei mennyt läpi") diff --git a/zipit/todo-sovellus-fastapi-.zip b/zipit/todo-sovellus-fastapi-.zip deleted file mode 100644 index dfab338..0000000 Binary files a/zipit/todo-sovellus-fastapi-.zip and /dev/null differ diff --git a/zipit/todo_new/Dockerfile b/zipit/todo_new/Dockerfile deleted file mode 100644 index 109a85a..0000000 --- a/zipit/todo_new/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -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"] \ No newline at end of file diff --git a/zipit/todo_new/README.md b/zipit/todo_new/README.md deleted file mode 100644 index e496752..0000000 --- a/zipit/todo_new/README.md +++ /dev/null @@ -1,63 +0,0 @@ -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 -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. \ No newline at end of file diff --git a/zipit/todo_new/agents.yaml b/zipit/todo_new/agents.yaml deleted file mode 100644 index 633ea9c..0000000 --- a/zipit/todo_new/agents.yaml +++ /dev/null @@ -1,160 +0,0 @@ -# Agents — Kipinä Agentic Studio → CrewAI - -client: - role: >- - Client - goal: >- - product owner who turns vague ideas into clear, actionable software requirements - backstory: | - You are a product owner who turns vague ideas into clear, actionable software requirements. - - GIVEN a short project description from the user, produce a structured brief: - - 1. PROJECT NAME: a short, descriptive name - 2. GOAL: one sentence explaining what the software does and who it's for - 3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) - 4. DATA MODEL: list the main entities and their key fields (include field types) - 5. API ENDPOINTS: list the REST endpoints (method + path + purpose) - 6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - - RULES: - - Be specific: "User can filter todos by status" not "todo management" - - Use plain English, no code - - Maximum 400 words total - llm: qwen-coder - -data: - role: >- - Data Engineer - goal: >- - database architect specializing in SQLAlchemy and relational databases - backstory: | - You are a database architect specializing in SQLAlchemy and relational databases. - - YOUR RESPONSIBILITIES: - 1. Design normalized database schemas with proper column types and constraints - 2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships - 3. Set up engine, SessionLocal, and Base in the same file (models.py) - 4. Use String(length) not bare String for SQLite compatibility - 5. Add nullable=False for required fields, unique=True where appropriate - 6. Use Column(Integer, primary_key=True, index=True) for IDs - 7. SQLite: create_engine(url, connect_args={"check_same_thread": False}) - - ENUM HANDLING (IMPORTANT): - - For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible - - Do NOT define Python Enum classes — use plain strings instead - - Example: status = Column(String(20), default="pending") - - ALWAYS INCLUDE: - - from sqlalchemy import create_engine, Column, Integer, String - - from sqlalchemy.ext.declarative import declarative_base - - from sqlalchemy.orm import sessionmaker - - DATABASE_URL, engine, SessionLocal, Base - - create_engine with connect_args={"check_same_thread": False} - llm: qwen-coder - -coder: - role: >- - Coder - goal: >- - expert Python developer - backstory: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) - 2. Import from other project files: from models import Todo, SessionLocal - 3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... - 4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) - 5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Leave out any import (EVERY type you use must be imported) - - Use relative imports (from .models) - - Add explanations or comments - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - llm: qwen-coder - -qa: - role: >- - QA - goal: >- - QA engineer responsible for code review and automated testing - backstory: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - ALWAYS import app from main.py: from main import app, get_db - - ALWAYS import Base from models.py: from models import Base - - NEVER redefine the app, models, or routes in the test file - - Use file-based SQLite for test isolation: sqlite:///./test.db - - Override the get_db dependency to use test database - - Use TestClient from fastapi.testclient - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - Each test should create its own data, not depend on other tests - llm: qwen-coder - -tester: - role: >- - DevOps - goal: >- - DevOps engineer specializing in containerization and deployment - backstory: | - You are a DevOps engineer specializing in containerization and deployment. - - DOCKERFILE RULES: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync) - - Copy pyproject.toml first, then RUN uv sync, then COPY source files - - Set USER AFTER installing dependencies (uv sync needs write access) - - RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache - - NEVER use pip, poetry, or requirements.txt - - Expose port 8000 - - CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - - Write ONLY the Dockerfile, no explanations. - llm: qwen-coder - -observer: - role: >- - Observer - goal: >- - independent technical observer and risk analyst - backstory: | - 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" - llm: qwen-coder diff --git a/zipit/todo_new/crew.py b/zipit/todo_new/crew.py deleted file mode 100644 index edcee05..0000000 --- a/zipit/todo_new/crew.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit - -CrewAI crew — generated by Kipinä Agentic Studio. -Run: crewai run -""" - -from crewai import Agent, Crew, Process, Task -from crewai.project import CrewBase, agent, crew, task - - -@CrewBase -class ProjectCrew: - """Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit""" - - agents_config = "agents.yaml" - tasks_config = "tasks.yaml" - - @agent - def client(self) -> Agent: - return Agent(config=self.agents_config["client"]) - - @agent - def data(self) -> Agent: - return Agent(config=self.agents_config["data"]) - - @agent - def coder(self) -> Agent: - return Agent(config=self.agents_config["coder"]) - - @agent - def qa(self) -> Agent: - return Agent(config=self.agents_config["qa"]) - - @agent - def tester(self) -> Agent: - return Agent(config=self.agents_config["tester"]) - - @agent - def observer(self) -> Agent: - return Agent(config=self.agents_config["observer"]) - - @task - def step_0_requirements(self) -> Task: - return Task(config=self.tasks_config["step_0_requirements"]) - - @task - def step_1_models_py(self) -> Task: - return Task(config=self.tasks_config["step_1_models_py"]) - - @task - def step_2_schemas_py(self) -> Task: - return Task(config=self.tasks_config["step_2_schemas_py"]) - - @task - def step_3_main_py(self) -> Task: - return Task(config=self.tasks_config["step_3_main_py"]) - - @task - def step_4_pyproject_toml(self) -> Task: - return Task(config=self.tasks_config["step_4_pyproject_toml"]) - - @task - def step_5_review(self) -> Task: - return Task(config=self.tasks_config["step_5_review"]) - - @task - def step_6_test_main_py(self) -> Task: - return Task(config=self.tasks_config["step_6_test_main_py"]) - - @task - def step_7_dockerfile(self) -> Task: - return Task(config=self.tasks_config["step_7_dockerfile"]) - - @task - def step_8_readme_md(self) -> Task: - return Task(config=self.tasks_config["step_8_readme_md"]) - - @crew - def crew(self) -> Crew: - return Crew( - agents=self.agents, - tasks=self.tasks, - process=Process.sequential, - verbose=True, - ) diff --git a/zipit/todo_new/main.py b/zipit/todo_new/main.py deleted file mode 100644 index 9d84e6a..0000000 --- a/zipit/todo_new/main.py +++ /dev/null @@ -1,56 +0,0 @@ -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() \ No newline at end of file diff --git a/zipit/todo_new/models.py b/zipit/todo_new/models.py deleted file mode 100644 index b3e7c9c..0000000 --- a/zipit/todo_new/models.py +++ /dev/null @@ -1,19 +0,0 @@ -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) \ No newline at end of file diff --git a/zipit/todo_new/prompts/0_client_requirements.md b/zipit/todo_new/prompts/0_client_requirements.md deleted file mode 100644 index 1e97cae..0000000 --- a/zipit/todo_new/prompts/0_client_requirements.md +++ /dev/null @@ -1,66 +0,0 @@ -# 0 — Client (client) — requirements - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a product owner who turns vague ideas into clear, actionable software requirements. - -GIVEN a short project description from the user, produce a structured brief: - -1. PROJECT NAME: a short, descriptive name -2. GOAL: one sentence explaining what the software does and who it's for -3. CORE FEATURES: numbered list of 3-8 concrete features (not vague wishes) -4. DATA MODEL: list the main entities and their key fields (include field types) -5. API ENDPOINTS: list the REST endpoints (method + path + purpose) -6. CONSTRAINTS: any technical constraints (e.g. "must use SQLite", "no auth needed") - -RULES: -- Be specific: "User can filter todos by status" not "todo management" -- Use plain English, no code -- Maximum 400 words total -``` - -## Syöte - -``` -Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -``` - -## Tulos - -``` -### PROJECT NAME: Todo-App - -### GOAL: -Create a simple task manager application for individuals to keep track of their daily tasks. - -### CORE FEATURES: -1. **Add Task**: Users can add new tasks with title and description. -2. **View Tasks**: Users can see all their tasks in a list. -3. **Edit Task**: Users can update the details of an existing task. -4. **Delete Task**: Users can remove tasks they no longer need. -5. **Mark as Complete**: Users can mark tasks as completed. -6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed). -7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description. - -### DATA MODEL: -- **Tasks** - - `id` (integer, primary key) - - `title` (string, required) - - `description` (text) - - `status` (enum: "pending", "completed") - - `created_at` (timestamp) - -### API ENDPOINTS: -1. **GET /tasks**: Retrieve a list of all tasks. -2. **POST /tasks**: Create a new task with title, description, and status. -3. **PUT /tasks/{id}**: Update an existing task by ID. -4. **DELETE /tasks/{id}**: Delete a task by ID. -5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status. - -### CONSTRAINTS: -- Must use SQLite as the database. -- No authentication required for simplicity. -``` diff --git a/zipit/todo_new/prompts/1_data_models.py.md b/zipit/todo_new/prompts/1_data_models.py.md deleted file mode 100644 index 7ebb62e..0000000 --- a/zipit/todo_new/prompts/1_data_models.py.md +++ /dev/null @@ -1,141 +0,0 @@ -# 1 — Data Engineer (data) — models.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a database architect specializing in SQLAlchemy and relational databases. - -YOUR RESPONSIBILITIES: -1. Design normalized database schemas with proper column types and constraints -2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships -3. Set up engine, SessionLocal, and Base in the same file (models.py) -4. Use String(length) not bare String for SQLite compatibility -5. Add nullable=False for required fields, unique=True where appropriate -6. Use Column(Integer, primary_key=True, index=True) for IDs -7. SQLite: create_engine(url, connect_args={"check_same_thread": False}) - -ENUM HANDLING (IMPORTANT): -- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible -- Do NOT define Python Enum classes — use plain strings instead -- Example: status = Column(String(20), default="pending") - -ALWAYS INCLUDE: -- from sqlalchemy import create_engine, Column, Integer, String -- from sqlalchemy.ext.declarative import declarative_base -- from sqlalchemy.orm import sessionmaker -- DATABASE_URL, engine, SessionLocal, Base -- create_engine with connect_args={"check_same_thread": False} -``` - -## Syöte - -``` -You are a database architect specializing in SQLAlchemy and relational databases. - -YOUR RESPONSIBILITIES: -1. Design normalized database schemas with proper column types and constraints -2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships -3. Set up engine, SessionLocal, and Base in the same file (models.py) -4. Use String(length) not bare String for SQLite compatibility -5. Add nullable=False for required fields, unique=True where appropriate -6. Use Column(Integer, primary_key=True, index=True) for IDs -7. SQLite: create_engine(url, connect_args={"check_same_thread": False}) - -ENUM HANDLING (IMPORTANT): -- For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible -- Do NOT define Python Enum classes — use plain strings instead -- Example: status = Column(String(20), default="pending") - -ALWAYS INCLUDE: -- from sqlalchemy import create_engine, Column, Integer, String -- from sqlalchemy.ext.declarative import declarative_base -- from sqlalchemy.orm import sessionmaker -- DATABASE_URL, engine, SessionLocal, Base -- create_engine with connect_args={"check_same_thread": False} - -EXAMPLE of models.py (for a different project, adapt to this one): -``` -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class Item(Base): - __tablename__ = "items" - id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False) - description = Column(String(500)) -``` - -PROJECT REQUIREMENTS (from product owner): -### PROJECT NAME: Todo-App - -### GOAL: -Create a simple task manager application for individuals to keep track of their daily tasks. - -### CORE FEATURES: -1. **Add Task**: Users can add new tasks with title and description. -2. **View Tasks**: Users can see all their tasks in a list. -3. **Edit Task**: Users can update the details of an existing task. -4. **Delete Task**: Users can remove tasks they no longer need. -5. **Mark as Complete**: Users can mark tasks as completed. -6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed). -7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description. - -### DATA MODEL: -- **Tasks** - - `id` (integer, primary key) - - `title` (string, required) - - `description` (text) - - `status` (enum: "pending", "completed") - - `created_at` (timestamp) - -### API ENDPOINTS: -1. **GET /tasks**: Retrieve a list of all tasks. -2. **POST /tasks**: Create a new task with title, description, and status. -3. **PUT /tasks/{id}**: Update an existing task by ID. -4. **DELETE /tasks/{id}**: Delete a task by ID. -5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status. - -### CONSTRAINTS: -- Must use SQLite as the database. -- No authentication required for simplicity. - -NOW write "models.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Define the SQLAlchemy model based on the project description. Always include: -- engine with check_same_thread=False for SQLite -- SessionLocal with autocommit=False -- Base = declarative_base() -- Model class with __tablename__, primary key, and fields -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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) -``` diff --git a/zipit/todo_new/prompts/2_coder_schemas.py.md b/zipit/todo_new/prompts/2_coder_schemas.py.md deleted file mode 100644 index 93cb238..0000000 --- a/zipit/todo_new/prompts/2_coder_schemas.py.md +++ /dev/null @@ -1,150 +0,0 @@ -# 2 — Coder (coder) — schemas.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of schemas.py (for a different project, adapt to this one): -``` -from pydantic import BaseModel - -class ItemCreate(BaseModel): - name: str - description: str | None = None - -class ItemResponse(ItemCreate): - id: int - - class Config: - from_attributes = True -``` - -Already written files in THIS project: ---- 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) - -PROJECT REQUIREMENTS (from product owner): -### PROJECT NAME: Todo-App - -### GOAL: -Create a simple task manager application for individuals to keep track of their daily tasks. - -### CORE FEATURES: -1. **Add Task**: Users can add new tasks with title and description. -2. **View Tasks**: Users can see all their tasks in a list. -3. **Edit Task**: Users can update the details of an existing task. -4. **Delete Task**: Users can remove tasks they no longer need. -5. **Mark as Complete**: Users can mark tasks as completed. -6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed). -7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description. - -### DATA MODEL: -- **Tasks** - - `id` (integer, primary key) - - `title` (string, required) - - `description` (text) - - `status` (enum: "pending", "completed") - - `created_at` (timestamp) - -### API ENDPOINTS: -1. **GET /tasks**: Retrieve a list of all tasks. -2. **POST /tasks**: Create a new task with title, description, and status. -3. **PUT /tasks/{id}**: Update an existing task by ID. -4. **DELETE /tasks/{id}**: Delete a task by ID. -5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status. - -### CONSTRAINTS: -- Must use SQLite as the database. -- No authentication required for simplicity. - -NOW write "schemas.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Create Pydantic schemas that match the SQLAlchemy model: -- Create schema: fields without id (user provides these) -- Response schema: inherits from Create, adds id -- Add class Config with from_attributes = True (required for SQLAlchemy ORM) -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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 -``` diff --git a/zipit/todo_new/prompts/3_coder_main.py.md b/zipit/todo_new/prompts/3_coder_main.py.md deleted file mode 100644 index a743166..0000000 --- a/zipit/todo_new/prompts/3_coder_main.py.md +++ /dev/null @@ -1,252 +0,0 @@ -# 3 — Coder (coder) — main.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of main.py (for a different project, adapt to this one): -``` -from fastapi import FastAPI, Depends, HTTPException -from sqlalchemy.orm import Session -from models import Base, engine, SessionLocal, Item -from schemas import ItemCreate, ItemResponse - -Base.metadata.create_all(bind=engine) -app = FastAPI() - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/items/", response_model=ItemResponse, status_code=201) -def create_item(item: ItemCreate, db: Session = Depends(get_db)): - db_item = Item(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - -@app.get("/items/", response_model=list[ItemResponse]) -def list_items(db: Session = Depends(get_db)): - return db.query(Item).all() - -@app.get("/items/{item_id}", response_model=ItemResponse) -def get_item(item_id: int, db: Session = Depends(get_db)): - item = db.query(Item).filter(Item.id == item_id).first() - if not item: - raise HTTPException(status_code=404, detail="Not found") - return item - -@app.put("/items/{item_id}", response_model=ItemResponse) -def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)): - db_item = db.query(Item).filter(Item.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Not found") - for key, value in item.model_dump().items(): - setattr(db_item, key, value) - db.commit() - db.refresh(db_item) - return db_item - -@app.delete("/items/{item_id}", status_code=204) -def delete_item(item_id: int, db: Session = Depends(get_db)): - db_item = db.query(Item).filter(Item.id == item_id).first() - if not db_item: - raise HTTPException(status_code=404, detail="Not found") - db.delete(db_item) - db.commit() -``` - -Already written files in THIS project: ---- 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 - -PROJECT REQUIREMENTS (from product owner): -### PROJECT NAME: Todo-App - -### GOAL: -Create a simple task manager application for individuals to keep track of their daily tasks. - -### CORE FEATURES: -1. **Add Task**: Users can add new tasks with title and description. -2. **View Tasks**: Users can see all their tasks in a list. -3. **Edit Task**: Users can update the details of an existing task. -4. **Delete Task**: Users can remove tasks they no longer need. -5. **Mark as Complete**: Users can mark tasks as completed. -6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed). -7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description. - -### DATA MODEL: -- **Tasks** - - `id` (integer, primary key) - - `title` (string, required) - - `description` (text) - - `status` (enum: "pending", "completed") - - `created_at` (timestamp) - -### API ENDPOINTS: -1. **GET /tasks**: Retrieve a list of all tasks. -2. **POST /tasks**: Create a new task with title, description, and status. -3. **PUT /tasks/{id}**: Update an existing task by ID. -4. **DELETE /tasks/{id}**: Delete a task by ID. -5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status. - -### CONSTRAINTS: -- Must use SQLite as the database. -- No authentication required for simplicity. - -NOW write "main.py" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Create the FastAPI app with all CRUD endpoints: -- Import from models.py and schemas.py (use exact class names) -- create_all(bind=engine) at module level -- get_db dependency with yield pattern -- POST (201), GET list, GET by id, PUT, DELETE (204) -- Use response_model for type safety -- Use model_dump() not dict() (Pydantic v2) -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -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() -``` diff --git a/zipit/todo_new/prompts/4_coder_pyproject.toml.md b/zipit/todo_new/prompts/4_coder_pyproject.toml.md deleted file mode 100644 index 0e200b1..0000000 --- a/zipit/todo_new/prompts/4_coder_pyproject.toml.md +++ /dev/null @@ -1,220 +0,0 @@ -# 4 — Coder (coder) — pyproject.toml - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) -``` - -## Syöte - -``` -You are an expert Python developer. Write complete, production-ready code. - -CRITICAL RULES: -1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) -2. Import from other project files: from models import Todo, SessionLocal -3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... -4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) -5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) -6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) -7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() -8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True -9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - -NEVER: -- Leave out any import (EVERY type you use must be imported) -- Use relative imports (from .models) -- Add explanations or comments -- Leave placeholder code or TODO comments -- Use Flask syntax (app.run) in FastAPI projects -- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) -- Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - -EXAMPLE of pyproject.toml (for a different project, adapt to this one): -``` -[project] -name = "myapp" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" -``` - -Already written files in THIS project: ---- 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() - -PROJECT REQUIREMENTS (from product owner): -### PROJECT NAME: Todo-App - -### GOAL: -Create a simple task manager application for individuals to keep track of their daily tasks. - -### CORE FEATURES: -1. **Add Task**: Users can add new tasks with title and description. -2. **View Tasks**: Users can see all their tasks in a list. -3. **Edit Task**: Users can update the details of an existing task. -4. **Delete Task**: Users can remove tasks they no longer need. -5. **Mark as Complete**: Users can mark tasks as completed. -6. **Filter Tasks**: Users can filter tasks by status (e.g., pending, completed). -7. **Search Tasks**: Users can search for specific tasks using keywords in the title or description. - -### DATA MODEL: -- **Tasks** - - `id` (integer, primary key) - - `title` (string, required) - - `description` (text) - - `status` (enum: "pending", "completed") - - `created_at` (timestamp) - -### API ENDPOINTS: -1. **GET /tasks**: Retrieve a list of all tasks. -2. **POST /tasks**: Create a new task with title, description, and status. -3. **PUT /tasks/{id}**: Update an existing task by ID. -4. **DELETE /tasks/{id}**: Delete a task by ID. -5. **GET /tasks/status/{status}**: Retrieve tasks filtered by status. - -### CONSTRAINTS: -- Must use SQLite as the database. -- No authentication required for simplicity. - -NOW write "pyproject.toml" for THIS project: Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit -Use [project] format (PEP 621, compatible with uv). List dependencies under [project.dependencies]. Add [project.scripts] with dev command. Never use requirements.txt or Poetry format. Run with: uv run uvicorn main:app --reload -Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations. -``` - -## Tulos - -``` -[project] -name = "todo-app" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" -``` diff --git a/zipit/todo_new/prompts/5_qa_review.md b/zipit/todo_new/prompts/5_qa_review.md deleted file mode 100644 index 0f7ea6f..0000000 --- a/zipit/todo_new/prompts/5_qa_review.md +++ /dev/null @@ -1,182 +0,0 @@ -# 5 — QA (qa) — review - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- ALWAYS import app from main.py: from main import app, get_db -- ALWAYS import Base from models.py: from models import Base -- NEVER redefine the app, models, or routes in the test file -- Use file-based SQLite for test isolation: sqlite:///./test.db -- Override the get_db dependency to use test database -- Use TestClient from fastapi.testclient -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- Each test should create its own data, not depend on other tests -``` - -## Syöte - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- ALWAYS import app from main.py: from main import app, get_db -- ALWAYS import Base from models.py: from models import Base -- NEVER redefine the app, models, or routes in the test file -- Use file-based SQLite for test isolation: sqlite:///./test.db -- Override the get_db dependency to use test database -- Use TestClient from fastapi.testclient -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- Each test should create its own data, not depend on other tests - -Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description". - ---- 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" -``` - -## Tulos - -``` -LGTM -``` diff --git a/zipit/todo_new/prompts/6_qa_test_main.py.md b/zipit/todo_new/prompts/6_qa_test_main.py.md deleted file mode 100644 index 811a70f..0000000 --- a/zipit/todo_new/prompts/6_qa_test_main.py.md +++ /dev/null @@ -1,277 +0,0 @@ -# 6 — QA (qa) — test_main.py - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- ALWAYS import app from main.py: from main import app, get_db -- ALWAYS import Base from models.py: from models import Base -- NEVER redefine the app, models, or routes in the test file -- Use file-based SQLite for test isolation: sqlite:///./test.db -- Override the get_db dependency to use test database -- Use TestClient from fastapi.testclient -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- Each test should create its own data, not depend on other tests -``` - -## Syöte - -``` -You are a QA engineer responsible for code review and automated testing. - -CODE REVIEW CHECKLIST: -1. IMPORTS: Every "from X import Y" must match an actual export in file X -2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) -3. TYPES: All function parameters have type hints, return types specified -4. ERRORS: Every db query that can return None has a 404 check -5. RESOURCES: Database session uses yield+finally pattern (no leaks) -6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic -7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) -8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() -9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - -WHEN REVIEWING: -- If all checks pass: respond "LGTM" -- If issues found: list each as "ISSUE: filename.py: description" -- Be specific and actionable, not vague - -WHEN WRITING TESTS: -- ALWAYS import app from main.py: from main import app, get_db -- ALWAYS import Base from models.py: from models import Base -- NEVER redefine the app, models, or routes in the test file -- Use file-based SQLite for test isolation: sqlite:///./test.db -- Override the get_db dependency to use test database -- Use TestClient from fastapi.testclient -- Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) -- Each test should create its own data, not depend on other tests - -Write pytest tests for this project: - ---- 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" - -Write a complete test_main.py file with TestClient. -``` - -## Tulos - -``` -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. -``` diff --git a/zipit/todo_new/prompts/7_tester_dockerfile.md b/zipit/todo_new/prompts/7_tester_dockerfile.md deleted file mode 100644 index 5a7bded..0000000 --- a/zipit/todo_new/prompts/7_tester_dockerfile.md +++ /dev/null @@ -1,72 +0,0 @@ -# 7 — DevOps (tester) — Dockerfile - -**Malli:** `qwen-coder` - -## System Prompt - -``` -You are a DevOps engineer specializing in containerization and deployment. - -DOCKERFILE RULES: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync) -- Copy pyproject.toml first, then RUN uv sync, then COPY source files -- Set USER AFTER installing dependencies (uv sync needs write access) -- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache -- NEVER use pip, poetry, or requirements.txt -- Expose port 8000 -- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - -Write ONLY the Dockerfile, no explanations. -``` - -## Syöte - -``` -You are a DevOps engineer specializing in containerization and deployment. - -DOCKERFILE RULES: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync) -- Copy pyproject.toml first, then RUN uv sync, then COPY source files -- Set USER AFTER installing dependencies (uv sync needs write access) -- RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache -- NEVER use pip, poetry, or requirements.txt -- Expose port 8000 -- CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - -Write ONLY the Dockerfile, no explanations. - -Write a Dockerfile for this Python FastAPI project. - -Project files: models.py, schemas.py, main.py, pyproject.toml, test_main.py - -Requirements: -- Use python:3.12-slim as base -- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -- Copy pyproject.toml first, then uv sync, then copy source -- Expose port 8000 -- CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000 - -Write ONLY the Dockerfile, no explanations. -``` - -## Tulos - -``` -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"] -``` diff --git a/zipit/todo_new/prompts/8_observer_readme.md.md b/zipit/todo_new/prompts/8_observer_readme.md.md deleted file mode 100644 index 0d951a2..0000000 --- a/zipit/todo_new/prompts/8_observer_readme.md.md +++ /dev/null @@ -1,380 +0,0 @@ -# 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, 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 -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. -``` diff --git a/zipit/todo_new/pyproject.toml b/zipit/todo_new/pyproject.toml deleted file mode 100644 index 1db16d0..0000000 --- a/zipit/todo_new/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name = "todo-app" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", -] - -[project.scripts] -dev = "uvicorn main:app --reload" \ No newline at end of file diff --git a/zipit/todo_new/schemas.py b/zipit/todo_new/schemas.py deleted file mode 100644 index 6fcb247..0000000 --- a/zipit/todo_new/schemas.py +++ /dev/null @@ -1,12 +0,0 @@ -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 \ No newline at end of file diff --git a/zipit/todo_new/tasks.yaml b/zipit/todo_new/tasks.yaml deleted file mode 100644 index 8194d55..0000000 --- a/zipit/todo_new/tasks.yaml +++ /dev/null @@ -1,440 +0,0 @@ -# Tasks — Kipinä Agentic Studio → CrewAI - -step_0_requirements: - description: | - Todo-sovellus FastAPI + SQLite, CRUD-endpointit ja testit - expected_output: >- - requirements - agent: client - -step_1_models_py: - description: | - You are a database architect specializing in SQLAlchemy and relational databases. - - YOUR RESPONSIBILITIES: - 1. Design normalized database schemas with proper column types and constraints - 2. Define SQLAlchemy models with __tablename__, primary keys, indexes, and relationships - 3. Set up engine, SessionLocal, and Base in the same file (models.py) - 4. Use String(length) not bare String for SQLite compatibility - 5. Add nullable=False for required fields, unique=True where appropriate - 6. Use Column(Integer, primary_key=True, index=True) for IDs - 7. SQLite: create_engine(url, connect_args={"check_same_thread": False}) - - ENUM HANDLING (IMPORTANT): - - For status fields, use Column(String(20)) with a default value — simpler and SQLite-compatible - - Do NOT define Python Enum classes — use plain strings instead - - Example: status = Column(String(20), default="pending") - - ALWAYS INCLUDE: - - from sqlalchemy import create_engine, Column, Integer, String - - from sqlalchemy.ext.declarative import declarative_base - - from sqlalchemy.orm import sessionmaker - - DATABASE_URL, engine, SessionLocal, Base - - create_engine with connect_args={"check_same_thread": False} - - EXAMPLE of models.py (for a different project, adapt to this one): - ``` - from sqlalchemy import create_engine, Column, Integer, String - from sqlalchemy.ext.declarative import declarative_base - from sqlalchemy.orm import sessionmaker - - DATABASE_URL = "sqlite:///./app.db" - engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) - SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - Base = declarative_base() - - class Item(Base): - __tablename__ = "items" - id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False) - description = Column(String(500)) - ``` - - PROJECT REQUIREMENTS (from product owner): - ### PROJECT NAME: Todo-App - - ### GOAL: - Create a simple task manager application for individuals to keep track of their daily tasks. - - ### CORE FEATURES: - 1. **Add Task**: Users can add new tasks with title and description. - 2. **View Tasks**: Users can see all their tasks in a list. - # ... (truncated) - expected_output: >- - models.py - agent: data - -step_2_schemas_py: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) - 2. Import from other project files: from models import Todo, SessionLocal - 3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... - 4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) - 5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Leave out any import (EVERY type you use must be imported) - - Use relative imports (from .models) - - Add explanations or comments - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of schemas.py (for a different project, adapt to this one): - ``` - from pydantic import BaseModel - - class ItemCreate(BaseModel): - name: str - description: str | None = None - - class ItemResponse(ItemCreate): - id: int - - class Config: - from_attributes = True - ``` - - Already written files in THIS project: - --- 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): - # ... (truncated) - expected_output: >- - schemas.py - agent: coder - -step_3_main_py: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) - 2. Import from other project files: from models import Todo, SessionLocal - 3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... - 4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) - 5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Leave out any import (EVERY type you use must be imported) - - Use relative imports (from .models) - - Add explanations or comments - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of main.py (for a different project, adapt to this one): - ``` - from fastapi import FastAPI, Depends, HTTPException - from sqlalchemy.orm import Session - from models import Base, engine, SessionLocal, Item - from schemas import ItemCreate, ItemResponse - - Base.metadata.create_all(bind=engine) - app = FastAPI() - - def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - @app.post("/items/", response_model=ItemResponse, status_code=201) - def create_item(item: ItemCreate, db: Session = Depends(get_db)): - db_item = Item(**item.model_dump()) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - - @app.get("/items/", response_model=list[ItemResponse]) - def list_items(db: Session = Depends(get_db)): - return db.query(Item).all() - # ... (truncated) - expected_output: >- - main.py - agent: coder - -step_4_pyproject_toml: - description: | - You are an expert Python developer. Write complete, production-ready code. - - CRITICAL RULES: - 1. Include ALL imports at the top of every file — including stdlib (from datetime import date, etc.) - 2. Import from other project files: from models import Todo, SessionLocal - 3. NEVER use relative imports (from .models) — ALWAYS absolute: from models import ... - 4. Pydantic schemas use different names than SQLAlchemy models: TodoCreate, TodoResponse (not Todo) - 5. SQLAlchemy engine: create_engine(url, connect_args={"check_same_thread": False}) - 6. SessionLocal: sessionmaker(autocommit=False, autoflush=False, bind=engine) - 7. FastAPI dependencies: def get_db(): db = SessionLocal(); try: yield db; finally: db.close() - 8. Pydantic v2: use model_dump() not dict(), class Config: from_attributes = True - 9. All CRUD endpoints: POST (201), GET list, GET by id, PUT, DELETE (204) - - NEVER: - - Leave out any import (EVERY type you use must be imported) - - Use relative imports (from .models) - - Add explanations or comments - - Leave placeholder code or TODO comments - - Use Flask syntax (app.run) in FastAPI projects - - Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621) - - Use pip install — use uv (e.g. uv run uvicorn main:app --reload) - - EXAMPLE of pyproject.toml (for a different project, adapt to this one): - ``` - [project] - name = "myapp" - version = "0.1.0" - requires-python = ">=3.11" - dependencies = [ - "fastapi", - "uvicorn[standard]", - "sqlalchemy", - ] - - [project.scripts] - dev = "uvicorn main:app --reload" - ``` - - Already written files in THIS project: - --- 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() - - # ... (truncated) - expected_output: >- - pyproject.toml - agent: coder - -step_5_review: - description: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - ALWAYS import app from main.py: from main import app, get_db - - ALWAYS import Base from models.py: from models import Base - - NEVER redefine the app, models, or routes in the test file - - Use file-based SQLite for test isolation: sqlite:///./test.db - - Override the get_db dependency to use test database - - Use TestClient from fastapi.testclient - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - Each test should create its own data, not depend on other tests - - Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description". - - --- 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) - # ... (truncated) - expected_output: >- - review - agent: qa - -step_6_test_main_py: - description: | - You are a QA engineer responsible for code review and automated testing. - - CODE REVIEW CHECKLIST: - 1. IMPORTS: Every "from X import Y" must match an actual export in file X - 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) - 3. TYPES: All function parameters have type hints, return types specified - 4. ERRORS: Every db query that can return None has a 404 check - 5. RESOURCES: Database session uses yield+finally pattern (no leaks) - 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic - 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) - 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() - 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers - - WHEN REVIEWING: - - If all checks pass: respond "LGTM" - - If issues found: list each as "ISSUE: filename.py: description" - - Be specific and actionable, not vague - - WHEN WRITING TESTS: - - ALWAYS import app from main.py: from main import app, get_db - - ALWAYS import Base from models.py: from models import Base - - NEVER redefine the app, models, or routes in the test file - - Use file-based SQLite for test isolation: sqlite:///./test.db - - Override the get_db dependency to use test database - - Use TestClient from fastapi.testclient - - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - - Each test should create its own data, not depend on other tests - - Write pytest tests for this project: - - --- 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) - # ... (truncated) - expected_output: >- - test_main.py - agent: qa - -step_7_dockerfile: - description: | - You are a DevOps engineer specializing in containerization and deployment. - - DOCKERFILE RULES: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - ENV UV_CACHE_DIR=/tmp/uv-cache (MUST set before uv sync) - - Copy pyproject.toml first, then RUN uv sync, then COPY source files - - Set USER AFTER installing dependencies (uv sync needs write access) - - RUN useradd -m appuser && chown -R appuser:appuser /app /tmp/uv-cache - - NEVER use pip, poetry, or requirements.txt - - Expose port 8000 - - CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - - Write ONLY the Dockerfile, no explanations. - - Write a Dockerfile for this Python FastAPI project. - - Project files: models.py, schemas.py, main.py, pyproject.toml, test_main.py - - Requirements: - - Use python:3.12-slim as base - - Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv - - Copy pyproject.toml first, then uv sync, then copy source - - Expose port 8000 - - CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000 - - Write ONLY the Dockerfile, no explanations. - expected_output: >- - Dockerfile - agent: tester - -step_8_readme_md: - description: | - 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 - ``` - # ... (truncated) - expected_output: >- - README.md - agent: observer diff --git a/zipit/todo_new/test_main.py b/zipit/todo_new/test_main.py deleted file mode 100644 index dde543f..0000000 --- a/zipit/todo_new/test_main.py +++ /dev/null @@ -1,94 +0,0 @@ -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. \ No newline at end of file