refactor: configure pre-commit and CI/CD pipeline

- Restructured GitHub Actions workflow with separate jobs for linting, testing, and security
- Configured pre-commit hooks: black, isort, flake8, yamllint
- Added setup.cfg for centralized configuration
- Relaxed flake8 rules (B008, D* docstrings) for FastAPI compatibility
- Removed bandit (pbr dependency issue) - can be added later
- All pre-commit checks now passing
This commit is contained in:
Johan
2026-02-02 15:46:05 +01:00
parent 35d6e2ff05
commit 0d0dd3cfcf
14 changed files with 316 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from sqlalchemy.orm import DeclarativeBase, sessionmaker
# SQLite local file. In CI, you can swap to Postgres later.
DATABASE_URL = "sqlite:///./taskmanager.db"

View File

@@ -1,15 +1,15 @@
import os
import yaml
from fastapi import FastAPI, Body, Depends, Header, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from sqlalchemy import select, text
from datetime import datetime, timezone
import yaml
from fastapi import Body, Depends, FastAPI, Header, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import select, text
from sqlalchemy.orm import Session
from .db import Base, engine, get_db
from .models import Task
from .schemas import TaskCreate, TaskUpdate, TaskOut
from .schemas import TaskCreate, TaskOut, TaskUpdate
API_KEY = "devsecops-demo-secret-<a_remplacer>"
@@ -31,21 +31,25 @@ Base.metadata.create_all(bind=engine)
def debug():
return {"env": dict(os.environ)}
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/admin/stats")
def admin_stats(x_api_key: str | None = Header(default=None)):
if x_api_key != API_KEY:
raise HTTPException(status_code=401, detail="Unauthorized")
return {"tasks": ""}
@app.post("/import")
def import_yaml(payload: str = Body(embed=True)):
data = yaml.full_load(payload)
return {"imported": True, "keys": list(data.keys()) if isinstance(data, dict) else "n/a"}
@app.get("/tasks", response_model=list[TaskOut])
def list_tasks(db: Session = Depends(get_db)):
tasks = db.execute(select(Task).order_by(Task.id.desc())).scalars().all()
@@ -103,4 +107,4 @@ def delete_task(task_id: int, db: Session = Depends(get_db)):
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
return None
return None

View File

@@ -1,6 +1,8 @@
from sqlalchemy import String, Integer, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from datetime import datetime, timezone
from sqlalchemy import DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base
@@ -10,6 +12,12 @@ class Task(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(String(1000), nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="TODO") # TODO/DOING/DONE
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
status: Mapped[str] = mapped_column(
String(20), nullable=False, default="TODO"
) # TODO/DOING/DONE
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
)

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel, Field
from datetime import datetime
from pydantic import BaseModel, Field
class TaskCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)

View File

@@ -3,4 +3,4 @@ uvicorn[standard]==0.34.0
sqlalchemy==2.0.36
pydantic==2.10.3
jinja2==2.10.1
PyYAML==5.3.1
PyYAML==5.3.1