Исходный код config

import os
from dataclasses import dataclass, field
from pathlib import Path

# from dotenv import load_dotenv # Removed from here, should be called in main entry point
import re
import logging

# Load environment variables from .env file if it exists
# load_dotenv() # Removed call

BASE_DIR = Path(__file__).resolve().parent


def _int_env(var_name: str, default: int) -> int:
    """Converts an environment variable to int, ignoring extraneous characters.
    Returns default if the number is not found.
    """
    raw = os.getenv(var_name)
    if raw is None:
        return default
    m = re.search(r"-?\d+", raw)
    try:
        return int(m.group()) if m else default
    except Exception:
        return default


[документация] @dataclass(frozen=True) class TelegramConfig: """Configuration for the Telegram Bot token.""" token: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
[документация] @dataclass(frozen=True) class OpenAIConfig: """Configuration for the OpenAI API client.""" api_key: str = os.getenv("OPENAI_API_KEY", "") model: str = os.getenv("OPENAI_MODEL", "gpt-4o-mini") # Number of retry attempts in case of LLM error max_retries: int = _int_env("OPENAI_MAX_RETRIES", 2)
[документация] @dataclass class GoogleConfig: """Configuration for Google API access, specifically for service account credentials. If a relative path is specified in the GOOGLE_CREDENTIALS_PATH environment variable (or if it is not set), it will be converted to an absolute path within the project directory. This avoids issues when the bot is run from any directory or on a different OS (Windows vs Linux). """ _raw_path: str = os.getenv("GOOGLE_CREDENTIALS_PATH", "google_credentials.json") @property def credentials_path(self) -> str: # type: ignore[override] """Absolute path to the service account file. - If an absolute path is specified (e.g., `/opt/...`), it's returned as is. - Otherwise, the path is considered relative to `BASE_DIR`. Checks for file existence and provides guidance if not found. """ path = Path(self._raw_path) if not path.is_absolute(): path = BASE_DIR / path if not path.exists(): # Try fallback path - file in the project root if not found via env var fallback = BASE_DIR / "google_credentials.json" if fallback.exists(): logger = logging.getLogger(__name__) logger.warning( "Google credentials file not found at '%s'. " "Using '%s' instead.", path, fallback, ) path = fallback else: raise FileNotFoundError( f"Google credentials file not found at '{path}' " f"nor at fallback '{fallback}'. " "Specify the correct path in GOOGLE_CREDENTIALS_PATH environment variable " "or place 'google_credentials.json' in the project root." ) return str(path)
[документация] @dataclass(frozen=True) class SchedulerConfig: """Configuration for the task scheduler (APScheduler).""" timezone: str = os.getenv("SCHEDULER_TIMEZONE", "Europe/Moscow") morning_time: str = os.getenv("MORNING_REMINDER_TIME", "08:00") evening_time: str = os.getenv("EVENING_REMINDER_TIME", "20:00") motivation_interval_hours: int = _int_env("MOTIVATION_INTERVAL_HOURS", 8)
[документация] @dataclass(frozen=True) class LoggingConfig: """Configuration for application logging.""" level: str = os.getenv("LOG_LEVEL", "WARNING") format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
[документация] @dataclass(frozen=True) class RateLimiterConfig: """Configuration for API rate limiting, specifically for LLM calls.""" # For LLM calls, e.g., OpenAI llm_requests_per_minute: int = _int_env( "LLM_REQUESTS_PER_MINUTE", 20 ) # Default: 20 RPM per user llm_max_burst: int = _int_env( "LLM_MAX_BURST", 5 ) # Default: allow burst of 5 requests
[документация] @dataclass(frozen=True) class PrometheusConfig: """Configuration for prometheus_client exporter.""" port: int = field( default_factory=lambda: int(os.getenv("PROMETHEUS_PORT", "8000")) ) # Port for metrics exporter
telegram = TelegramConfig() openai_cfg = OpenAIConfig() google = GoogleConfig() scheduler_cfg = SchedulerConfig() logging_cfg = LoggingConfig() ratelimiter_cfg = RateLimiterConfig() prometheus_cfg = PrometheusConfig()