refactor: centralize settings and application services
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
@@ -46,6 +47,26 @@ MAX_AGENT_STEPS = 5
|
||||
CONVERSATION_HISTORY_LIMIT = 12
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AppSettings:
|
||||
bot_token: str
|
||||
assistant_password: str
|
||||
db_path: Path
|
||||
mode: str
|
||||
timezone: ZoneInfo
|
||||
voice_max_duration_seconds: int
|
||||
ollama_base_url: str
|
||||
ollama_model: str
|
||||
yandex_cloud_folder: str | None
|
||||
yandex_cloud_model: str
|
||||
yandex_stt_model: str
|
||||
yandex_stt_language: str
|
||||
whisper_model: str
|
||||
whisper_device: str
|
||||
whisper_compute_type: str
|
||||
whisper_language: str | None
|
||||
|
||||
|
||||
def load_env_file(path: Path = ENV_FILE) -> None:
|
||||
if not path.exists():
|
||||
return
|
||||
@@ -68,6 +89,128 @@ def load_env_file(path: Path = ENV_FILE) -> None:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
def load_app_settings() -> AppSettings:
|
||||
"""Load and validate the complete startup configuration once."""
|
||||
load_env_file()
|
||||
|
||||
token = os.getenv(TOKEN_ENV_NAME)
|
||||
if not token:
|
||||
raise RuntimeError(
|
||||
f"Set {TOKEN_ENV_NAME} in environment or .env file."
|
||||
)
|
||||
|
||||
password = os.getenv(PASSWORD_ENV_NAME, "").strip()
|
||||
if not password:
|
||||
raise RuntimeError(
|
||||
f"Set {PASSWORD_ENV_NAME} in environment or .env file."
|
||||
)
|
||||
|
||||
mode = os.getenv(MODE_ENV_NAME, DEFAULT_MODE).strip().lower()
|
||||
if mode not in {"local", "yandex"}:
|
||||
raise RuntimeError(
|
||||
f"{MODE_ENV_NAME} must be either 'local' or 'yandex', got {mode!r}."
|
||||
)
|
||||
|
||||
raw_db_path = os.getenv(DB_ENV_NAME)
|
||||
if raw_db_path:
|
||||
db_path = Path(raw_db_path).expanduser()
|
||||
if not db_path.is_absolute():
|
||||
db_path = PROJECT_ROOT / db_path
|
||||
else:
|
||||
db_path = DEFAULT_DB_FILE
|
||||
|
||||
timezone_name = os.getenv(TIMEZONE_ENV_NAME, DEFAULT_TIMEZONE)
|
||||
try:
|
||||
local_timezone = ZoneInfo(timezone_name)
|
||||
except ZoneInfoNotFoundError:
|
||||
logger.warning(
|
||||
"Unknown timezone %s, falling back to UTC",
|
||||
timezone_name,
|
||||
)
|
||||
local_timezone = ZoneInfo("UTC")
|
||||
|
||||
raw_voice_duration = os.getenv(
|
||||
VOICE_MAX_DURATION_ENV_NAME,
|
||||
str(DEFAULT_VOICE_MAX_DURATION_SECONDS),
|
||||
)
|
||||
try:
|
||||
voice_duration = int(raw_voice_duration)
|
||||
except ValueError:
|
||||
voice_duration = 0
|
||||
if voice_duration <= 0:
|
||||
logger.warning(
|
||||
"%s must be a positive integer, using %s",
|
||||
VOICE_MAX_DURATION_ENV_NAME,
|
||||
DEFAULT_VOICE_MAX_DURATION_SECONDS,
|
||||
)
|
||||
voice_duration = DEFAULT_VOICE_MAX_DURATION_SECONDS
|
||||
|
||||
yandex_folder = os.getenv(
|
||||
YANDEX_CLOUD_FOLDER_ENV_NAME,
|
||||
"",
|
||||
).strip().strip("/")
|
||||
if mode == "yandex" and not yandex_folder:
|
||||
raise RuntimeError(
|
||||
f"Set {YANDEX_CLOUD_FOLDER_ENV_NAME} in environment or .env file."
|
||||
)
|
||||
|
||||
yandex_model = os.getenv(
|
||||
YANDEX_CLOUD_MODEL_ENV_NAME,
|
||||
DEFAULT_YANDEX_CLOUD_MODEL,
|
||||
).strip().strip("/")
|
||||
if not yandex_model:
|
||||
yandex_model = DEFAULT_YANDEX_CLOUD_MODEL
|
||||
|
||||
whisper_language = os.getenv(
|
||||
WHISPER_LANGUAGE_ENV_NAME,
|
||||
DEFAULT_WHISPER_LANGUAGE,
|
||||
).strip()
|
||||
|
||||
return AppSettings(
|
||||
bot_token=token,
|
||||
assistant_password=password,
|
||||
db_path=db_path,
|
||||
mode=mode,
|
||||
timezone=local_timezone,
|
||||
voice_max_duration_seconds=voice_duration,
|
||||
ollama_base_url=os.getenv(
|
||||
OLLAMA_BASE_URL_ENV_NAME,
|
||||
DEFAULT_OLLAMA_BASE_URL,
|
||||
).rstrip("/"),
|
||||
ollama_model=os.getenv(
|
||||
OLLAMA_MODEL_ENV_NAME,
|
||||
DEFAULT_OLLAMA_MODEL,
|
||||
),
|
||||
yandex_cloud_folder=yandex_folder or None,
|
||||
yandex_cloud_model=yandex_model,
|
||||
yandex_stt_model=os.getenv(
|
||||
YANDEX_STT_MODEL_ENV_NAME,
|
||||
DEFAULT_YANDEX_STT_MODEL,
|
||||
).strip(),
|
||||
yandex_stt_language=os.getenv(
|
||||
YANDEX_STT_LANGUAGE_ENV_NAME,
|
||||
DEFAULT_YANDEX_STT_LANGUAGE,
|
||||
).strip(),
|
||||
whisper_model=os.getenv(
|
||||
WHISPER_MODEL_ENV_NAME,
|
||||
DEFAULT_WHISPER_MODEL,
|
||||
).strip(),
|
||||
whisper_device=os.getenv(
|
||||
WHISPER_DEVICE_ENV_NAME,
|
||||
DEFAULT_WHISPER_DEVICE,
|
||||
).strip(),
|
||||
whisper_compute_type=os.getenv(
|
||||
WHISPER_COMPUTE_TYPE_ENV_NAME,
|
||||
DEFAULT_WHISPER_COMPUTE_TYPE,
|
||||
).strip(),
|
||||
whisper_language=(
|
||||
None
|
||||
if whisper_language.lower() == "auto"
|
||||
else whisper_language or None
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_bot_token() -> str:
|
||||
load_env_file()
|
||||
token = os.getenv(TOKEN_ENV_NAME)
|
||||
|
||||
Reference in New Issue
Block a user