Imago/doc/rapport-amelioration-imago.md
Bruno Charest cc99fea20a
Some checks failed
CI / Lint & Format (push) Has been cancelled
CI / Tests (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
Add comprehensive test suite for image processing and related services
- Implement tests for database generator to ensure proper session handling.
- Create tests for EXIF extraction and conversion functions.
- Add tests for image-related endpoints, ensuring proper data retrieval and isolation between clients.
- Develop tests for OCR functionality, including language detection and text extraction.
- Introduce tests for the image processing pipeline, covering success and failure scenarios.
- Validate rate limiting functionality and ensure independent counters for different clients.
- Implement scraper tests to verify HTML content fetching and error handling.
- Add unit tests for various services, including storage and filename generation.
- Establish worker entry point for ARQ to handle background image processing tasks.
2026-02-24 11:22:10 -05:00

29 KiB
Raw Blame History

Rapport d'amélioration — Imago

Architecture d'un hub centralisé multi-clients

Version 1.0.0
Date Février 2026
Statut Proposition — v1 Initiale
Contexte Backend FastAPI — Hub image multi-clients

Table des matières

  1. Résumé exécutif
  2. Axe 1 — Authentification et autorisation
  3. Axe 2 — Architecture multi-clients (tenants)
  4. Axe 3 — Pipeline asynchrone robuste
  5. Axe 4 — Stockage et distribution des fichiers
  6. Axe 5 — WebSockets et notifications en temps réel
  7. Axe 6 — Observabilité et monitoring
  8. Axe 7 — Versioning de l'API et SDK clients
  9. Axe 8 — Tests, qualité de code et CI/CD
  10. Feuille de route proposée
  11. Stack technique finale recommandée
  12. Conclusion

1. Résumé exécutif

Le backend Imago a été conçu initialement comme un complément à l'interface Shaarli pour gérer les images, l'OCR et les analyses AI. L'objectif a évolué : ce backend doit devenir un hub centralisé capable de servir plusieurs applications clientes simultanément.

Ce rapport identifie 8 axes d'amélioration couvrant la sécurité, la scalabilité, la qualité du code et l'expérience développeur. Chaque axe est accompagné de recommandations concrètes, d'exemples de code et d'une estimation d'effort.

Contexte actuel

  • Clients identifiés : Interface Shaarli (client 1), futures applications tierces
  • Technologies actuelles : FastAPI, SQLAlchemy async, Pillow, Tesseract, Anthropic Claude
  • Points critiques immédiats : absence d'authentification, pas de gestion multi-clients, pipeline non persistant
  • Horizon de développement proposé : 3 phases sur 3 mois

1.1 Synthèse des axes d'amélioration

Axe d'amélioration Impact principal Priorité Effort
Authentification & Autorisation Sécurité multi-clients 🔴 Critique 35 j
Gestion multi-clients (tenants) Isolation des données 🔴 Critique 57 j
Pipeline asynchrone robuste Fiabilité du traitement AI 🟠 Haute 45 j
Stockage & CDN Performance, scalabilité 🟠 Haute 34 j
WebSockets & temps réel Expérience client 🟡 Moyenne 23 j
Observabilité & Monitoring Production-ready 🟡 Moyenne 34 j
API versioning & SDK clients Contrat API stable 🟡 Moyenne 23 j
Tests & CI/CD Qualité et maintenabilité 🟠 Haute 45 j

2. Axe 1 — Authentification et autorisation

2.1 Problématique

Dans l'état actuel, le backend est entièrement public. N'importe quelle application ou personne ayant accès au réseau peut uploader, lire ou supprimer des images. Cela est incompatible avec un hub servant plusieurs clients distincts.

⚠️ Risques actuels

  • Accès non contrôlé à l'ensemble des images et métadonnées
  • Consommation illimitée de tokens AI (coût financier direct)
  • Suppression accidentelle ou malveillante de données

2.2 Solution recommandée — API Keys + JWT

Pour un hub multi-clients, la combinaison la plus pragmatique est : des API Keys pour l'authentification machine-to-machine (clients automatiques, scripts), et des tokens JWT pour les sessions utilisateurs interactives.

Mécanisme Cas d'usage Avantages
API Key Shaarli, applications serveur, scripts CLI Simple, stateless, facile à révoquer par client
JWT (Bearer) Interface web, utilisateur connecté Expiration automatique, payload riche
OAuth2 (futur) Intégration systèmes tiers Standard industriel, écosystème large

2.3 Implémentation

Ajouter un modèle APIClient en base de données reliant chaque client à ses permissions et sa clé. Injecter une dépendance FastAPI verify_api_key qui valide la clé sur chaque requête. Définir des scopes granulaires : images:read, images:write, images:delete, ai:use.

# app/models/client.py
class APIClient(Base):
    __tablename__ = "api_clients"

    id           = Column(UUID, primary_key=True, default=uuid4)
    name         = Column(String, nullable=False)        # "Shaarli", "App Mobile"
    api_key_hash = Column(String, nullable=False)        # SHA-256, jamais en clair
    scopes       = Column(JSON, default=list)            # ["images:read", "ai:use"]
    is_active    = Column(Boolean, default=True)
    created_at   = Column(DateTime, default=datetime.utcnow)
# app/dependencies/auth.py
async def verify_api_key(
    authorization: str = Header(...),
    db: AsyncSession = Depends(get_db)
) -> APIClient:
    key = authorization.removeprefix("Bearer ").strip()
    key_hash = hashlib.sha256(key.encode()).hexdigest()

    result = await db.execute(
        select(APIClient).where(APIClient.api_key_hash == key_hash)
    )
    client = result.scalar_one_or_none()

    if not client or not client.is_active:
        raise HTTPException(status_code=401, detail="Clé API invalide")

    return client

💡 Points d'implémentation

  • Librairies : python-jose (JWT), passlib (hachage), itsdangerous (tokens signés)
  • Stockage des clés : hachées en base (sha256), jamais en clair
  • Rotation des clés : endpoint POST /auth/rotate-key avec période de transition
  • Audit log : chaque requête authentifiée loggée avec client_id, action, timestamp

3. Axe 2 — Architecture multi-clients (tenants)

3.1 Vision cible

Le hub doit servir plusieurs applications clientes avec une isolation complète des données. Shaarli est le client 1, mais d'autres applications peuvent s'inscrire et utiliser l'API de manière indépendante. Chaque client possède son propre espace de stockage, ses propres images et ses propres quotas.

Concept Description
Client (Tenant) Une application consommatrice de l'API. Identifiée par son API Key. Ex : Shaarli, App Mobile, Script de backup.
Espace de stockage Répertoire dédié : uploads/{client_id}/. Les images d'un client ne sont jamais accessibles par un autre.
Quota Limite configurable : nb d'images, espace disque total, tokens AI consommés par mois.
Plan Niveaux de service : free (limite stricte), standard, premium (OCR + AI complets).

3.2 Modifications de la base de données

Ajouter une table clients avec les champs essentiels. Lier chaque image à son client via une clé étrangère client_id. Tous les endpoints filtrent automatiquement par client_id injecté depuis l'authentification.

Champ Type Description
id UUID Identifiant unique du client
name String Nom de l'application cliente
api_key_hash String Hash SHA-256 de la clé (jamais en clair)
scopes JSON Permissions accordées : ["images:read", "ai:use", ...]
quota_images Integer Nombre maximum d'images stockées
quota_storage_mb Integer Espace disque alloué en mégaoctets
quota_ai_tokens Integer Tokens AI mensuels autorisés
is_active Boolean Désactiver un client sans supprimer ses données
created_at DateTime Date d'inscription

3.3 Injection automatique du client dans les endpoints

# Avant (aucun contrôle)
@router.get("/images")
async def list_images(db: AsyncSession = Depends(get_db)):
    return await db.execute(select(Image))

# Après (filtrage automatique par client)
@router.get("/images")
async def list_images(
    db: AsyncSession = Depends(get_db),
    client: APIClient = Depends(verify_api_key),   # ← injecté
):
    query = select(Image).where(Image.client_id == client.id)  # ← filtré
    return await db.execute(query)

4. Axe 3 — Pipeline asynchrone robuste

4.1 Limites de l'implémentation actuelle

Le pipeline actuel utilise les BackgroundTasks de FastAPI. Cette approche est fonctionnelle pour un usage léger mais présente des limites importantes pour un hub en production.

⚠️ Problèmes identifiés

  • Perte de tâches : si le serveur redémarre, toutes les tâches en attente sont perdues (pas de persistance)
  • Pas de limite de concurrence : risque de saturer l'API Anthropic et de dépasser les quotas
  • Pas de retry automatique : un timeout API ou une erreur réseau = tâche perdue définitivement
  • Pas de priorité : un client premium attend autant qu'un client free

4.2 Solution — File de messages avec ARQ ou Celery

Solution Avantages Inconvénients
ARQ + Redis 100% async, léger, parfait pour FastAPI, retry natif Nécessite Redis (service supplémentaire)
Celery + Redis Très mature, monitoring Flower intégré, priorités Plus lourd, mélange sync/async parfois délicat
Dramatiq Simple, middleware extensible Communauté plus petite qu'Celery

Recommandation : ARQ avec Redis pour rester dans un écosystème async pur. Redis sert à la fois de broker de tâches et de cache pour les résultats AI.

# app/workers/image_worker.py
import arq

async def process_image_task(ctx, image_id: int):
    """Tâche ARQ — remplace le BackgroundTask actuel."""
    db = ctx["db"]
    await process_image_pipeline(image_id, db)

class WorkerSettings:
    functions = [process_image_task]
    redis_settings = arq.connections.RedisSettings()
    max_jobs = 10          # concurrence max
    job_timeout = 180      # timeout par tâche (secondes)
    retry_jobs = True
    max_tries = 3          # retry automatique × 3

💡 Points d'implémentation

  • Retry configurable : 3 tentatives avec backoff exponentiel (1s, 4s, 16s)
  • Timeout par tâche : 120 secondes pour l'appel Vision AI, 30s pour l'OCR
  • Files distinctes : queue:premium (priorité haute) et queue:standard
  • Dead-letter queue : tâches échouées après 3 tentatives → alerte + log persistant

5. Axe 4 — Stockage et distribution des fichiers

5.1 Limites du stockage local

Le stockage sur disque local fonctionne pour un serveur unique mais devient problématique dès que l'on veut scaler horizontalement ou séparer le serveur applicatif du stockage.

⚠️ Problèmes identifiés

  • Le disque du serveur est partagé par tous les clients sans isolation
  • Pas de réplication — une panne disque = perte de toutes les images
  • Les URLs statiques exposent le chemin réel du fichier sur le serveur
  • Impossible de scaler sur plusieurs instances (fichiers non partagés)

5.2 Abstraction du stockage

Introduire une interface StorageBackend abstraite permettant de basculer entre stockage local et S3 sans modifier le reste du code.

# app/services/storage_backend.py
from abc import ABC, abstractmethod

class StorageBackend(ABC):
    @abstractmethod
    async def save(self, file: bytes, path: str) -> str: ...

    @abstractmethod
    async def delete(self, path: str) -> None: ...

    @abstractmethod
    async def get_url(self, path: str, expires_in: int = 900) -> str: ...


class LocalStorage(StorageBackend):
    """Backend local — développement et serveur unique."""
    async def save(self, file: bytes, path: str) -> str:
        full_path = Path(settings.UPLOAD_DIR) / path
        async with aiofiles.open(full_path, "wb") as f:
            await f.write(file)
        return str(full_path)

    async def get_url(self, path: str, expires_in: int = 900) -> str:
        # Token HMAC signé avec expiration
        token = signing.dumps({"path": path, "exp": time() + expires_in})
        return f"/files/{token}"


class S3Storage(StorageBackend):
    """Backend S3/MinIO/R2 — production."""
    async def get_url(self, path: str, expires_in: int = 900) -> str:
        # URL pré-signée AWS native
        return await self.client.generate_presigned_url(
            "get_object", Params={"Bucket": self.bucket, "Key": path},
            ExpiresIn=expires_in
        )
Backend Usage recommandé Librairie Coût
Local FileSystem Développement, serveur unique aiofiles (déjà présent) Gratuit
MinIO (self-hosted) Production on-premise aioboto3 Infrastructure
AWS S3 Production cloud aioboto3 ~0.023$/GB/mois
Cloudflare R2 Production cloud économique aioboto3 (compatible S3) 0$ egress

5.3 URLs signées et sécurité d'accès

Remplacer les routes /static/ par des URLs signées à durée limitée. Le client reçoit une URL temporaire valide X minutes.

💡 Points d'implémentation

  • Endpoint : GET /images/{id}/download-url → retourne une URL signée valide 15 minutes
  • Paramètre optionnel : expires_in (en secondes, max configurable par plan)
  • Pour S3/R2 : URL pré-signée native. Pour stockage local : token HMAC signé par le serveur
  • Thumbnail : même mécanisme via GET /images/{id}/thumbnail-url

6. Axe 5 — WebSockets et notifications en temps réel

6.1 Problématique du polling

Actuellement, les clients doivent interroger GET /images/{id}/status régulièrement pour connaître l'avancement du pipeline. Cette approche génère du trafic inutile et ajoute de la latence perceptible.

6.2 Solution — WebSocket par session d'upload

Proposer un endpoint WebSocket que le client connecte après l'upload. Le serveur pousse des événements au fur et à mesure de l'avancement du pipeline.

Endpoint : WS /ws/pipeline/{image_id}?token=<api_key>

Événement WebSocket Payload
pipeline.started { "image_id": 42, "steps": ["exif", "ocr", "ai"] }
step.completed { "step": "exif", "duration_ms": 45, "data": { "camera": "Canon EOS R5" } }
step.completed { "step": "ocr", "duration_ms": 820, "data": { "has_text": true, "preview": "Café de Flore..." } }
step.completed { "step": "ai", "duration_ms": 3200, "data": { "tags": ["café", "paris"], "confidence": 0.97 } }
pipeline.done { "image_id": 42, "total_duration_ms": 4100, "status": "done" }
pipeline.error { "step": "ai", "error": "API timeout", "retry": 1 }
# app/routers/websocket.py
@router.websocket("/ws/pipeline/{image_id}")
async def pipeline_ws(
    websocket: WebSocket,
    image_id: int,
    token: str = Query(...),
):
    client = await verify_api_key_ws(token)  # auth via query param
    await websocket.accept()

    # S'abonner aux événements Redis publiés par le pipeline
    async with redis.subscribe(f"pipeline:{image_id}") as channel:
        async for message in channel:
            await websocket.send_json(message)
            if message.get("event") in ("pipeline.done", "pipeline.error"):
                break

💡 Points d'implémentation

  • Fallback automatique : si WebSocket non disponible, le client retombe sur le polling REST
  • FastAPI supporte nativement les WebSockets sans dépendance supplémentaire
  • Gestion de la déconnexion : événements bufferisés 60s si le client se reconnecte
  • Redis Pub/Sub : le pipeline publie ses événements, le WebSocket handler s'y abonne

7. Axe 6 — Observabilité et monitoring

7.1 Logs structurés

Remplacer les print() du code actuel par des logs structurés en JSON. Chaque log doit contenir au minimum : timestamp, level, client_id, image_id, action, duration_ms.

# app/logging.py
import structlog

log = structlog.get_logger()

# Exemple d'usage dans le pipeline
log.info(
    "pipeline.step.completed",
    image_id=image.id,
    client_id=client.id,
    step="ocr",
    duration_ms=820,
    has_text=True,
)

# Sortie JSON en production :
# {"event": "pipeline.step.completed", "image_id": 42, "client_id": "abc",
#  "step": "ocr", "duration_ms": 820, "has_text": true, "timestamp": "2026-02-23T..."}

💡 Points d'implémentation

  • Librairie recommandée : structlog (compatible FastAPI, format JSON natif)
  • Middleware de logs : enregistrer chaque requête HTTP avec méthode, path, status, duration, client_id
  • Niveaux : DEBUG (dev), INFO (prod normal), WARNING (anomalie récupérable), ERROR (action requise)
  • Exportation vers Loki, Datadog, CloudWatch selon infrastructure cible

7.2 Métriques applicatives

Exposer des métriques Prometheus sur /metrics pour un monitoring en temps réel.

Métrique Type Utilité
hub_images_uploaded_total Counter Volume d'uploads par client
hub_pipeline_duration_seconds Histogram Temps de traitement p50/p95/p99
hub_ai_tokens_consumed_total Counter Suivi des coûts AI par client
hub_pipeline_errors_total Counter Taux d'erreur par étape
hub_storage_used_bytes Gauge Espace disque par client
hub_active_websockets Gauge Connexions WebSocket actives
# main.py
from prometheus_fastapi_instrumentator import Instrumentator

Instrumentator().instrument(app).expose(app, endpoint="/metrics")

7.3 Health checks détaillés

Améliorer /health/detailed pour qu'il vérifie activement chaque composant.

@app.get("/health/detailed")
async def health_detailed():
    checks = {}

    # Base de données
    try:
        await db.execute(text("SELECT 1"))
        checks["database"] = {"status": "ok", "latency_ms": 12}
    except Exception as e:
        checks["database"] = {"status": "error", "detail": str(e)}

    # Tesseract
    checks["tesseract"] = _check_tesseract()

    # API Anthropic
    checks["anthropic"] = await _check_anthropic_api()

    # Espace disque
    usage = shutil.disk_usage(settings.UPLOAD_DIR)
    checks["disk"] = {
        "status": "ok" if usage.percent < 85 else "warning",
        "used_pct": round(usage.percent, 1)
    }

    # File ARQ
    checks["queue"] = await _check_arq_queue()

    overall = "healthy" if all(c["status"] == "ok" for c in checks.values()) else "degraded"
    return {"status": overall, "checks": checks}

8. Axe 7 — Versioning de l'API et SDK clients

8.1 Versioning des endpoints

Un hub servant plusieurs clients ne peut pas modifier ses endpoints sans risquer de casser les intégrations existantes.

Stratégie Exemple Recommandation
URL path /v1/images/upload, /v2/images/upload Simple, visible, recommandé
Header API-Version: 2024-01-15 Flexible mais moins visible
Query param ?version=1 À éviter, pollue les URLs

Adopter /api/v1/ comme préfixe dès maintenant. Définir une politique de dépréciation : une version est supportée au minimum 12 mois après publication de la suivante.

# main.py
from fastapi import APIRouter

v1 = APIRouter(prefix="/api/v1")
v1.include_router(images_router)
v1.include_router(ai_router)

app.include_router(v1)

# Middleware de dépréciation
@app.middleware("http")
async def deprecation_header(request: Request, call_next):
    response = await call_next(request)
    if request.url.path.startswith("/api/v1/"):
        # Ajouter quand v2 sera disponible
        # response.headers["Deprecation"] = "true"
        # response.headers["Sunset"] = "2027-02-01"
        pass
    return response

8.2 SDK Python officiel

Générer un SDK Python à partir des schémas OpenAPI du backend.

# Génération automatique depuis /openapi.json
openapi-generator-cli generate \
  -i http://localhost:8000/openapi.json \
  -g python \
  -o ./sdk \
  --package-name imago_client
# Exemple d'utilisation du SDK par les clients
from imago_client import HubClient

client = HubClient(api_key="sk-...", base_url="https://hub.example.com")

# Upload avec gestion automatique du pipeline
result = await client.images.upload("photo.jpg")
await result.wait_until_done()          # WebSocket ou polling selon dispo

print(result.ai.description)
print(result.ai.tags)
print(result.exif.camera.model)

💡 Points d'implémentation

  • Package installable : pip install imago-client
  • Fonctionnalités : upload, polling, WebSocket, gestion des erreurs, retry automatique
  • Publication : PyPI (public) ou Gitea/GitHub Packages (privé)

9. Axe 8 — Tests, qualité de code et CI/CD

9.1 Stratégie de tests

Niveau Ce qu'on teste Couverture cible Outils
Unitaires Services isolés (EXIF, OCR, storage) 90%+ pytest, unittest.mock
Intégration Endpoints FastAPI + BDD de test 80%+ httpx.AsyncClient, SQLite in-memory
End-to-End Pipeline complet upload → AI Scénarios clés pytest-asyncio, mocks API Anthropic
Performance Charge concurrente, temps de réponse Benchmarks locust, k6
# tests/test_api_integration.py
import pytest
from httpx import AsyncClient
from app.main import app

@pytest.mark.asyncio
async def test_upload_and_pipeline():
    async with AsyncClient(app=app, base_url="http://test") as client:
        # Upload avec API Key de test
        headers = {"Authorization": "Bearer test-key-123"}
        with open("tests/fixtures/photo.jpg", "rb") as f:
            response = await client.post(
                "/api/v1/images/upload",
                files={"file": f},
                headers=headers
            )

        assert response.status_code == 201
        data = response.json()
        assert data["status"] == "pending"

        # Vérifier le statut après pipeline (mocké)
        status = await client.get(f"/api/v1/images/{data['id']}/status", headers=headers)
        assert status.json()["status"] in ("processing", "done")

9.2 Qualité de code

Outil Rôle Configuration
ruff Linter ultra-rapide (remplace flake8 + isort) ruff check . --fix
black Formatage automatique black . --line-length 100
mypy Vérification des types statiques mypy app/ --strict
bandit Analyse de sécurité du code Python bandit -r app/
pre-commit Exécute tous les checks avant chaque commit .pre-commit-config.yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.3.0
    hooks:
      - id: ruff
        args: [--fix]
  - repo: https://github.com/psf/black
    rev: 24.2.0
    hooks:
      - id: black
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        args: [--strict]

9.3 Pipeline CI/CD

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install -r requirements-dev.txt
      - run: ruff check .
      - run: black --check .
      - run: mypy app/ --strict
      - run: bandit -r app/ -ll

  tests:
    runs-on: ubuntu-latest
    services:
      redis:
        image: redis:7
        ports: ["6379:6379"]
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt -r requirements-dev.txt
      - run: pytest tests/ -v --cov=app --cov-report=xml
      - uses: codecov/codecov-action@v4

  docker:
    needs: [quality, tests]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t imago:latest .
      - run: docker push registry.example.com/imago:latest

💡 Étapes du pipeline

  • Sur chaque Pull Request : ruff + mypy + bandit + tests unitaires + tests d'intégration
  • Sur merge main : build image Docker + push registry + déploiement staging automatique
  • Sur tag release : déploiement production avec approbation manuelle
  • Rapports : couverture de tests publiée sur chaque PR, alertes Slack si échec

10. Feuille de route proposée

Les 8 axes ont été organisés en trois phases. Chaque phase est indépendante et livre de la valeur. Les phases ne se bloquent pas mutuellement, certains travaux peuvent commencer en parallèle.

Phase 1 — Fondations sécurité (Semaines 13)

# Livrable Axe Effort Priorité
1.1 Authentification API Keys + JWT avec scopes Axe 1 35 j 🔴 Critique
1.2 Modèle clients + isolation des données Axe 2 57 j 🔴 Critique
1.3 Rate limiting par client et par endpoint Axe 1 12 j 🟠 Haute
1.4 Tests d'intégration auth + multi-tenants Axe 8 23 j 🟠 Haute

Résultat de la phase 1 : hub sécurisé, données isolées par client, déployable en production.

Phase 2 — Robustesse et scalabilité (Semaines 46)

# Livrable Axe Effort Priorité
2.1 Migration BackgroundTasks → ARQ + Redis Axe 3 45 j 🟠 Haute
2.2 Abstraction StorageBackend + support MinIO/S3 Axe 4 34 j 🟠 Haute
2.3 URLs signées pour accès aux fichiers Axe 4 12 j 🟠 Haute
2.4 Logs structurés (structlog) + métriques Prometheus Axe 6 34 j 🟡 Moyenne
2.5 CI/CD complet avec GitHub Actions Axe 8 23 j 🟠 Haute

Résultat de la phase 2 : pipeline robuste avec retry, stockage abstrait, observabilité opérationnelle.

Phase 3 — Expérience développeur (Semaines 710)

# Livrable Axe Effort Priorité
3.1 WebSocket pipeline temps réel Axe 5 23 j 🟡 Moyenne
3.2 API versioning /api/v1/ + politique de dépréciation Axe 7 12 j 🟡 Moyenne
3.3 SDK Python généré + publié sur PyPI Axe 7 34 j 🟡 Moyenne
3.4 Dashboard admin (quotas, métriques, clients) Axe 6 45 j 🟢 Faible
3.5 Intégration Shaarli complète + documentation Axe 7 23 j 🟠 Haute

Résultat de la phase 3 : hub avec SDK, WebSockets et intégration Shaarli finalisée.


11. Stack technique finale recommandée

La stack suivante est une évolution directe de la stack actuelle. Chaque ajout répond à un besoin identifié dans ce rapport. Rien n'est ajouté par effet de mode.

Couche Librairie Justification
Web Framework FastAPI + Uvicorn Déjà en place. Performance async excellente.
Base de données SQLAlchemy async + Alembic Déjà en place. Migrations propres.
File de tâches ARQ + Redis Pipeline robuste, retry, persistance, priorités.
Cache Redis (partagé avec ARQ) Cache résultats AI, sessions WebSocket.
Authentification python-jose + passlib JWT + hachage des API Keys.
Stockage fichiers Local → MinIO → S3/R2 Interface abstraite. Migration transparente.
Traitement images Pillow + piexif Déjà en place.
OCR pytesseract + Tesseract Déjà en place.
Vision AI Anthropic Claude (httpx) Déjà en place. Abstraction multi-provider future.
Logs structlog JSON structuré, compatible Loki/Datadog.
Métriques prometheus-fastapi-instrumentator Exposition automatique des métriques HTTP.
Qualité code ruff + black + mypy + bandit Lint, format, types, sécurité.
Tests pytest-asyncio + httpx Tests unitaires et d'intégration async.
CI/CD GitHub Actions / Gitea CI Automatisation complète du cycle de vie.

requirements.txt mis à jour

# Existant
fastapi==0.115.0
uvicorn[standard]==0.30.6
sqlalchemy[asyncio]==2.0.35
alembic==1.13.3
pydantic-settings==2.5.2
pillow==10.4.0
piexif==1.1.3
pytesseract==0.3.13
anthropic==0.34.2
httpx==0.27.2
beautifulsoup4==4.12.3
aiofiles==24.1.0

# Nouveaux — Phase 1
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
slowapi==0.1.9               # rate limiting

# Nouveaux — Phase 2
arq==0.25.0                  # file de tâches async
redis==5.0.8                 # client Redis
aioboto3==13.0.0             # S3/MinIO async
structlog==24.4.0            # logs structurés
prometheus-fastapi-instrumentator==0.14.0

# Nouveaux — Phase 3
websockets==13.0             # WebSocket (déjà dans FastAPI)

12. Conclusion

Le backend Imago dispose d'une base technique solide. L'architecture FastAPI + SQLAlchemy async est bien choisie et évolutive. Les services EXIF, OCR et Vision AI sont fonctionnels et bien découpés.

La priorité absolue est la sécurisation avant tout passage en production multi-clients. L'authentification et l'isolation des données par client (Phase 1) sont des prérequis non négociables. Ces deux axes représentent environ 10 jours de développement.

Une fois la sécurité en place, la Phase 2 transforme le backend en un service de production véritable avec un pipeline robuste, un stockage abstrait et un monitoring opérationnel. La Phase 3 améliore l'expérience des équipes qui intègrent le hub.

Résumé des horizons

  • 10 jours : Phase 1 complète — hub sécurisé et multi-clients opérationnel
  • 20 jours : Phase 2 complète — hub production-ready, robuste et observable
  • 30 jours : Phase 3 complète — hub avec SDK, WebSockets et intégration Shaarli finalisée

Imago — Rapport d'amélioration v1.0.0 — Février 2026