File size: 7,996 Bytes
bbc3fdf aac350d bbc3fdf aac350d bbc3fdf 9bd3ee0 bbc3fdf 9bd3ee0 7e25f7a 9bd3ee0 bbc3fdf f5eeb1c bbc3fdf 9bd3ee0 aac350d bbc3fdf 9bd3ee0 bbc3fdf aac350d bbc3fdf 23d337e 9bd3ee0 23d337e 9bd3ee0 bbc3fdf aac350d bbc3fdf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | """
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()
|