refactor(config): migrar a SettingsConfigDict y quitar Field(env=...)
Elimina las dos deprecaciones de Pydantic V2 (se rompían en V3): - class Config -> model_config = SettingsConfigDict(env_file='.env') - Field(env='X') en los 33 campos -> resolución automática por nombre de campo (case-insensitive en pydantic-settings); verificado que los 33 nombres coinciden con su variable, así que el comportamiento es idéntico. pytest pasa de 34 warnings a 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
19efc70539
commit
a91e119171
+33
-36
@@ -1,52 +1,51 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# Telegram
|
||||
telegram_bot_token: str = Field(..., env="TELEGRAM_BOT_TOKEN")
|
||||
telegram_allowed_users: str = Field("", env="TELEGRAM_ALLOWED_USERS") # comma-separated user IDs
|
||||
telegram_bot_token: str = Field(...)
|
||||
telegram_allowed_users: str = Field("") # comma-separated user IDs
|
||||
|
||||
# Ollama
|
||||
ollama_url: str = Field("http://ollama.chemavx.xyz", env="OLLAMA_URL")
|
||||
ollama_model: str = Field("qwen2.5:3b", env="OLLAMA_MODEL")
|
||||
ollama_embed_model: str = Field("qwen2.5:3b", env="OLLAMA_EMBED_MODEL")
|
||||
ollama_url: str = Field("http://ollama.chemavx.xyz")
|
||||
ollama_model: str = Field("qwen2.5:3b")
|
||||
ollama_embed_model: str = Field("qwen2.5:3b")
|
||||
|
||||
# Claude fallback (optional)
|
||||
anthropic_api_key: Optional[str] = Field(None, env="ANTHROPIC_API_KEY")
|
||||
claude_model: str = Field("claude-haiku-4-5", env="CLAUDE_MODEL")
|
||||
anthropic_api_key: Optional[str] = Field(None)
|
||||
claude_model: str = Field("claude-haiku-4-5")
|
||||
|
||||
# Database
|
||||
db_path: str = Field("/data/researchowl.db", env="DB_PATH")
|
||||
db_path: str = Field("/data/researchowl.db")
|
||||
|
||||
# Scraping
|
||||
max_depth: int = Field(3, env="MAX_DEPTH") # recursion depth
|
||||
max_sources: int = Field(150, env="MAX_SOURCES") # hard cap
|
||||
max_pages_per_search: int = Field(5, env="MAX_PAGES_PER_SEARCH")
|
||||
max_depth: int = Field(3) # recursion depth
|
||||
max_sources: int = Field(150) # hard cap
|
||||
max_pages_per_search: int = Field(5)
|
||||
searxng_url: str = Field(
|
||||
"http://searxng-svc.researchowl.svc.cluster.local:8080/search",
|
||||
env="SEARXNG_URL"
|
||||
)
|
||||
request_timeout: int = Field(30, env="REQUEST_TIMEOUT")
|
||||
request_delay: float = Field(1.0, env="REQUEST_DELAY") # seconds between requests
|
||||
min_content_length: int = Field(200, env="MIN_CONTENT_LENGTH") # chars
|
||||
request_timeout: int = Field(30)
|
||||
request_delay: float = Field(1.0) # seconds between requests
|
||||
min_content_length: int = Field(200) # chars
|
||||
|
||||
# Fuentes opcionales — desactivadas por defecto: la IP del homelab está
|
||||
# bloqueada por Reddit (403) y YouTube (transcripts vacíos), eran peso muerto.
|
||||
enable_youtube: bool = Field(False, env="ENABLE_YOUTUBE")
|
||||
enable_reddit: bool = Field(False, env="ENABLE_REDDIT")
|
||||
enable_youtube: bool = Field(False)
|
||||
enable_reddit: bool = Field(False)
|
||||
|
||||
# Processing
|
||||
chunk_size: int = Field(800, env="CHUNK_SIZE") # tokens per chunk
|
||||
chunk_overlap: int = Field(100, env="CHUNK_OVERLAP")
|
||||
quality_threshold: float = Field(0.3, env="QUALITY_THRESHOLD") # 0-1, chunks below discarded
|
||||
chunk_size: int = Field(800) # tokens per chunk
|
||||
chunk_overlap: int = Field(100)
|
||||
quality_threshold: float = Field(0.3) # 0-1, chunks below discarded
|
||||
|
||||
# Ghost CMS
|
||||
ghost_url: Optional[str] = Field(None, env="GHOST_URL")
|
||||
ghost_api_key: Optional[str] = Field(None, env="GHOST_API_KEY")
|
||||
ghost_url_en: str = Field("", env="GHOST_URL_EN")
|
||||
ghost_api_key_en: str = Field("", env="GHOST_API_KEY_EN")
|
||||
ghost_url: Optional[str] = Field(None)
|
||||
ghost_api_key: Optional[str] = Field(None)
|
||||
ghost_url_en: str = Field("")
|
||||
ghost_api_key_en: str = Field("")
|
||||
|
||||
# SEO autofill — "off" | "on" | "dryrun" (default off).
|
||||
# off = today's exact behavior (bare draft, no second LLM call).
|
||||
@@ -55,28 +54,27 @@ class Settings(BaseSettings):
|
||||
# dryrun = runs the full pipeline + reports the proposed SEO to Telegram, but
|
||||
# writes a BARE draft (no seo fields), for inspection before trusting
|
||||
# the write path. A `/generate blog en dry` arg forces dryrun per-call.
|
||||
seo_autofill: str = Field("off", env="SEO_AUTOFILL")
|
||||
seo_autofill: str = Field("off")
|
||||
|
||||
# News monitor (RSS de noticias UAP/OVNI) — inerte hasta NEWS_ENABLED=true.
|
||||
NEWS_ENABLED: bool = Field(False, env="NEWS_ENABLED")
|
||||
NEWS_POLL_INTERVAL_HOURS: int = Field(6, env="NEWS_POLL_INTERVAL_HOURS")
|
||||
NEWS_ENABLED: bool = Field(False)
|
||||
NEWS_POLL_INTERVAL_HOURS: int = Field(6)
|
||||
# Chat al que el monitor empuja novedades; si None -> 1er TELEGRAM_ALLOWED_USERS
|
||||
# (ver propiedad news_chat_id).
|
||||
NEWS_CHAT_ID: Optional[int] = Field(None, env="NEWS_CHAT_ID")
|
||||
NEWS_CHAT_ID: Optional[int] = Field(None)
|
||||
NEWS_KEYWORDS: str = Field(
|
||||
"ovni,ovnis,uap,ufo,desclasificado,desclasificacion,declassified,"
|
||||
"avistamiento,sighting,extraterrestre,non-human,nhi,grusch,"
|
||||
"aaro,pursue,fenomeno aereo,whistleblower",
|
||||
env="NEWS_KEYWORDS",
|
||||
)
|
||||
NEWS_MAX_ITEMS: int = Field(15, env="NEWS_MAX_ITEMS")
|
||||
NEWS_MAX_ITEMS: int = Field(15)
|
||||
|
||||
# Alerts
|
||||
cost_alert_threshold: float = Field(0.15, env="COST_ALERT_THRESHOLD")
|
||||
cost_alert_threshold: float = Field(0.15)
|
||||
|
||||
# App
|
||||
log_level: str = Field("INFO", env="LOG_LEVEL")
|
||||
timezone: str = Field("Europe/Madrid", env="TIMEZONE")
|
||||
log_level: str = Field("INFO")
|
||||
timezone: str = Field("Europe/Madrid")
|
||||
|
||||
@property
|
||||
def allowed_user_ids(self) -> list[int]:
|
||||
@@ -102,8 +100,7 @@ class Settings(BaseSettings):
|
||||
def seo_autofill_dryrun(self) -> bool:
|
||||
return (self.seo_autofill or "").strip().lower() == "dryrun"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
Reference in New Issue
Block a user