Files
MarketingTool/apps/api/app/config.py
T

60 lines
3.0 KiB
Python
Raw Normal View History

"""Environment configuration validation for portable deployments."""
from dataclasses import dataclass
import os
from pathlib import Path
2026-09-03 21:49:02 +02:00
from urllib.parse import urlparse
class ConfigError(ValueError):
"""Raised when deployment configuration is unsafe or malformed."""
@dataclass(frozen=True)
class Config:
app_env: str
data_dir: Path
session_secret: str
outreach_enabled: bool
log_level: str
ai_research_provider: str
ai_research_model: str
2026-09-03 21:49:02 +02:00
nous_base_url: str = ""
searxng_base_url: str = ""
def _env(values, key, default=""):
return str(values.get(key, default) or "").strip()
def load_config(values=None):
values = os.environ if values is None else values
app_env = _env(values, "APP_ENV", "development").lower()
if app_env not in {"development", "test", "staging", "production"}:
raise ConfigError("APP_ENV must be development, test, staging, or production")
data_dir = Path(_env(values, "DATA_DIR", ".") or ".").expanduser()
if not data_dir.is_absolute():
data_dir = (Path.cwd() / data_dir).resolve()
if data_dir.exists() and not data_dir.is_dir():
raise ConfigError("DATA_DIR must be a directory")
secret = _env(values, "SESSION_SECRET")
if app_env == "production" and (len(secret) < 32 or secret.lower() in {"change-me", "development", "dev"}):
raise ConfigError("SESSION_SECRET must be at least 32 characters in production")
outreach = _env(values, "AUTOMATED_OUTREACH_ENABLED", "false").lower()
if outreach not in {"", "0", "false", "no", "off"}:
raise ConfigError("automated outreach is disabled in this release")
log_level = _env(values, "LOG_LEVEL", "INFO").upper()
if log_level not in {"QUIET", "ERROR", "WARNING", "INFO", "DEBUG"}:
raise ConfigError("LOG_LEVEL is invalid")
2026-09-03 21:49:02 +02:00
provider = _env(values, "AI_RESEARCH_PROVIDER").lower()
nous_url = _env(values, "NOUS_BASE_URL", "https://inference-api.nousresearch.com/v1")
searx_url = _env(values, "SEARXNG_BASE_URL", "http://searxng:8080")
if provider in {"nous_portal", "nous_portal_web_research"}:
parsed = urlparse(nous_url); allowed = {x.strip().lower() for x in _env(values, "NOUS_ALLOWED_HOSTS", "inference-api.nousresearch.com").split(",") if x.strip()}
if parsed.scheme != "https" or parsed.hostname not in allowed or parsed.username or parsed.password or parsed.query or parsed.fragment:
raise ConfigError("NOUS_BASE_URL is unsafe")
parsed = urlparse(searx_url); allowed = {x.strip().lower() for x in _env(values, "SEARXNG_ALLOWED_HOSTS", "searxng").split(",") if x.strip()}
if parsed.scheme != "http" or parsed.hostname not in allowed or parsed.username or parsed.password or parsed.query or parsed.fragment:
raise ConfigError("SEARXNG_BASE_URL is unsafe")
return Config(app_env, data_dir, secret, False, log_level, provider,
_env(values, "AI_RESEARCH_PROVIDER_MODEL") or _env(values, "NOUS_MODEL", "Hermes-4-405B"), nous_url, searx_url)