- 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.
404 lines
13 KiB
Markdown
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
|
|
```
|
|
|
|
### 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
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/images/upload \
|
|
-H "X-API-Key: your_api_key" \
|
|
-F "file=@photo.jpg"
|
|
```
|
|
|
|
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: your_api_key"
|
|
```
|
|
|
|
```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: your_api_key"
|
|
```
|
|
|
|
```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` | SQLite local | URL de connexion (SQLite ou Postgres) |
|
|
| `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
|
|
├── 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
|
|
```
|