Imago/README.md

404 lines
13 KiB
Markdown

# Imago
Backend FastAPI pour la gestion d'images et fonctionnalités AI, conçu comme complément à l'interface Web-thème **Shaarli-Pro** et toute autre applications qui voudrait l'utiliser.
---
## Fonctionnalités
| Feature | Description |
|---|---|
| 📸 Upload d'images | Stockage + thumbnails, isolés par client (multi-tenant) |
| 🛡️ Sécurité | Authentification par API Key + scopes granulaires + Rate Limiting |
| 🔍 Extraction EXIF | Appareil photo, GPS, ISO, ouverture, date de prise de vue |
| 📝 OCR | Extraction du texte visible dans l'image (Tesseract + fallback AI) |
| 🤖 Vision AI | Description naturelle + classification par tags (Gemini / OpenRouter) |
| 🔗 Résumé URL | Scraping d'une page web + résumé AI |
| ✅ Rédaction de tâches | Génération structurée d'une tâche à partir d'une description |
| 📋 File de tâches ARQ | Pipeline persistant avec retry automatique (Redis) |
| 📊 Métriques Prometheus | Endpoint `/metrics` + custom counters/histograms |
| 💾 Storage abstrait | Local (HMAC signed URLs) ou S3/MinIO (presigned URLs) |
| 📈 Quota tracking | Suivi du stockage par client avec enforcement |
| 🔧 Logging structuré | JSON (production) / Console colorée (dev) via structlog |
---
## Stack technique
```
FastAPI + Uvicorn — Framework web async
SQLAlchemy (async) — ORM + SQLite/Postgres
ARQ + Redis — File de tâches persistante
Pillow + piexif — Traitement d'images + EXIF
pytesseract — OCR (Tesseract)
Google GenAI SDK — Vision AI (Gemini)
aioboto3 — Stockage S3/MinIO
structlog — Logging structuré JSON
Prometheus — Métriques et monitoring
slowapi — Rate limiting par client
ruff — Linting + formatting
```
---
## Installation
### Prérequis
- Python 3.12+ (3.14 supporté)
- Tesseract OCR installé sur le système
- Redis (optionnel, pour ARQ worker)
```bash
# Ubuntu/Debian
sudo apt install tesseract-ocr tesseract-ocr-fra tesseract-ocr-eng
# macOS
brew install tesseract tesseract-lang
# Windows
scoop install main/tesseract
```
### Setup
> [!NOTE]
> Les fichiers `requirements.txt` appliquent automatiquement des versions compatibles Python 3.14 (notamment pour Pillow et Pydantic).
```bash
# 1. Cloner et installer
git clone <repo>
cd imago
# Production
pip install -r requirements.txt
# Développement (inclut linting, tests, pre-commit)
pip install -r requirements-dev.txt
pre-commit install
# 2. Configuration
cp .env.example .env
# Éditer .env — voir la section Configuration ci-dessous
# 3. Migrations
alembic upgrade head
# La migration crée un client "default" et affiche sa clé API une seule fois.
# 4. Démarrage
python run.py # API sur http://localhost:8000
python worker.py # Worker ARQ (requiert Redis)
```
### Avec Docker
```bash
docker-compose up -d # API + Redis + Worker + PostgreSQL
```
### Commandes utiles (Makefile)
```bash
make help # Affiche toutes les commandes
make install-dev # Dépendances dev + pre-commit
make lint # Ruff + Mypy
make format # Auto-format du code
make test # Tests rapides
make test-cov # Tests + couverture HTML
make serve # Serveur dev (reload)
make worker # Worker ARQ
make docker-up # Docker compose up
make clean # Nettoyage __pycache__, .pytest_cache, etc.
```
---
## Pipeline de traitement AI
Chaque image uploadée déclenche un pipeline via ARQ (Redis) :
```
Upload → Sauvegarde fichier + thumbnail → BDD (PENDING)
▼ (ARQ Worker — persistant, avec retry)
┌────────────────────────────────────────┐
│ Étape 1 — Extraction EXIF │
│ Étape 2 — OCR (Tesseract + AI) │
│ Étape 3 — Vision AI (Gemini) │
└────────────────────────────────────────┘
BDD (DONE) + événements Redis pub/sub
```
> [!IMPORTANT]
> **Isolation multi-tenant** : fichiers et données sont compartimentés par `client_id`. Un client ne peut ni voir, ni modifier, ni supprimer les images d'un autre.
Chaque étape est **indépendante** : un échec partiel n'arrête pas le pipeline.
**Priority queues** : les clients `premium` ont une file dédiée.
**Retry automatique** avec backoff exponentiel. Dead-letter après max retries.
---
## Endpoints API
### Images (Auth requise)
| Méthode | URL | Description | Scope |
|---|---|---|---|
| `POST` | `/images/upload` | Uploader une image | `images:write` |
| `GET` | `/images` | Lister (paginé, filtrable, quota info) | `images:read` |
| `GET` | `/images/{id}` | Détail complet | `images:read` |
| `GET` | `/images/{id}/status` | Statut du pipeline | `images:read` |
| `GET` | `/images/{id}/exif` | Métadonnées EXIF | `images:read` |
| `GET` | `/images/{id}/ocr` | Texte OCR extrait | `images:read` |
| `GET` | `/images/{id}/ai` | Description + tags AI | `images:read` |
| `GET` | `/images/{id}/download-url` | URL signée de téléchargement | `images:read` |
| `GET` | `/images/{id}/thumbnail-url` | URL signée du thumbnail | `images:read` |
| `GET` | `/images/tags/all` | Tous les tags du client | `images:read` |
| `POST` | `/images/{id}/reprocess` | Relancer le pipeline | `images:write` |
| `DELETE` | `/images/{id}` | Supprimer image | `images:delete` |
### Intelligence Artificielle (Auth requise)
| Méthode | URL | Description | Scope |
|---|---|---|---|
| `POST` | `/ai/summarize` | Résumé AI d'une URL | `ai:use` |
| `POST` | `/ai/draft-task` | Rédaction de tâche | `ai:use` |
### Administration & Clients (Admin only)
| Méthode | URL | Description | Scope |
|---|---|---|---|
| `POST` | `/auth/clients` | Créer un client API | `admin` |
| `GET` | `/auth/clients` | Lister les clients | `admin` |
| `GET` | `/auth/clients/{id}` | Détail d'un client | `admin` |
| `PATCH` | `/auth/clients/{id}` | Modifier client (plan, scopes) | `admin` |
| `POST` | `/auth/clients/{id}/rotate-key` | Régénérer clé API | `admin` |
| `DELETE` | `/auth/clients/{id}` | Soft delete (désactivation) | `admin` |
### Fichiers signés
| Méthode | URL | Description | Auth |
|---|---|---|---|
| `GET` | `/files/signed/{token}` | Télécharger via URL signée | Token HMAC |
### Observabilité & Santé
| Méthode | URL | Description | Auth |
|---|---|---|---|
| `GET` | `/` | Info application | Non |
| `GET` | `/health` | Santé du service | Non |
| `GET` | `/health/detailed` | Santé détaillée (DB, Redis, ARQ, OCR) | Non |
| `GET` | `/metrics` | Métriques Prometheus | Non |
| `GET` | `/docs` | Documentation Swagger | Non |
---
## Exemples d'utilisation
> [!TIP]
> Tous les appels (sauf `/health` et `/metrics`) nécessitent une clé API valide passée dans le header `X-API-Key`.
### Upload d'une image
### api_key=emH92l92LD4L7cLhl2imidMZANsIUb9x_AlGWiYpVSA client_id=925463e0-27a4-4993-aa3a-f1cb31c19d32 warning=Notez cette clé ! Elle ne sera plus affichée.
```bash
curl -X POST http://localhost:8000/images/upload \
-H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg" \
-F "file=@pushup.gif"
```
Réponse :
```json
{
"id": 1,
"uuid": "a1b2c3d4-...",
"original_name": "photo.jpg",
"status": "pending",
"message": "Image uploadée — traitement AI en cours"
}
```
### Polling du statut
```bash
curl http://localhost:8000/images/1/status -H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg"
```
```json
{
"id": 1,
"status": "done",
"started_at": "2024-01-15T10:30:00",
"done_at": "2024-01-15T10:30:08"
}
```
### Détail complet
```bash
curl http://localhost:8000/images/1 -H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg"
```
```json
{
"id": 1,
"original_name": "photo.jpg",
"processing_status": "done",
"exif": {
"camera": {
"make": "Canon",
"model": "EOS R5",
"iso": 400,
"aperture": "f/2.8",
"shutter_speed": "1/250",
"focal_length": "50mm",
"taken_at": "2024-06-15T14:30:00"
},
"gps": {
"latitude": 48.8566,
"longitude": 2.3522,
"has_gps": true,
"maps_url": "https://maps.google.com/?q=48.8566,2.3522"
}
},
"ocr": {
"has_text": true,
"text": "Café de Flore",
"language": "fr",
"confidence": 0.94
},
"ai": {
"description": "Une terrasse de café parisien animée en fin d'après-midi. Les tables en osier sont disposées sur le trottoir boulevard Saint-Germain, avec une clientèle détendue profitant du soleil. L'enseigne emblématique du Café de Flore est visible en arrière-plan.",
"tags": ["café", "paris", "terrasse", "france", "bistrot", "extérieur", "urbain"],
"confidence": 0.97,
"model_used": "gemini-1.5-pro"
}
}
```
### Résumé d'URL
```bash
curl -X POST http://localhost:8000/ai/summarize \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/article", "language": "français"}'
```
### Rédaction de tâche
```bash
curl -X POST http://localhost:8000/ai/draft-task \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"description": "Mettre à jour la documentation technique du projet backend",
"context": "Projet Imago, backend FastAPI"
}'
```
---
## Configuration
| Variable | Défaut | Description |
|---|---|---|
| `ADMIN_API_KEY` | — | Clé maîtresse pour gérer les clients API |
| `JWT_SECRET_KEY` | — | Secret pour la signature des tokens |
| `AI_PROVIDER` | `gemini` | `gemini` ou `openrouter` |
| `GEMINI_API_KEY` | — | Clé API Gemini |
| `DATABASE_URL` | PostgreSQL (Docker) / SQLite (Local) | URL de connexion (Postgres recommandé) |
| `REDIS_URL` | `redis://localhost:6379/0` | URL Redis pour ARQ |
| `STORAGE_BACKEND` | `local` | `local` ou `s3` |
| `S3_BUCKET` | — | Bucket S3/MinIO |
| `S3_ENDPOINT_URL` | — | Endpoint S3 custom (MinIO/R2) |
| `S3_ACCESS_KEY` | — | Clé d'accès S3 |
| `S3_SECRET_KEY` | — | Secret S3 |
| `SIGNED_URL_SECRET` | — | Secret HMAC pour URLs signées locales |
| `MAX_UPLOAD_SIZE_MB` | `50` | Taille max par upload |
| `OCR_ENABLED` | `true` | Activer/désactiver l'OCR |
| `WORKER_MAX_JOBS` | `10` | Concurrence du worker ARQ |
| `WORKER_MAX_TRIES` | `3` | Retries max avant dead-letter |
| `DEBUG` | `false` | Mode debug (console logging) |
---
## Tests
```bash
# Tests rapides
make test
# Tests avec couverture
make test-cov
# Directement
python -m pytest tests/ -v --cov=app
```
76 tests couvrant : auth, isolation multi-tenant, rate limiting, pipeline, EXIF, OCR, AI, stockage.
---
## CI/CD
Le pipeline GitHub Actions (`.github/workflows/ci.yml`) exécute :
1. **Lint** — ruff check + format + mypy
2. **Tests** — pytest + couverture
3. **Sécurité** — bandit via ruff (règles S)
4. **Docker** — build + smoke test (branche main uniquement)
---
## Structure du projet
```
imago/
├── app/
│ ├── main.py # FastAPI + lifespan + Prometheus
│ ├── config.py # Settings Pydantic
│ ├── database.py # Engine SQLAlchemy async
│ ├── logging_config.py # structlog JSON/Console
│ ├── metrics.py # Prometheus custom metrics
│ ├── models/
│ │ ├── image.py # Modèle Image
│ │ └── client.py # APIClient + quotas
│ ├── schemas/
│ │ ├── __init__.py # Schémas images + quota
│ │ └── auth.py # Schémas Auth/Clients
│ ├── routers/
│ │ ├── images.py # CRUD images + signed URLs
│ │ ├── ai.py # Endpoints AI
│ │ ├── auth.py # Gestion clients
│ │ └── files.py # Serving signed local files
│ ├── dependencies/
│ │ └── auth.py # verify_api_key, require_scope
│ ├── middleware/
│ │ ├── rate_limit.py # slowapi configuration
│ │ └── logging_middleware.py # HTTP request logger
│ ├── services/
│ │ ├── storage.py # Sauvegarde isolée par client
│ │ ├── storage_backend.py # ABC + Local/S3 backends
│ │ ├── pipeline.py # Pipeline EXIF → OCR → AI
│ │ ├── ai_vision.py # Gemini/OpenRouter
│ │ ├── exif_service.py # Extraction EXIF
│ │ ├── ocr_service.py # Tesseract OCR
│ │ └── scraper.py # Scraping web
│ └── workers/
│ ├── redis_client.py # Redis pool partagé
│ └── image_worker.py # ARQ worker + retries
├── tests/ # 76 tests
├── .github/workflows/ci.yml # CI/CD pipeline
├── pyproject.toml # ruff, mypy, coverage config
├── Makefile # Commandes utiles
├── docker-compose.yml # API + Redis + Worker + PostgreSQL
├── Dockerfile
├── requirements.txt # Production deps
├── requirements-dev.txt # Dev deps (lint, test)
├── .pre-commit-config.yaml # Pre-commit hooks
├── worker.py # ARQ worker entrypoint
└── .env.example
```