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.
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Configuration centralisée — chargée depuis .env
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic import field_validator
|
||||
import json
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# Application
|
||||
APP_NAME: str = "Imago"
|
||||
APP_VERSION: str = "1.0.0"
|
||||
DEBUG: bool = False
|
||||
SECRET_KEY: str = "changez-moi"
|
||||
|
||||
# Serveur
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8000
|
||||
|
||||
# Base de données
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///./data/imago.db"
|
||||
|
||||
# Stockage
|
||||
UPLOAD_DIR: str = "./data/uploads"
|
||||
THUMBNAILS_DIR: str = "./data/thumbnails"
|
||||
MAX_UPLOAD_SIZE_MB: int = 50
|
||||
|
||||
# AI — Configuration
|
||||
AI_ENABLED: bool = True
|
||||
AI_PROVIDER: str = "openrouter"
|
||||
|
||||
# AI — Google Gemini
|
||||
GEMINI_API_KEY: str = ""
|
||||
GEMINI_MODEL: str = "gemini-3.1-pro-preview"
|
||||
GEMINI_MAX_TOKENS: int = 1024
|
||||
|
||||
# AI — OpenRouter
|
||||
OPENROUTER_API_KEY: str = ""
|
||||
OPENROUTER_MODEL: str = "qwen/qwen2.5-vl-72b-instruct"
|
||||
|
||||
# AI — Comportement
|
||||
AI_TAGS_MIN: int = 5
|
||||
AI_TAGS_MAX: int = 10
|
||||
AI_DESCRIPTION_LANGUAGE: str = "français"
|
||||
AI_CACHE_DAYS: int = 30
|
||||
|
||||
# OCR
|
||||
OCR_ENABLED: bool = True
|
||||
TESSERACT_CMD: str = "/usr/bin/tesseract"
|
||||
OCR_LANGUAGES: str = "fra+eng"
|
||||
|
||||
# CORS
|
||||
CORS_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:5173"]
|
||||
|
||||
# Authentification
|
||||
ADMIN_API_KEY: str = ""
|
||||
JWT_SECRET_KEY: str = "changez-moi-jwt-secret"
|
||||
JWT_ALGORITHM: str = "HS256"
|
||||
|
||||
# Rate limiting — global (legacy)
|
||||
RATE_LIMIT_UPLOAD: int = 10
|
||||
RATE_LIMIT_AI: int = 20
|
||||
|
||||
# Rate limiting — par plan (requêtes/heure)
|
||||
RATE_LIMIT_FREE_UPLOAD: int = 20
|
||||
RATE_LIMIT_FREE_AI: int = 50
|
||||
RATE_LIMIT_STANDARD_UPLOAD: int = 100
|
||||
RATE_LIMIT_STANDARD_AI: int = 200
|
||||
RATE_LIMIT_PREMIUM_UPLOAD: int = 500
|
||||
RATE_LIMIT_PREMIUM_AI: int = 1000
|
||||
|
||||
# Redis + ARQ Worker
|
||||
REDIS_URL: str = "redis://localhost:6379"
|
||||
WORKER_MAX_JOBS: int = 10
|
||||
WORKER_JOB_TIMEOUT: int = 180
|
||||
WORKER_MAX_TRIES: int = 3
|
||||
AI_STEP_TIMEOUT: int = 120
|
||||
OCR_STEP_TIMEOUT: int = 30
|
||||
|
||||
# Storage Backend
|
||||
STORAGE_BACKEND: str = "local" # "local" | "s3"
|
||||
S3_BUCKET: str = ""
|
||||
S3_REGION: str = "us-east-1"
|
||||
S3_ENDPOINT_URL: str = "" # vide = AWS, sinon MinIO/R2
|
||||
S3_ACCESS_KEY: str = ""
|
||||
S3_SECRET_KEY: str = ""
|
||||
S3_PREFIX: str = "imago"
|
||||
SIGNED_URL_SECRET: str = "changez-moi-signed-url"
|
||||
|
||||
@field_validator("CORS_ORIGINS", mode="before")
|
||||
@classmethod
|
||||
def parse_cors(cls, v):
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
return json.loads(v)
|
||||
except Exception:
|
||||
return [v]
|
||||
return v
|
||||
|
||||
@property
|
||||
def upload_path(self) -> Path:
|
||||
p = Path(self.UPLOAD_DIR)
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
@property
|
||||
def thumbnails_path(self) -> Path:
|
||||
p = Path(self.THUMBNAILS_DIR)
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
@property
|
||||
def max_upload_bytes(self) -> int:
|
||||
return self.MAX_UPLOAD_SIZE_MB * 1024 * 1024
|
||||
|
||||
model_config = {"env_file": ".env", "case_sensitive": True, "extra": "ignore"}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
Reference in New Issue
Block a user