"""Configuration management — env vars, config file, and defaults.""" from __future__ import annotations from pathlib import Path from typing import Optional import toml from platformdirs import user_config_dir from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict CONFIG_DIR = Path(user_config_dir("freepik-cli")) CONFIG_FILE = CONFIG_DIR / "config.toml" class FreepikConfig(BaseSettings): """ Configuration with priority (highest to lowest): 1. CLI --api-key flag (handled in commands directly) 2. FREEPIK_* environment variables 3. ~/.config/freepik-cli/config.toml 4. Defaults below """ model_config = SettingsConfigDict( env_prefix="FREEPIK_", env_file=".env", env_file_encoding="utf-8", extra="ignore", ) api_key: Optional[str] = None base_url: str = "https://api.freepik.com" default_output_dir: str = "." default_image_model: str = "flux-2-pro" default_video_model: str = "kling-o1-pro" default_upscale_mode: str = "precision-v2" poll_timeout: int = 600 poll_max_interval: int = 15 show_banner: bool = True @field_validator("api_key", mode="before") @classmethod def strip_api_key(cls, v: Optional[str]) -> Optional[str]: return v.strip() if isinstance(v, str) else v @classmethod def load(cls) -> "FreepikConfig": """Load from config file, then overlay environment variables.""" file_data: dict = {} if CONFIG_FILE.exists(): try: file_data = toml.load(CONFIG_FILE) except Exception: pass return cls(**file_data) def save(self, exclude_keys: set[str] | None = None) -> None: """Persist non-sensitive config to disk.""" exclude_keys = (exclude_keys or set()) | {"api_key"} CONFIG_DIR.mkdir(parents=True, exist_ok=True) data = self.model_dump(exclude=exclude_keys, exclude_none=True) # Stringify paths for k, v in data.items(): if isinstance(v, Path): data[k] = str(v) with open(CONFIG_FILE, "w") as f: toml.dump(data, f) def to_display_dict(self) -> dict: """Return all settings as a displayable dict (keeps api_key for masking).""" d = self.model_dump() return {k: v for k, v in d.items()} def set_value(self, key: str, value: str) -> None: """Update a single config key and save.""" allowed = { "base_url", "default_output_dir", "default_image_model", "default_video_model", "default_upscale_mode", "poll_timeout", "poll_max_interval", "show_banner", } if key not in allowed: raise ValueError( f"Key '{key}' is not configurable via this command. " f"Use the FREEPIK_API_KEY environment variable to set the API key." ) current = self.model_dump() if key in ("poll_timeout", "poll_max_interval"): current[key] = int(value) elif key == "show_banner": current[key] = value.lower() in ("true", "1", "yes") else: current[key] = value updated = FreepikConfig(**{k: v for k, v in current.items() if v is not None}) updated.save()