face-intel / config /settings.py
Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
8 kB
"""
Centralized configuration for Face Intel.
All settings are environment-driven via pydantic-settings so the same
code runs in dev, test, and production without code changes.
After the refactor, `settings` is still importable for backward
compatibility with provider modules that read tuning knobs, BUT every
stateful service (storage, cache, orchestrator, services) receives its
dependencies through constructor injection — never by importing this
module directly.
"""
from __future__ import annotations
from pathlib import Path
from typing import List
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "data"
MODELS_DIR = DATA_DIR / "models"
GALLERY_DIR = DATA_DIR / "gallery"
UPLOADS_DIR = DATA_DIR / "uploads"
JOBS_DIR = DATA_DIR / "jobs"
UI_DIR = BASE_DIR / "ui" / "static"
# Ensure runtime dirs exist (idempotent)
for _d in (DATA_DIR, MODELS_DIR, GALLERY_DIR, UPLOADS_DIR, JOBS_DIR):
_d.mkdir(parents=True, exist_ok=True)
class Settings(BaseSettings):
"""Environment-driven settings. All fields map to FI_* env vars."""
model_config = SettingsConfigDict(
env_file=".env",
env_prefix="FI_",
case_sensitive=False,
extra="ignore",
)
# ------------------------------------------------------------------ #
# Core
# ------------------------------------------------------------------ #
app_name: str = "Face Intel"
app_version: str = "1.0.0"
environment: str = "development"
host: str = "0.0.0.0"
port: int = 8000
debug: bool = False
# ------------------------------------------------------------------ #
# Provider enable flags
# ------------------------------------------------------------------ #
# Detection
enable_haar: bool = True
enable_dnn: bool = False # requires model download
# Recognition
enable_insightface: bool = False # requires onnxruntime + model
# Reverse search
enable_serpapi: bool = False # requires API key
enable_social_lookup: bool = True # pure stdlib, always available
# Image analysis
enable_image_quality: bool = True
enable_image_properties: bool = True
# Metadata
enable_exif: bool = True
# Forensics
enable_image_integrity: bool = True
enable_duplicate_detector: bool = True
enable_ela: bool = True
enable_image_similarity: bool = True
# OCR (optional — requires onnxruntime)
enable_ocr: bool = False
# Object detection (optional — requires onnxruntime)
enable_yolov8: bool = False
# QR code + barcode detection (pure OpenCV — always available)
enable_qr_code: bool = True
enable_barcode: bool = True
# Scene classification (optional — requires onnxruntime)
enable_places365: bool = False
# NSFW detection (optional — requires onnxruntime)
enable_nudenet: bool = False
# AI image detection (optional — requires onnxruntime)
enable_ai_image_detector: bool = False
# Embeddings (optional — requires onnxruntime)
enable_mobilenet_embed: bool = False
# ------------------------------------------------------------------ #
# Reverse face search (PimEyes-style indexed search)
# ------------------------------------------------------------------ #
enable_face_index: bool = True
face_index_path: str = str(DATA_DIR / "face_index.db")
face_index_top_k: int = 10
face_index_threshold: float = 0.5
face_index_max_faces: int = 100_000
face_index_crawler_user_agent: str = "FaceIntel/1.0"
face_index_crawler_seed_wikipedia: bool = False
face_index_crawler_seed_imdb: bool = False
# ------------------------------------------------------------------ #
# Detection tuning
# ------------------------------------------------------------------ #
dnn_confidence_threshold: float = 0.7
haar_scale_factor: float = 1.1
haar_min_neighbors: int = 5
# ------------------------------------------------------------------ #
# Recognition tuning
# ------------------------------------------------------------------ #
insightface_model_pack: str = "buffalo_s" # small variant for low-RAM
recognition_match_threshold: float = 0.5
# ------------------------------------------------------------------ #
# Scraping (used by reverse-search providers)
# ------------------------------------------------------------------ #
scrape_timeout: int = 30
scrape_max_images: int = 50
user_agent: str = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
)
# ------------------------------------------------------------------ #
# Reverse image search
# ------------------------------------------------------------------ #
reverse_search_max_results: int = 20
serpapi_key: str = ""
# ------------------------------------------------------------------ #
# Orchestrator
# ------------------------------------------------------------------ #
orchestrator_timeout_seconds: float = 90.0
orchestrator_max_concurrency: int = 8
retry_max_attempts: int = 3
retry_initial_backoff_seconds: float = 0.5
retry_max_backoff_seconds: float = 8.0
# ------------------------------------------------------------------ #
# Cache
# ------------------------------------------------------------------ #
cache_enabled: bool = True
cache_ttl_seconds: int = 3600
cache_max_entries: int = 1000
# ------------------------------------------------------------------ #
# Health
# ------------------------------------------------------------------ #
health_check_interval_seconds: int = 60
circuit_breaker_failure_threshold: int = 5
circuit_breaker_recovery_seconds: int = 120
# ------------------------------------------------------------------ #
# Storage
# ------------------------------------------------------------------ #
db_path: str = str(DATA_DIR / "face_intel.db")
audit_log_path: str = str(DATA_DIR / "audit.log")
job_retention_days: int = 7
# ------------------------------------------------------------------ #
# API
# ------------------------------------------------------------------ #
rate_limit_per_minute: int = 30
cors_origins: List[str] = Field(default_factory=lambda: ["*"])
require_consent_header: bool = True
consent_header_name: str = "X-Consent-Statement"
max_image_bytes: int = 20 * 1024 * 1024 # 20 MB upload limit
job_timeout_seconds: float = 300.0 # 5 min overall job cap
max_request_body_bytes: int = 25 * 1024 * 1024 # 25 MB HTTP body cap
# ------------------------------------------------------------------ #
# Model management
# ------------------------------------------------------------------ #
models_dir: str = str(MODELS_DIR)
models_auto_download: bool = True # auto-download on first use
onnx_intra_op_threads: int = 2 # limit CPU threads for low-RAM
# ------------------------------------------------------------------ #
# Logging
# ------------------------------------------------------------------ #
log_level: str = "INFO"
log_json: bool = False
def make_settings(**overrides) -> Settings:
"""Factory used by the DI container to build a Settings instance.
Tests can override individual fields without touching env vars:
make_settings(environment="test", cache_enabled=False)
"""
if overrides:
# Build a fresh instance with overrides merged
base = Settings()
data = base.model_dump()
data.update(overrides)
return Settings(**data)
return Settings()
# Default singleton — fine for read-only config consumption by providers.
# Stateful services MUST be injected, not import this directly.
settings = Settings()