File size: 2,823 Bytes
71c1ad2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# app/config.py
# Centralized configuration via Pydantic Settings

from pathlib import Path
from functools import lru_cache
from pydantic_settings import BaseSettings
from pydantic import Field


class Settings(BaseSettings):
    """Application settings loaded from environment variables."""

    # --- Server ---
    host: str = "0.0.0.0"
    port: int = 7860
    env: str = "production"
    log_level: str = "INFO"

    # --- JWT ---
    jwt_access_secret: str = Field(default="change-me-in-production-at-least-32-chars!!")
    jwt_refresh_secret: str = Field(default="change-me-in-production-at-least-32-chars!!")
    jwt_access_expires_minutes: int = 15
    jwt_refresh_expires_days: int = 7

    # --- Security ---
    bcrypt_rounds: int = 12
    cors_origins: str = "*"

    # --- MongoDB ---
    mongodb_uri: str = Field(default="")
    mongodb_db_name: str = "hubble"

    # --- Redis ---
    redis_url: str = Field(default="")
    redis_cache_ttl: int = 300  # seconds

    # --- Gemini ---
    gemini_api_keys: str = ""  # comma-separated
    gemini_model: str = "gemini-2.5-flash"

    # --- LangSmith ---
    langsmith_api_key: str = ""
    langsmith_project: str = "hubble-moderation"
    langsmith_tracing_v2: bool = True

    # --- Models ---
    model_cache_dir: str = "/tmp/model_cache"
    onnx_enabled: bool = False
    text_model_name: str = "unitary/toxic-bert"
    image_model_name: str = "google/efficientnet-b0"
    clip_model_name: str = "openai/clip-vit-base-patch32"

    # --- Risk Thresholds ---
    risk_low_max: int = 30
    risk_medium_max: int = 65

    # --- Content Limits ---
    max_text_length: int = 10000
    max_image_size: int = 10 * 1024 * 1024   # 10MB
    max_video_size: int = 50 * 1024 * 1024   # 50MB

    # --- Video Processing ---
    video_max_frames: int = 10
    video_fps_sample: int = 1

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
        "extra": "ignore",
    }

    @property
    def gemini_keys_list(self) -> list[str]:
        """Parse comma-separated Gemini API keys."""
        if not self.gemini_api_keys:
            return []
        return [k.strip() for k in self.gemini_api_keys.split(",") if k.strip()]

    @property
    def cors_origins_list(self) -> list[str]:
        """Parse comma-separated CORS origins."""
        return [o.strip() for o in self.cors_origins.split(",") if o.strip()]

    @property
    def model_cache_path(self) -> Path:
        """Resolved path for model cache directory."""
        path = Path(self.model_cache_dir)
        path.mkdir(parents=True, exist_ok=True)
        return path

    @property
    def is_production(self) -> bool:
        return self.env == "production"


@lru_cache()
def get_settings() -> Settings:
    """Cached settings singleton."""
    return Settings()