Spaces:
Build error
Build error
File size: 2,755 Bytes
bd09498 c0b5d70 bd09498 d03b796 bd09498 1fa7ddd bd09498 5d9b495 99f23f1 5d9b495 99f23f1 5d9b495 99f23f1 5d9b495 99f23f1 5d9b495 99f23f1 5d9b495 bd09498 5d9b495 bd09498 d03b796 bd09498 d03b796 c0b5d70 5d9b495 bd09498 5d9b495 bd09498 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | """
Endpoints de lecture des profils de corpus (R10 — préfixe /api/v1/).
GET /api/v1/profiles
GET /api/v1/profiles/{profile_id}
Les profils sont des fichiers JSON dans profiles/ (racine du dépôt).
Ils sont validés par CorpusProfile avant d'être retournés.
"""
# 1. stdlib
import asyncio
import json
import logging
import re
from pathlib import Path
# 2. third-party
from fastapi import APIRouter, HTTPException
from pydantic import ValidationError
# 3. local
from app.config import settings
from app.schemas.corpus_profile import CorpusProfile
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/profiles", tags=["profiles"])
_profiles_cache: dict[str, CorpusProfile] | None = None
_profiles_cache_dir: Path | None = None
def _load_all_profiles() -> dict[str, CorpusProfile]:
"""Charge tous les profils depuis le disque (cache invalidé si le répertoire change)."""
global _profiles_cache, _profiles_cache_dir
current_dir = settings.profiles_dir
if _profiles_cache is not None and _profiles_cache_dir == current_dir:
return _profiles_cache
result: dict[str, CorpusProfile] = {}
if current_dir.is_dir():
for path in sorted(current_dir.glob("*.json")):
profile = _load_profile(path)
if profile is not None:
result[profile.profile_id] = profile
else:
logger.warning("profiles_dir introuvable : %s", current_dir)
_profiles_cache = result
_profiles_cache_dir = current_dir
return _profiles_cache
def _load_profile(path: Path) -> CorpusProfile | None:
"""Charge et valide un fichier de profil JSON. Retourne None si invalide."""
try:
data = json.loads(path.read_text(encoding="utf-8"))
return CorpusProfile.model_validate(data)
except (json.JSONDecodeError, ValidationError) as exc:
logger.warning("Profil invalide ignoré", extra={"path": str(path), "error": str(exc)})
return None
@router.get("", response_model=list[dict])
async def list_profiles() -> list[dict]:
"""Retourne tous les profils valides du dossier profiles/."""
profiles = _load_all_profiles()
return [p.model_dump() for p in profiles.values()]
_SAFE_ID_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
@router.get("/{profile_id}", response_model=dict)
async def get_profile(profile_id: str) -> dict:
"""Retourne un profil par son id (nom du fichier sans extension)."""
if not _SAFE_ID_RE.match(profile_id):
raise HTTPException(status_code=400, detail="profile_id invalide")
profiles = _load_all_profiles()
profile = profiles.get(profile_id)
if profile is None:
raise HTTPException(status_code=404, detail="Profil introuvable")
return profile.model_dump()
|