2026-09-07 14:54:20 +08:00
|
|
|
"""Deployment configuration; secrets are required and never included in API responses."""
|
|
|
|
|
|
2026-09-09 09:58:09 +08:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-09-07 14:54:20 +08:00
|
|
|
from cryptography.fernet import Fernet
|
|
|
|
|
from pydantic import Field, SecretStr, model_validator
|
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Settings(BaseSettings):
|
2026-09-09 09:58:09 +08:00
|
|
|
model_config = SettingsConfigDict(
|
|
|
|
|
env_file=Path(__file__).resolve().parents[2] / ".env", extra="ignore", hide_input_in_errors=True
|
|
|
|
|
)
|
2026-09-07 14:54:20 +08:00
|
|
|
|
|
|
|
|
database_url: str = "postgresql+asyncpg://wq:wq@localhost:5432/wq"
|
|
|
|
|
admin_username: str = "admin"
|
|
|
|
|
admin_password: SecretStr = Field(min_length=12)
|
|
|
|
|
encryption_key: SecretStr
|
|
|
|
|
public_origin: str = "http://localhost:8080"
|
|
|
|
|
cookie_secure: bool = False
|
|
|
|
|
session_hours: int = Field(default=24, ge=1, le=168)
|
2026-09-09 09:58:09 +08:00
|
|
|
wq_email: str = ""
|
|
|
|
|
wq_password: SecretStr = SecretStr("")
|
2026-09-07 14:54:20 +08:00
|
|
|
wq_base_url: str = "https://api.worldquantbrain.com"
|
|
|
|
|
request_timeout: float = 30
|
|
|
|
|
retry_attempts: int = Field(default=4, ge=1, le=8)
|
2026-09-09 19:34:08 +08:00
|
|
|
pnl_poll_attempts: int = Field(default=120, ge=1, le=1000)
|
2026-09-07 14:54:20 +08:00
|
|
|
enable_runner: bool = True
|
2026-09-09 16:18:27 +08:00
|
|
|
mcp_enabled: bool = False
|
2026-09-08 12:43:00 +08:00
|
|
|
ai_request_limit: int = Field(default=12, ge=1, le=30)
|
2026-09-07 23:02:55 +08:00
|
|
|
ai_tool_limit: int = Field(default=12, ge=1, le=100)
|
|
|
|
|
ai_output_tokens: int = Field(default=4096, ge=128, le=32768)
|
|
|
|
|
ai_timeout: float = Field(default=180, ge=1, le=600)
|
2026-09-07 14:54:20 +08:00
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def validate_secrets(self):
|
2026-09-09 09:58:09 +08:00
|
|
|
self.wq_email = self.wq_email.strip()
|
|
|
|
|
if bool(self.wq_email) != bool(self.wq_password.get_secret_value()):
|
|
|
|
|
raise ValueError("WQ_EMAIL and WQ_PASSWORD must be configured together")
|
2026-09-07 14:54:20 +08:00
|
|
|
Fernet(self.encryption_key.get_secret_value().encode())
|
|
|
|
|
if self.cookie_secure and not self.public_origin.startswith("https://"):
|
|
|
|
|
raise ValueError("COOKIE_SECURE requires an HTTPS PUBLIC_ORIGIN")
|
|
|
|
|
return self
|