mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
212 lines
9.6 KiB
Python
212 lines
9.6 KiB
Python
"""
|
|
Application settings loaded from environment variables only.
|
|
إعدادات التطبيق — تُحمّل من متغيرات البيئة فقط.
|
|
|
|
Uses pydantic-settings v2 BaseSettings. NO hardcoded secrets anywhere.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
from typing import Literal
|
|
|
|
from pydantic import Field, SecretStr, field_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
Environment = Literal["development", "staging", "production", "test"]
|
|
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
Locale = Literal["ar", "en"]
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Single source of truth for configuration | المصدر الوحيد للإعدادات."""
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
case_sensitive=False,
|
|
extra="ignore",
|
|
)
|
|
|
|
# ── App ─────────────────────────────────────────────────────
|
|
app_name: str = "Dealix"
|
|
app_version: str = "3.0.0"
|
|
app_env: Environment = "development"
|
|
app_debug: bool = False
|
|
app_host: str = "0.0.0.0" # noqa: S104 — intentional for containerized deploy
|
|
app_port: int = 8000
|
|
app_timezone: str = "Asia/Riyadh"
|
|
app_default_locale: Locale = "ar"
|
|
app_default_currency: str = "SAR"
|
|
app_log_level: LogLevel = "INFO"
|
|
app_secret_key: SecretStr = Field(default=SecretStr("change-me"))
|
|
cors_origins: str = "http://localhost:3000,http://localhost:8000"
|
|
|
|
# ── LLM: Anthropic ──────────────────────────────────────────
|
|
anthropic_api_key: SecretStr | None = None
|
|
anthropic_model: str = "claude-sonnet-4-5-20250929"
|
|
anthropic_max_tokens: int = 4096
|
|
anthropic_timeout: int = 60
|
|
|
|
# ── LLM: DeepSeek ───────────────────────────────────────────
|
|
deepseek_api_key: SecretStr | None = None
|
|
deepseek_base_url: str = "https://api.deepseek.com/v1"
|
|
deepseek_model: str = "deepseek-chat"
|
|
|
|
# ── LLM: GLM (Z.ai) ─────────────────────────────────────────
|
|
glm_api_key: SecretStr | None = None
|
|
glm_base_url: str = "https://open.bigmodel.cn/api/paas/v4"
|
|
glm_model: str = "glm-4"
|
|
|
|
# ── LLM: Google Gemini ──────────────────────────────────────
|
|
google_api_key: SecretStr | None = None
|
|
gemini_model: str = "gemini-1.5-pro"
|
|
|
|
# ── LLM: Groq ───────────────────────────────────────────────
|
|
groq_api_key: SecretStr | None = None
|
|
groq_api_key_alt: SecretStr | None = None
|
|
groq_model: str = "llama-3.3-70b-versatile"
|
|
groq_base_url: str = "https://api.groq.com/openai/v1"
|
|
|
|
# ── LLM: OpenAI (fallback) ──────────────────────────────────
|
|
openai_api_key: SecretStr | None = None
|
|
openai_base_url: str = "https://api.openai.com/v1"
|
|
openai_model: str = "gpt-4o-mini"
|
|
|
|
# ── Databases ───────────────────────────────────────────────
|
|
database_url: str = "postgresql+asyncpg://ai_user:ai_password@localhost:5432/ai_company"
|
|
redis_url: str = "redis://localhost:6379/0"
|
|
mongodb_uri: str = "mongodb://localhost:27017/ai_company"
|
|
|
|
@field_validator("database_url", mode="before")
|
|
@classmethod
|
|
def _ensure_asyncpg_driver(cls, v: str | None) -> str:
|
|
"""Normalize Railway/Heroku postgres://… URLs to postgresql+asyncpg://…
|
|
|
|
Managed Postgres providers (Railway, Heroku, Render) export the URL
|
|
as ``postgres://`` or ``postgresql://``; SQLAlchemy async needs the
|
|
explicit ``postgresql+asyncpg://`` driver prefix.
|
|
"""
|
|
if not v:
|
|
return "postgresql+asyncpg://ai_user:ai_password@localhost:5432/ai_company"
|
|
if v.startswith("postgres://"):
|
|
v = "postgresql://" + v[len("postgres://") :]
|
|
if v.startswith("postgresql://") and "+asyncpg" not in v:
|
|
v = "postgresql+asyncpg://" + v[len("postgresql://") :]
|
|
return v
|
|
|
|
# ── WhatsApp Business ───────────────────────────────────────
|
|
whatsapp_access_token: SecretStr | None = None
|
|
whatsapp_phone_number_id: str | None = None
|
|
whatsapp_business_account_id: str | None = None
|
|
whatsapp_verify_token: SecretStr | None = None
|
|
whatsapp_app_secret: SecretStr | None = None
|
|
# Live WhatsApp Cloud API send — MUST remain False until webhook + opt-in + legal sign-off.
|
|
# Env: WHATSAPP_ALLOW_LIVE_SEND (default false).
|
|
whatsapp_allow_live_send: bool = False
|
|
|
|
# ── Email ───────────────────────────────────────────────────
|
|
email_provider: Literal["resend", "sendgrid", "smtp"] = "resend"
|
|
email_from: str = "noreply@ai-company.sa"
|
|
email_from_name: str = "AI Company Saudi"
|
|
resend_api_key: SecretStr | None = None
|
|
sendgrid_api_key: SecretStr | None = None
|
|
smtp_host: str | None = None
|
|
smtp_port: int = 587
|
|
smtp_user: str | None = None
|
|
smtp_password: SecretStr | None = None
|
|
smtp_tls: bool = True
|
|
|
|
# ── Calendar ────────────────────────────────────────────────
|
|
google_calendar_credentials_file: str | None = None
|
|
google_calendar_id: str = "primary"
|
|
calendly_api_token: SecretStr | None = None
|
|
calendly_user_uri: str | None = None
|
|
|
|
# ── HubSpot ─────────────────────────────────────────────────
|
|
hubspot_access_token: SecretStr | None = None
|
|
hubspot_portal_id: str | None = None
|
|
|
|
# ── Automation ──────────────────────────────────────────────
|
|
n8n_webhook_url: str | None = None
|
|
n8n_encryption_key: SecretStr | None = None
|
|
|
|
# ── Observability ───────────────────────────────────────────
|
|
langfuse_public_key: SecretStr | None = None
|
|
langfuse_secret_key: SecretStr | None = None
|
|
langfuse_host: str = "https://cloud.langfuse.com"
|
|
sentry_dsn: str | None = None
|
|
|
|
# ── Other ───────────────────────────────────────────────────
|
|
clickbank_api_key: SecretStr | None = None
|
|
hix_ai_api_key: SecretStr | None = None
|
|
|
|
# ── Pricing (SAR) ───────────────────────────────────────────
|
|
pricing_sa_setup_min: int = 12000
|
|
pricing_sa_setup_max: int = 40000
|
|
pricing_sa_retainer_min: int = 3000
|
|
pricing_sa_retainer_max: int = 12000
|
|
pricing_gcc_setup_min: int = 15000
|
|
pricing_gcc_setup_max: int = 50000
|
|
pricing_gcc_retainer_min: int = 4000
|
|
pricing_gcc_retainer_max: int = 15000
|
|
pricing_global_setup_min_usd: int = 3000
|
|
pricing_global_setup_max_usd: int = 10000
|
|
pricing_global_retainer_min_usd: int = 800
|
|
pricing_global_retainer_max_usd: int = 3000
|
|
|
|
# ── Validators ──────────────────────────────────────────────
|
|
@field_validator("cors_origins")
|
|
@classmethod
|
|
def _split_cors(cls, v: str) -> str:
|
|
return v.strip()
|
|
|
|
@property
|
|
def cors_origin_list(self) -> list[str]:
|
|
"""Parsed CORS origins as list."""
|
|
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]
|
|
|
|
@property
|
|
def is_production(self) -> bool:
|
|
return self.app_env == "production"
|
|
|
|
@property
|
|
def is_development(self) -> bool:
|
|
return self.app_env == "development"
|
|
|
|
def require_secret(self, name: str) -> str:
|
|
"""Get a required secret or raise ValueError | يجلب سراً مطلوباً أو يرفع خطأ."""
|
|
value = getattr(self, name, None)
|
|
if value is None:
|
|
raise ValueError(f"Required secret missing: {name}")
|
|
if isinstance(value, SecretStr):
|
|
secret = value.get_secret_value()
|
|
if not secret or secret in ("", "change-me"):
|
|
raise ValueError(f"Required secret empty: {name}")
|
|
return secret
|
|
return str(value)
|
|
|
|
def has_llm_provider(self, provider: str) -> bool:
|
|
"""Check if an LLM provider is configured | هل المزود متوفر؟"""
|
|
mapping = {
|
|
"anthropic": self.anthropic_api_key,
|
|
"deepseek": self.deepseek_api_key,
|
|
"glm": self.glm_api_key,
|
|
"gemini": self.google_api_key,
|
|
"groq": self.groq_api_key,
|
|
"openai": self.openai_api_key,
|
|
}
|
|
key = mapping.get(provider.lower())
|
|
if key is None:
|
|
return False
|
|
return bool(key.get_secret_value())
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def get_settings() -> Settings:
|
|
"""Cached settings singleton | إعدادات مفردة مخزنة."""
|
|
return Settings()
|
|
|
|
|
|
settings = get_settings()
|