feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-03-07 11:42:58 +01:00
parent 3d37ce4582
commit 473b3e26c7
181 changed files with 30617 additions and 7170 deletions

98
schemas/__init__.py Normal file
View File

@@ -0,0 +1,98 @@
"""
Pydantic models for API documentation and validation
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from .translation import (
TranslateResponseData,
TranslateResponseMeta,
TranslateResponse,
TranslationStatusData,
TranslationStatusMeta,
TranslationStatusResponse,
LanguageResponse,
)
from .auth import (
RegisterRequest,
LoginRequest,
TokenResponse,
LogoutResponse,
RefreshRequest,
)
from .api_keys import (
APIKeyCreateRequest,
APIKeyResponse,
APIKeyListResponse,
APIKeyRevokeResponse,
)
from .admin import (
AdminLoginRequest,
AdminLoginResponse,
AdminDashboardResponse,
AdminUserResponse,
AdminUserUpdateRequest,
AdminStatsResponse,
AdminRevokeApiKeyRequest,
)
from .errors import ErrorResponse, ErrorCode
from .common import (
SuccessResponse,
HealthCheckResponse,
ReadyCheckResponse,
)
from .glossary_schemas import (
GlossaryTermCreate,
GlossaryTermResponse,
GlossaryCreate,
GlossaryUpdate,
GlossaryResponse,
GlossaryListItem,
GlossaryListResponse,
GlossaryDetailResponse,
)
__all__ = [
# Translation
"TranslateResponseData",
"TranslateResponseMeta",
"TranslateResponse",
"TranslationStatusData",
"TranslationStatusMeta",
"TranslationStatusResponse",
"LanguageResponse",
# Auth
"RegisterRequest",
"LoginRequest",
"TokenResponse",
"LogoutResponse",
"RefreshRequest",
# API Keys
"APIKeyCreateRequest",
"APIKeyResponse",
"APIKeyListResponse",
"APIKeyRevokeResponse",
# Admin
"AdminLoginRequest",
"AdminLoginResponse",
"AdminDashboardResponse",
"AdminUserResponse",
"AdminUserUpdateRequest",
"AdminStatsResponse",
"AdminRevokeApiKeyRequest",
# Errors
"ErrorResponse",
"ErrorCode",
# Common
"SuccessResponse",
"HealthCheckResponse",
"ReadyCheckResponse",
# Glossaries
"GlossaryTermCreate",
"GlossaryTermResponse",
"GlossaryCreate",
"GlossaryUpdate",
"GlossaryResponse",
"GlossaryListItem",
"GlossaryListResponse",
"GlossaryDetailResponse",
]

262
schemas/admin.py Normal file
View File

@@ -0,0 +1,262 @@
"""
Pydantic models for admin endpoints
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from pydantic import BaseModel, Field
from typing import Optional, Literal, Dict, Any, List
class AdminLoginRequest(BaseModel):
"""Request model for admin login"""
password: str = Field(
...,
example="admin_secret_password",
description="Mot de passe administrateur"
)
class Config:
json_schema_extra = {
"example": {
"password": "admin_secret_password"
}
}
class AdminLoginData(BaseModel):
"""Admin login response data"""
access_token: str = Field(
...,
example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description="Token d'accès admin (expire dans 24h)"
)
token_type: str = Field(
default="bearer",
example="bearer",
description="Type de token"
)
expires_in: int = Field(
default=86400,
example=86400,
description="Durée de validité en secondes"
)
class AdminLoginResponse(BaseModel):
"""Response model for admin login"""
status: str = Field(default="success", description="Statut de la connexion")
access_token: str = Field(..., description="Token d'accès admin")
token_type: str = Field(default="bearer", description="Type de token")
expires_in: int = Field(default=86400, description="Durée de validité en secondes")
message: str = Field(default="Login successful", description="Message de confirmation")
class Config:
json_schema_extra = {
"example": {
"status": "success",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 86400,
"message": "Login successful"
}
}
class AdminUserUpdateRequest(BaseModel):
"""Request model for updating user tier"""
plan: Literal["free", "starter", "pro", "business", "enterprise"] = Field(
...,
example="pro",
description="Nouveau plan d'abonnement"
)
class Config:
json_schema_extra = {
"example": {
"plan": "pro"
}
}
class AdminUserData(BaseModel):
"""User data in admin responses"""
id: str = Field(..., description="Identifiant unique de l'utilisateur")
email: str = Field(..., description="Adresse email")
name: str = Field(..., description="Nom de l'utilisateur")
plan: str = Field(..., description="Plan d'abonnement actuel")
subscription_status: str = Field(..., description="Statut de l'abonnement")
docs_translated_this_month: int = Field(..., description="Documents traduits ce mois")
pages_translated_this_month: int = Field(..., description="Pages traduites ce mois")
extra_credits: int = Field(..., description="Crédits supplémentaires")
created_at: str = Field(..., description="Date de création du compte")
plan_limits: Dict[str, Any] = Field(..., description="Limites du plan actuel")
class AdminUserResponse(BaseModel):
"""Response model for admin user operations"""
data: AdminUserData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "usr_abc123def456",
"email": "utilisateur@exemple.com",
"name": "Jean Dupont",
"plan": "pro",
"subscription_status": "active",
"docs_translated_this_month": 15,
"pages_translated_this_month": 42,
"extra_credits": 0,
"created_at": "2024-01-01T00:00:00Z",
"plan_limits": {
"docs_per_month": 100,
"max_pages_per_doc": 100
}
},
"meta": {}
}
}
class AdminUsersListResponse(BaseModel):
"""Response model for listing users"""
total: int = Field(..., description="Nombre total d'utilisateurs")
users: List[AdminUserData] = Field(..., description="Liste des utilisateurs")
class AdminDashboardResponse(BaseModel):
"""Response model for admin dashboard"""
timestamp: str = Field(..., description="Timestamp de la réponse")
status: str = Field(..., description="Statut global du système")
system: Dict[str, Any] = Field(..., description="Informations système")
providers: Dict[str, Any] = Field(..., description="Statut des providers")
cleanup: Dict[str, Any] = Field(..., description="Statut du cleanup")
rate_limits: Dict[str, Any] = Field(..., description="Statut des rate limits")
config: Dict[str, Any] = Field(..., description="Configuration actuelle")
class Config:
json_schema_extra = {
"example": {
"timestamp": "2024-01-15T10:30:00Z",
"status": "healthy",
"system": {
"memory": {},
"disk": {}
},
"providers": {
"google": {
"name": "google",
"available": True,
"last_check": "2024-01-15T10:29:00Z"
}
},
"cleanup": {
"files_cleaned": 12,
"tracked_files_count": 5
},
"rate_limits": {
"active_clients": 3
},
"config": {
"max_file_size_mb": 50,
"supported_extensions": [".xlsx", ".docx", ".pptx"],
"translation_service": "google"
}
}
}
class AdminStatsResponse(BaseModel):
"""Response model for admin statistics"""
users: Dict[str, Any] = Field(..., description="Statistiques utilisateurs")
translations: Dict[str, Any] = Field(..., description="Statistiques de traduction")
cache: Dict[str, Any] = Field(..., description="Statistiques du cache")
config: Dict[str, Any] = Field(..., description="Configuration actuelle")
class Config:
json_schema_extra = {
"example": {
"users": {
"total": 150,
"active_this_month": 45,
"by_plan": {
"free": 100,
"pro": 40,
"business": 10
}
},
"translations": {
"docs_this_month": 350,
"pages_this_month": 1250
},
"cache": {
"hits": 1500,
"misses": 500,
"size": 42
},
"config": {
"translation_service": "google",
"max_file_size_mb": 50,
"supported_extensions": [".xlsx", ".docx", ".pptx"]
}
}
}
class AdminRevokeApiKeyRequest(BaseModel):
"""Request model for admin API key revocation"""
reason: Optional[str] = Field(
None,
example="Violation des conditions d'utilisation",
description="Raison de la révocation (optionnel)"
)
class Config:
json_schema_extra = {
"example": {
"reason": "Violation des conditions d'utilisation"
}
}
class AdminRevokeApiKeyData(BaseModel):
"""Data returned after admin API key revocation"""
id: str = Field(..., description="Identifiant de la clé révoquée")
revoked: bool = Field(..., description="Confirmation de révocation")
revoked_at: str = Field(..., description="Date de révocation (ISO 8601)")
owner_user_id: str = Field(..., description="ID de l'utilisateur propriétaire")
reason: Optional[str] = Field(None, description="Raison de la révocation")
class AdminRevokeApiKeyResponse(BaseModel):
"""Response model for admin API key revocation"""
data: AdminRevokeApiKeyData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"revoked": True,
"revoked_at": "2024-01-15T16:00:00Z",
"owner_user_id": "usr_abc123def456",
"reason": "Violation des conditions d'utilisation"
},
"meta": {}
}
}

177
schemas/api_keys.py Normal file
View File

@@ -0,0 +1,177 @@
"""
Pydantic models for API key endpoints
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from pydantic import BaseModel, Field
from typing import Optional
class APIKeyCreateRequest(BaseModel):
"""Request model for creating an API key"""
name: Optional[str] = Field(
default="Default API Key",
max_length=100,
example="Production API Key",
description="Nom descriptif pour la clé API"
)
class Config:
json_schema_extra = {
"example": {
"name": "Production API Key"
}
}
class APIKeyData(BaseModel):
"""API key data in response (full key shown only on creation)"""
id: str = Field(
...,
example="550e8400-e29b-41d4-a716-446655440000",
description="Identifiant unique de la clé API"
)
key: str = Field(
...,
example="sk_live_abc123def456ghi789...",
description="Clé API complète (affichée UNE SEULE FOIS à la création)"
)
name: str = Field(
...,
example="Production API Key",
description="Nom de la clé API"
)
key_prefix: str = Field(
...,
example="sk_live_",
description="Préfixe de la clé (pour identification)"
)
created_at: str = Field(
...,
example="2024-01-15T10:30:00Z",
description="Date de création (ISO 8601)"
)
class APIKeyResponse(BaseModel):
"""Response model for API key creation"""
data: APIKeyData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "sk_live_abc123def456ghi789jkl012mno345pqr678...",
"name": "Production API Key",
"key_prefix": "sk_live_",
"created_at": "2024-01-15T10:30:00Z"
},
"meta": {}
}
}
class APIKeyListItem(BaseModel):
"""API key item in list (without secret)"""
id: str = Field(..., description="Identifiant unique de la clé API")
name: str = Field(..., description="Nom de la clé API")
key_prefix: str = Field(..., description="Préfixe de la clé (pour identification)")
is_active: bool = Field(..., description="Si la clé est active")
last_used_at: Optional[str] = Field(None, description="Dernière utilisation (ISO 8601)")
usage_count: int = Field(..., description="Nombre total d'utilisations")
created_at: str = Field(..., description="Date de création (ISO 8601)")
class Config:
json_schema_extra = {
"example": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production API Key",
"key_prefix": "sk_live_",
"is_active": True,
"last_used_at": "2024-01-15T14:30:00Z",
"usage_count": 42,
"created_at": "2024-01-15T10:30:00Z"
}
}
class APIKeyListMeta(BaseModel):
"""Metadata for API key list response"""
total: int = Field(..., description="Nombre total de clés API")
class Config:
json_schema_extra = {
"example": {
"total": 2
}
}
class APIKeyListResponse(BaseModel):
"""Response model for listing API keys"""
data: list[APIKeyListItem]
meta: APIKeyListMeta
class Config:
json_schema_extra = {
"example": {
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production API Key",
"key_prefix": "sk_live_",
"is_active": True,
"last_used_at": "2024-01-15T14:30:00Z",
"usage_count": 42,
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Development API Key",
"key_prefix": "sk_live_",
"is_active": True,
"last_used_at": None,
"usage_count": 0,
"created_at": "2024-01-16T09:00:00Z"
}
],
"meta": {
"total": 2
}
}
}
class APIKeyRevokeData(BaseModel):
"""Data returned after API key revocation"""
id: str = Field(..., description="Identifiant de la clé révoquée")
revoked: bool = Field(..., description="Confirmation de révocation")
revoked_at: str = Field(..., description="Date de révocation (ISO 8601)")
class APIKeyRevokeResponse(BaseModel):
"""Response model for API key revocation"""
data: APIKeyRevokeData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"revoked": True,
"revoked_at": "2024-01-15T16:00:00Z"
},
"meta": {}
}
}

163
schemas/auth.py Normal file
View File

@@ -0,0 +1,163 @@
"""
Pydantic models for authentication endpoints
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
class RegisterRequest(BaseModel):
"""Request model for user registration"""
email: EmailStr = Field(
...,
example="utilisateur@exemple.com",
description="Adresse email de l'utilisateur"
)
password: str = Field(
...,
example="MotDePasse123!",
description="Mot de passe (min 8 caractères)",
min_length=8
)
name: Optional[str] = Field(
None,
example="Jean Dupont",
description="Nom complet de l'utilisateur (optionnel)"
)
class Config:
json_schema_extra = {
"example": {
"email": "utilisateur@exemple.com",
"password": "MotDePasse123!",
"name": "Jean Dupont"
}
}
class LoginRequest(BaseModel):
"""Request model for user login"""
email: EmailStr = Field(
...,
example="utilisateur@exemple.com",
description="Adresse email de l'utilisateur"
)
password: str = Field(
...,
example="MotDePasse123!",
description="Mot de passe"
)
class Config:
json_schema_extra = {
"example": {
"email": "utilisateur@exemple.com",
"password": "MotDePasse123!"
}
}
class TokenData(BaseModel):
"""Token data in response"""
access_token: str = Field(
...,
example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description="Token d'accès JWT (expire dans 15 minutes)"
)
refresh_token: str = Field(
...,
example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description="Token de rafraîchissement JWT (expire dans 7 jours)"
)
token_type: str = Field(
default="bearer",
example="bearer",
description="Type de token"
)
class TokenResponse(BaseModel):
"""Response model for authentication tokens"""
data: TokenData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
},
"meta": {}
}
}
class LogoutResponse(BaseModel):
"""Response model for logout"""
data: dict = Field(
default_factory=lambda: {"message": "Déconnexion réussie"},
description="Message de confirmation"
)
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"message": "Déconnexion réussie"
},
"meta": {}
}
}
class RefreshRequest(BaseModel):
"""Request model for token refresh"""
refresh_token: str = Field(
...,
example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description="Token de rafraîchissement"
)
class Config:
json_schema_extra = {
"example": {
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
class UserData(BaseModel):
"""User data in responses"""
id: str = Field(..., description="Identifiant unique de l'utilisateur")
email: str = Field(..., description="Adresse email")
tier: str = Field(..., description="Niveau d'abonnement (free, pro, etc.)")
class RegisterResponse(BaseModel):
"""Response model for registration"""
data: UserData
meta: dict = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "usr_abc123def456",
"email": "utilisateur@exemple.com",
"tier": "free"
},
"meta": {}
}
}

179
schemas/common.py Normal file
View File

@@ -0,0 +1,179 @@
"""
Common Pydantic models for API documentation
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
class SuccessResponse(BaseModel):
"""Generic success response"""
data: Dict[str, Any] = Field(..., description="Données de réponse")
meta: Dict[str, Any] = Field(default_factory=dict, description="Métadonnées")
class Config:
json_schema_extra = {
"example": {
"data": {
"message": "Opération réussie"
},
"meta": {}
}
}
class HealthCheckResponse(BaseModel):
"""Response model for health check endpoint"""
status: str = Field(..., description="Statut de santé (healthy/unhealthy)")
translation_service: str = Field(..., description="Service de traduction configuré")
database: Dict[str, Any] = Field(..., description="Statut de la base de données")
redis: Dict[str, Any] = Field(..., description="Statut de Redis")
memory: Dict[str, Any] = Field(..., description="Informations mémoire")
disk: Dict[str, Any] = Field(..., description="Informations disque")
cleanup_service: Dict[str, Any] = Field(..., description="Statut du service de cleanup")
rate_limits: Dict[str, Any] = Field(..., description="Configuration des rate limits")
translation_cache: Dict[str, Any] = Field(..., description="Statut du cache de traduction")
class Config:
json_schema_extra = {
"example": {
"status": "healthy",
"translation_service": "google",
"database": {
"status": "healthy"
},
"redis": {
"status": "not_configured"
},
"memory": {},
"disk": {},
"cleanup_service": {
"running": True
},
"rate_limits": {
"requests_per_minute": 30,
"translations_per_minute": 10
},
"translation_cache": {
"hits": 1500,
"misses": 500,
"size": 42
}
}
}
class ReadyCheckResponse(BaseModel):
"""Response model for readiness check endpoint"""
ready: bool = Field(..., description="Si le service est prêt à recevoir du trafic")
issues: Optional[List[str]] = Field(
None,
description="Liste des problèmes si non prêt"
)
class Config:
json_schema_extra = {
"examples": [
{
"summary": "Service prêt",
"value": {
"ready": True
}
},
{
"summary": "Service non prêt",
"value": {
"ready": False,
"issues": ["database_unavailable", "redis_unavailable"]
}
}
]
}
class RootResponse(BaseModel):
"""Response model for root endpoint"""
name: str = Field(..., description="Nom de l'API")
version: str = Field(..., description="Version de l'API")
status: str = Field(..., description="Statut opérationnel")
docs: str = Field(..., description="URL de la documentation Swagger")
redoc: str = Field(..., description="URL de la documentation ReDoc")
api_base: str = Field(..., description="Base URL des endpoints API")
supported_formats: List[str] = Field(..., description="Formats de fichier supportés")
class Config:
json_schema_extra = {
"example": {
"name": "Office Translator API",
"version": "1.0.0",
"status": "operational",
"docs": "/docs",
"redoc": "/redoc",
"api_base": "/api/v1",
"supported_formats": [".xlsx", ".docx", ".pptx"]
}
}
class RateLimitStatusResponse(BaseModel):
"""Response model for rate limit status"""
client_ip: str = Field(..., description="Adresse IP du client")
limits: Dict[str, int] = Field(..., description="Limites configurées")
current_usage: Dict[str, Any] = Field(..., description="Utilisation actuelle")
class Config:
json_schema_extra = {
"example": {
"client_ip": "192.168.1.1",
"limits": {
"requests_per_minute": 30,
"requests_per_hour": 200,
"translations_per_minute": 10,
"translations_per_hour": 50
},
"current_usage": {
"requests_this_minute": 5,
"requests_this_hour": 42,
"translations_this_minute": 1,
"translations_this_hour": 8
}
}
}
class MetricsResponse(BaseModel):
"""Response model for metrics endpoint"""
system: Dict[str, Any] = Field(..., description="Métriques système")
cleanup: Dict[str, Any] = Field(..., description="Métriques de cleanup")
rate_limits: Dict[str, Any] = Field(..., description="Métriques de rate limiting")
config: Dict[str, Any] = Field(..., description="Configuration actuelle")
class Config:
json_schema_extra = {
"example": {
"system": {
"memory": {},
"disk": {},
"status": "healthy"
},
"cleanup": {
"files_cleaned": 12,
"last_cleanup": "2024-01-15T10:00:00Z"
},
"rate_limits": {
"active_clients": 3
},
"config": {
"max_file_size_mb": 50,
"supported_extensions": [".xlsx", ".docx", ".pptx"],
"translation_service": "google"
}
}
}

279
schemas/errors.py Normal file
View File

@@ -0,0 +1,279 @@
"""
Error response models for API documentation
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from enum import Enum
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
class ErrorCode(str, Enum):
"""All error codes used in the API"""
# Client errors (4xx)
INVALID_FORMAT = "INVALID_FORMAT"
CORRUPTED_FILE = "CORRUPTED_FILE"
FILE_TOO_LARGE = "FILE_TOO_LARGE"
URL_DOWNLOAD_FAILED = "URL_DOWNLOAD_FAILED"
URL_UNREACHABLE = "URL_UNREACHABLE"
QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
UNAUTHORIZED = "UNAUTHORIZED"
FORBIDDEN = "FORBIDDEN"
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
USER_NOT_FOUND = "USER_NOT_FOUND"
EMAIL_EXISTS = "EMAIL_EXISTS"
INVALID_EMAIL = "INVALID_EMAIL"
TOKEN_EXPIRED = "TOKEN_EXPIRED"
TOKEN_MISSING = "TOKEN_MISSING"
TOKEN_INVALID = "TOKEN_INVALID"
MISSING_API_KEY = "MISSING_API_KEY"
INVALID_API_KEY = "INVALID_API_KEY"
API_KEY_REVOKED = "API_KEY_REVOKED"
API_KEY_NOT_FOUND = "API_KEY_NOT_FOUND"
API_KEY_LIMIT_REACHED = "API_KEY_LIMIT_REACHED"
PRO_FEATURE_REQUIRED = "PRO_FEATURE_REQUIRED"
GLOSSARY_NOT_FOUND = "GLOSSARY_NOT_FOUND"
PROMPT_NOT_FOUND = "PROMPT_NOT_FOUND"
INVALID_WEBHOOK_URL = "INVALID_WEBHOOK_URL"
FILE_EXPIRED = "FILE_EXPIRED"
NOT_READY = "NOT_READY"
NOT_FOUND = "NOT_FOUND"
INVALID_REQUEST = "INVALID_REQUEST"
INVALID_JOB_ID = "INVALID_JOB_ID"
ACCESS_DENIED = "ACCESS_DENIED"
# Provider errors (5xx but not 500)
PROVIDER_UNAVAILABLE = "PROVIDER_UNAVAILABLE"
PROVIDER_RATE_LIMITED = "PROVIDER_RATE_LIMITED"
ALL_PROVIDERS_FAILED = "ALL_PROVIDERS_FAILED"
WEBHOOK_FAILED = "WEBHOOK_FAILED"
# System errors (5xx)
INTERNAL_ERROR = "INTERNAL_ERROR"
AUTH_HASHING_UNAVAILABLE = "AUTH_HASHING_UNAVAILABLE"
class ErrorResponse(BaseModel):
"""Standard error response format"""
error: ErrorCode = Field(
...,
description="Code d'erreur standardisé",
example="INVALID_FORMAT"
)
message: str = Field(
...,
description="Message d'erreur lisible en français",
example="Format de fichier non supporté. Formats acceptés: .xlsx, .docx, .pptx"
)
details: Optional[Dict[str, Any]] = Field(
None,
description="Détails supplémentaires sur l'erreur"
)
class Config:
json_schema_extra = {
"example": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {
"accepted_formats": [".xlsx", ".docx", ".pptx"],
"detected_format": ".pdf"
}
}
}
# Pre-defined error examples for OpenAPI documentation
ERROR_EXAMPLES = {
"INVALID_FORMAT": {
"summary": "Format de fichier non supporté",
"value": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {
"accepted_formats": [".xlsx", ".docx", ".pptx"],
"detected_format": ".pdf"
}
}
},
"CORRUPTED_FILE": {
"summary": "Fichier corrompu",
"value": {
"error": "CORRUPTED_FILE",
"message": "Le fichier n'est pas un document Office valide ou est corrompu.",
"details": {
"reason": "Invalid magic bytes"
}
}
},
"FILE_TOO_LARGE": {
"summary": "Fichier trop volumineux",
"value": {
"error": "FILE_TOO_LARGE",
"message": "Le fichier dépasse la limite de 50 MB.",
"details": {
"max_size_mb": 50,
"actual_size_mb": 65.3
}
}
},
"QUOTA_EXCEEDED": {
"summary": "Limite quotidienne atteinte",
"value": {
"error": "QUOTA_EXCEEDED",
"message": "Limite quotidienne de 5 traductions atteinte. Réessayez après minuit UTC.",
"details": {
"current_usage": 5,
"limit": 5,
"tier": "free",
"reset_at": "2024-01-16T00:00:00Z"
}
}
},
"UNAUTHORIZED": {
"summary": "Authentification requise",
"value": {
"error": "UNAUTHORIZED",
"message": "Authentification requise.",
"details": None
}
},
"FORBIDDEN": {
"summary": "Accès interdit",
"value": {
"error": "FORBIDDEN",
"message": "Vous n'avez pas accès à cette ressource.",
"details": None
}
},
"INVALID_CREDENTIALS": {
"summary": "Identifiants invalides",
"value": {
"error": "INVALID_CREDENTIALS",
"message": "Email ou mot de passe incorrect.",
"details": None
}
},
"EMAIL_EXISTS": {
"summary": "Email déjà utilisé",
"value": {
"error": "EMAIL_EXISTS",
"message": "Un compte existe déjà avec cette adresse email.",
"details": None
}
},
"TOKEN_EXPIRED": {
"summary": "Token expiré",
"value": {
"error": "TOKEN_EXPIRED",
"message": "Token invalide ou expiré.",
"details": None
}
},
"PRO_FEATURE_REQUIRED": {
"summary": "Fonctionnalité Pro requise",
"value": {
"error": "PRO_FEATURE_REQUIRED",
"message": "Cette fonctionnalité nécessite un abonnement Pro.",
"details": {
"feature": "llm_translation",
"current_tier": "free",
"required_tier": "pro"
}
}
},
"API_KEY_NOT_FOUND": {
"summary": "Clé API non trouvée",
"value": {
"error": "API_KEY_NOT_FOUND",
"message": "Clé API non trouvée, n'appartient pas à l'utilisateur ou déjà révoquée.",
"details": None
}
},
"FILE_EXPIRED": {
"summary": "Fichier expiré",
"value": {
"error": "FILE_EXPIRED",
"message": "Le fichier traduit n'est plus disponible ou a expiré.",
"details": {
"job_id": "tr_abc123",
"status": "not_found"
}
}
},
"NOT_READY": {
"summary": "Traduction en cours",
"value": {
"error": "NOT_READY",
"message": "La traduction est encore en cours.",
"details": {
"job_id": "tr_abc123",
"status": "processing",
"progress_percent": 45
}
}
},
"NOT_FOUND": {
"summary": "Ressource non trouvée",
"value": {
"error": "NOT_FOUND",
"message": "Ressource non trouvée.",
"details": None
}
},
"INTERNAL_ERROR": {
"summary": "Erreur interne",
"value": {
"error": "INTERNAL_ERROR",
"message": "Une erreur interne est survenue. Veuillez réessayer.",
"details": None
}
},
"INVALID_WEBHOOK_URL": {
"summary": "URL webhook invalide",
"value": {
"error": "INVALID_WEBHOOK_URL",
"message": "L'URL du webhook doit être une URL HTTP/HTTPS valide.",
"details": {
"field": "webhook_url",
"allowed_schemes": ["http", "https"],
"hint": "L'URL doit commencer par http:// ou https://"
}
}
},
"WEBHOOK_LOCALHOST_BLOCKED": {
"summary": "Localhost non autorisé",
"value": {
"error": "INVALID_WEBHOOK_URL",
"message": "Les URLs localhost ne sont pas autorisées.",
"details": {
"field": "webhook_url",
"reason": "localhost_blocked"
}
}
},
"WEBHOOK_PRIVATE_IP_BLOCKED": {
"summary": "IP privée non autorisée",
"value": {
"error": "INVALID_WEBHOOK_URL",
"message": "Les adresses IP privées ne sont pas autorisées.",
"details": {
"field": "webhook_url",
"reason": "private_ip_blocked"
}
}
},
"WEBHOOK_CREDENTIALS_IN_URL": {
"summary": "Credentials dans l'URL",
"value": {
"error": "INVALID_WEBHOOK_URL",
"message": "L'URL ne doit pas contenir d'identifiants (credentials).",
"details": {
"field": "webhook_url",
"reason": "credentials_in_url"
}
}
}
}

100
schemas/glossary_schemas.py Normal file
View File

@@ -0,0 +1,100 @@
"""
Pydantic schemas for glossary endpoints.
Story 3.9: Glossaires - Endpoint CRUD
"""
from datetime import datetime
from uuid import UUID
from typing import Optional
from pydantic import BaseModel, Field, field_validator
class GlossaryTermCreate(BaseModel):
"""Schema for creating a single term."""
source: str = Field(..., min_length=1, max_length=500, description="Terme source")
target: str = Field(
..., min_length=1, max_length=500, description="Traduction cible"
)
@field_validator("source", "target")
@classmethod
def strip_whitespace(cls, v: str) -> str:
return v.strip()
class GlossaryTermResponse(BaseModel):
"""Schema for term in response."""
id: str
source: str
target: str
created_at: Optional[datetime] = None
model_config = {"from_attributes": True}
class GlossaryCreate(BaseModel):
"""Schema for creating a glossary."""
name: str = Field(..., min_length=1, max_length=255, description="Nom du glossaire")
terms: list[GlossaryTermCreate] = Field(
default_factory=list, description="Liste des termes"
)
@field_validator("name")
@classmethod
def strip_name(cls, v: str) -> str:
return v.strip()
class GlossaryUpdate(BaseModel):
"""Schema for updating a glossary (all fields optional)."""
name: Optional[str] = Field(None, min_length=1, max_length=255)
terms: Optional[list[GlossaryTermCreate]] = Field(None)
@field_validator("name")
@classmethod
def strip_name(cls, v: Optional[str]) -> Optional[str]:
return v.strip() if v else None
class GlossaryResponse(BaseModel):
"""Schema for glossary in response (with full terms)."""
id: str
name: str
terms: list[GlossaryTermResponse] = []
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = {"from_attributes": True}
class GlossaryListItem(BaseModel):
"""Schema for glossary in list (without full terms)."""
id: str
name: str
terms_count: int = Field(
default=0, description="Nombre de termes dans le glossaire"
)
created_at: Optional[datetime] = None
model_config = {"from_attributes": True}
class GlossaryListResponse(BaseModel):
"""Schema for glossaries list response."""
data: list[GlossaryListItem] = []
meta: dict = Field(default_factory=dict)
class GlossaryDetailResponse(BaseModel):
"""Schema for single glossary response."""
data: GlossaryResponse
meta: dict = Field(default_factory=dict)

79
schemas/prompt_schemas.py Normal file
View File

@@ -0,0 +1,79 @@
"""
Pydantic schemas for custom prompt endpoints.
Story 3.11: Custom Prompts - Endpoint CRUD
"""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field, field_validator
class PromptCreate(BaseModel):
"""Schema for creating a prompt."""
name: str = Field(..., min_length=1, max_length=255, description="Nom du prompt")
content: str = Field(
..., min_length=1, max_length=10000, description="Contenu du prompt"
)
@field_validator("name", "content")
@classmethod
def strip_whitespace(cls, v: str) -> str:
return v.strip()
class PromptUpdate(BaseModel):
"""Schema for updating a prompt (at least one field required)."""
name: Optional[str] = Field(None, min_length=1, max_length=255)
content: Optional[str] = Field(None, min_length=1, max_length=10000)
@field_validator("name", "content")
@classmethod
def strip_whitespace(cls, v: Optional[str]) -> Optional[str]:
return v.strip() if v else None
def has_updates(self) -> bool:
"""Check if at least one field is provided for update."""
return self.name is not None or self.content is not None
class PromptResponse(BaseModel):
"""Schema for prompt in response."""
id: str
name: str
content: str
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = {"from_attributes": True}
class PromptListItem(BaseModel):
"""Schema for prompt in list (lighter version)."""
id: str
name: str
content_preview: str = Field(
..., description="First 100 chars of content for list view"
)
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = {"from_attributes": True}
class PromptListResponse(BaseModel):
"""Schema for prompts list response."""
data: list[PromptListItem] = []
meta: dict = Field(default_factory=dict)
class PromptDetailResponse(BaseModel):
"""Schema for single prompt response."""
data: PromptResponse
meta: dict = Field(default_factory=dict)

236
schemas/translation.py Normal file
View File

@@ -0,0 +1,236 @@
"""
Pydantic models for translation endpoints
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
"""
from pydantic import BaseModel, Field
from typing import Optional, Literal, List, Dict, Any
from datetime import datetime
class TranslateResponseData(BaseModel):
"""Response data for translation request"""
id: str = Field(
...,
example="tr_abc123def456",
description="Identifiant unique du job de traduction"
)
status: Literal["processing"] = Field(
default="processing",
description="Statut du job (toujours 'processing' à la création)"
)
file_name: Optional[str] = Field(
None,
example="rapport_financier.xlsx",
description="Nom du fichier original"
)
source_lang: Optional[str] = Field(
None,
example="en",
description="Code langue source (ISO 639-1)"
)
target_lang: str = Field(
...,
example="fr",
description="Code langue cible (ISO 639-1)"
)
class Config:
json_schema_extra = {
"example": {
"id": "tr_abc123def456",
"status": "processing",
"file_name": "rapport_financier.xlsx",
"source_lang": "en",
"target_lang": "fr"
}
}
class TranslateResponseMeta(BaseModel):
"""Metadata for translation response"""
rate_limit_remaining: int = Field(
...,
description="Nombre de traductions restantes aujourd'hui",
example=4
)
estimated_time_seconds: Optional[int] = Field(
None,
description="Temps estimé pour la traduction en secondes",
example=15
)
class TranslateResponse(BaseModel):
"""Full response for translation request"""
data: TranslateResponseData
meta: TranslateResponseMeta
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "tr_abc123def456",
"status": "processing",
"file_name": "rapport_financier.xlsx",
"source_lang": "en",
"target_lang": "fr"
},
"meta": {
"rate_limit_remaining": 4,
"estimated_time_seconds": 15
}
}
}
class TranslationStatusData(BaseModel):
"""Response data for translation status endpoint"""
id: str = Field(
...,
description="Identifiant unique du job de traduction",
example="tr_abc123def456"
)
status: Literal["queued", "processing", "completed", "failed"] = Field(
...,
description="Statut actuel du job"
)
progress_percent: int = Field(
default=0,
ge=0,
le=100,
description="Pourcentage de progression (0-100)",
example=65
)
current_step: str = Field(
default="Initializing",
description="Description de l'opération en cours",
example="Traduction de la diapositive 3/5"
)
file_name: Optional[str] = Field(
None,
description="Nom du fichier original",
example="presentation.pptx"
)
source_lang: Optional[str] = Field(
None,
description="Code langue source",
example="en"
)
target_lang: Optional[str] = Field(
None,
description="Code langue cible",
example="fr"
)
created_at: Optional[str] = Field(
None,
description="Date de création (ISO 8601)",
example="2024-01-15T10:30:00Z"
)
completed_at: Optional[str] = Field(
None,
description="Date de complétion (ISO 8601)",
example="2024-01-15T10:35:00Z"
)
failed_at: Optional[str] = Field(
None,
description="Date d'échec (ISO 8601)",
example=None
)
error_message: Optional[str] = Field(
None,
description="Message d'erreur si status='failed'",
example=None
)
class Config:
json_schema_extra = {
"example": {
"id": "tr_abc123def456",
"status": "processing",
"progress_percent": 65,
"current_step": "Traduction de la diapositive 3/5",
"file_name": "presentation.pptx",
"source_lang": "en",
"target_lang": "fr",
"created_at": "2024-01-15T10:30:00Z",
"completed_at": None,
"failed_at": None,
"error_message": None
}
}
class TranslationStatusMeta(BaseModel):
"""Metadata for translation status response"""
estimated_remaining_seconds: Optional[int] = Field(
default=None,
description="Temps restant estimé en secondes",
example=30
)
class TranslationStatusResponse(BaseModel):
"""Full response for translation status endpoint"""
data: TranslationStatusData
meta: Optional[TranslationStatusMeta] = None
class Config:
json_schema_extra = {
"example": {
"data": {
"id": "tr_abc123def456",
"status": "processing",
"progress_percent": 65,
"current_step": "Traduction de la diapositive 3/5",
"file_name": "presentation.pptx",
"source_lang": "en",
"target_lang": "fr",
"created_at": "2024-01-15T10:30:00Z"
},
"meta": {
"estimated_remaining_seconds": 30
}
}
}
class LanguageItem(BaseModel):
"""Single language entry"""
code: str = Field(
...,
description="Code langue ISO 639-1",
example="fr"
)
name: str = Field(
...,
description="Nom de la langue en français",
example="Français"
)
class LanguageResponse(BaseModel):
"""Response model for supported languages"""
supported_languages: Dict[str, str] = Field(
...,
description="Dictionnaire des langues supportées (code -> nom)",
example={
"fr": "French",
"de": "German",
"es": "Spanish",
"it": "Italian"
}
)
note: Optional[str] = Field(
None,
description="Note sur la disponibilité des langues",
example="Les langues supportées peuvent varier selon le service de traduction configuré"
)