backend: Pydantic response_models für 5 GET-Endpoints hinzufügen

- health.py: HealthResponse
- eigenleben.py: EigenlebenOverviewResponse + SkillTextResponse
- chronik.py: ChronikResponse + ChronikItem
- wissen.py: WissenListResponse + WissenFileResponse
- reminders.py: ReminderListResponse + ReminderOut

py_compile bestanden, /docs zeigt neue Schemata.
Response-Payloads unverändert — nur Typisierung hinzugefügt.
This commit is contained in:
2026-07-15 19:33:39 +02:00
parent d0a967e765
commit 21ea7e1dd3
5 changed files with 101 additions and 9 deletions
+16 -1
View File
@@ -3,6 +3,8 @@ Quelle ist der persistente Melde-Briefkasten (services/announce.py): Health-Wäc
Auto-Updates, Erinnerungen, Radar/Traum/Chef-Gutachter-Crons, Auftragsbuch — alle Auto-Updates, Erinnerungen, Radar/Traum/Chef-Gutachter-Crons, Auftragsbuch — alle
autonomen Kanäle laufen dort bereits durch. Hier wird nichts Neues erhoben, nur erzählt.""" autonomen Kanäle laufen dort bereits durch. Hier wird nichts Neues erhoben, nur erzählt."""
from pydantic import BaseModel
from fastapi import APIRouter from fastapi import APIRouter
from services import announce from services import announce
@@ -10,6 +12,19 @@ from services import announce
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@router.get("/chronik") class ChronikItem(BaseModel):
id: int
ts: float
subject: str
text: str
source: str
priority: str
class ChronikResponse(BaseModel):
items: list[ChronikItem]
@router.get("/chronik", response_model=ChronikResponse)
def chronik(limit: int = 150) -> dict: def chronik(limit: int = 150) -> dict:
return {"items": announce.list_recent(limit)} return {"items": announce.list_recent(limit)}
+34 -3
View File
@@ -1,4 +1,6 @@
"""Eigenleben-Endpoints — „Von allein"-Ansicht (Skills + Vorschlags-Bilanz), read-only.""" """Eigenleben-Endpoints — „Von allein-Ansicht (Skills + Vorschlags-Bilanz), read-only."""
from pydantic import BaseModel
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
@@ -7,7 +9,36 @@ from services import eigenleben
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@router.get("/eigenleben") class SkillItem(BaseModel):
id: str
name: str
kategorie: str | None
aktiv: bool | None
beschreibung: str
version: str
autor: str
herkunft: str
herkunft_label: str
genutzt: int
zuletzt_genutzt: str | None
erstellt: str | None
status: str
geaendert: float
class EigenlebenOverviewResponse(BaseModel):
available: bool
skills: list[SkillItem]
selbst_erstellt: int
bilanz: dict
class SkillTextResponse(BaseModel):
id: str
text: str
@router.get("/eigenleben", response_model=EigenlebenOverviewResponse)
def get_overview() -> dict: def get_overview() -> dict:
return eigenleben.overview() return eigenleben.overview()
@@ -17,7 +48,7 @@ def get_overview() -> dict:
# fällt in den SPA-Fallback (index.html, Status 200) und die UI zeigt „Konnte den # fällt in den SPA-Fallback (index.html, Status 200) und die UI zeigt „Konnte den
# Skill-Text nicht laden". Die Pfad-Validierung (max. eine Ebene, keine . /..) macht # Skill-Text nicht laden". Die Pfad-Validierung (max. eine Ebene, keine . /..) macht
# services.eigenleben.skill_text. # services.eigenleben.skill_text.
@router.get("/eigenleben/skill/{skill_id:path}") @router.get("/eigenleben/skill/{skill_id:path}", response_model=SkillTextResponse)
def get_skill(skill_id: str) -> dict: def get_skill(skill_id: str) -> dict:
text = eigenleben.skill_text(skill_id) text = eigenleben.skill_text(skill_id)
if text is None: if text is None:
+11 -1
View File
@@ -1,5 +1,7 @@
"""Health-/Status-Endpoint — schlanker Lebenszeichen-Check für MC 2.0.""" """Health-/Status-Endpoint — schlanker Lebenszeichen-Check für MC 2.0."""
from pydantic import BaseModel
from fastapi import APIRouter from fastapi import APIRouter
from config import VERSION from config import VERSION
@@ -8,7 +10,15 @@ from services import gateway, llamaswap
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@router.get("/health") class HealthResponse(BaseModel):
status: str
version: str
engine_reachable: bool
gateway_reachable: bool
brain: dict
@router.get("/health", response_model=HealthResponse)
def health() -> dict: def health() -> dict:
return { return {
"status": "ok", "status": "ok",
+16 -2
View File
@@ -2,9 +2,10 @@
Konsument ist v.a. der Hermes-Agent via mcp_mc.py (reminder_create/list/delete); Konsument ist v.a. der Hermes-Agent via mcp_mc.py (reminder_create/list/delete);
LAN-only wie alle MC2-Endpoints.""" LAN-only wie alle MC2-Endpoints."""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import APIRouter, HTTPException
from services import reminders from services import reminders
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@@ -16,7 +17,20 @@ class ReminderIn(BaseModel):
repeat: str = "" # '' einmalig | daily | weekdays | weekly repeat: str = "" # '' einmalig | daily | weekdays | weekly
@router.get("/reminders") class ReminderOut(BaseModel):
id: int
text: str
next_ts: float
repeat: str
created_ts: float
when_local: str
class ReminderListResponse(BaseModel):
items: list[ReminderOut]
@router.get("/reminders", response_model=ReminderListResponse)
def list_reminders() -> dict: def list_reminders() -> dict:
return {"items": reminders.list_all()} return {"items": reminders.list_all()}
+24 -2
View File
@@ -6,6 +6,8 @@ import os
import time import time
from pathlib import Path from pathlib import Path
from pydantic import BaseModel
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@@ -30,7 +32,27 @@ def _title_of(p: Path) -> str:
return p.stem return p.stem
@router.get("/wissen") class VaultFile(BaseModel):
path: str
name: str
dir: str
title: str
mtime: float
neu: bool
class WissenListResponse(BaseModel):
available: bool
files: list[VaultFile]
class WissenFileResponse(BaseModel):
path: str
content: str
mtime: float
@router.get("/wissen", response_model=WissenListResponse)
def list_vault() -> dict: def list_vault() -> dict:
"""Alle Markdown-Notizen des Vaults (relativer Pfad, Titel, Ordner, Alter).""" """Alle Markdown-Notizen des Vaults (relativer Pfad, Titel, Ordner, Alter)."""
if not _available(): if not _available():
@@ -54,7 +76,7 @@ def list_vault() -> dict:
return {"available": True, "files": files} return {"available": True, "files": files}
@router.get("/wissen/datei") @router.get("/wissen/datei", response_model=WissenFileResponse)
def read_file(pfad: str) -> dict: def read_file(pfad: str) -> dict:
"""Inhalt einer Vault-Notiz — Traversal hart geblockt (resolve + is_relative_to).""" """Inhalt einer Vault-Notiz — Traversal hart geblockt (resolve + is_relative_to)."""
if not _available(): if not _available():