from __future__ import annotations from dataclasses import dataclass from pathlib import Path import logging import re from typing import Self from dotenv import load_dotenv import os logger = logging.getLogger(__name__) @dataclass(slots=True) class Settings: api_key: str | None base_url: str | None model: str @classmethod def from_env(cls, env_file: Path | None = None, model_override: str | None = None) -> Self: if env_file: logger.info("Loading environment variables from %s", env_file) load_dotenv(env_file) else: logger.debug("Loading environment variables using default dotenv lookup") load_dotenv() settings = cls( api_key=os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY"), base_url=os.getenv("GEMINI_BASE_URL") or None, model=model_override or os.getenv("GEMINI_MODEL") or "gemini-2.5-flash", ) logger.info( "Configuration loaded: model=%s base_url=%s api_key=%s", settings.model, settings.normalized_base_url() or "", "set" if settings.api_key else "missing", ) return settings def require_api_key(self) -> None: if not self.api_key: raise ValueError("Missing GEMINI_API_KEY (or GOOGLE_API_KEY) in the environment.") logger.debug("Gemini API key is available") def normalized_base_url(self) -> str | None: if not self.base_url: return None base_url = self.base_url.strip().rstrip("/") return re.sub(r"/v\d+(?:alpha|beta)?$", "", base_url, flags=re.IGNORECASE)