Files
FastAnime/viu_media/cli/config/loader.py
2025-08-18 14:06:31 +03:00

115 lines
3.8 KiB
Python

import logging
import tomllib
from pathlib import Path
from typing import Dict
import click
from pydantic import ValidationError
from ...core.config import AppConfig
from ...core.constants import USER_CONFIG
from ...core.exceptions import ConfigError
logger = logging.getLogger(__name__)
class ConfigLoader:
"""
Handles loading the application configuration from a .toml file.
It ensures a default configuration exists, reads the .toml file,
and uses Pydantic to parse and validate the data into a type-safe
AppConfig object.
"""
def __init__(self, config_path: Path = USER_CONFIG):
"""
Initializes the loader with the path to the configuration file.
Args:
config_path: The path to the user's config.toml file.
"""
self.config_path = config_path
def _handle_first_run(self) -> AppConfig:
"""Handles the configuration process when no config.toml file is found."""
click.echo(
"[bold yellow]Welcome to Viu![/bold yellow] No configuration file found."
)
from InquirerPy import inquirer
from .editor import InteractiveConfigEditor
from .generate import generate_config_toml_from_app_model
choice = inquirer.select( # type: ignore
message="How would you like to proceed?",
choices=[
"Use default settings (Recommended for new users)",
"Configure settings interactively",
],
default="Use default settings (Recommended for new users)",
).execute()
if "interactively" in choice:
editor = InteractiveConfigEditor(AppConfig())
app_config = editor.run()
else:
app_config = AppConfig()
config_toml_content = generate_config_toml_from_app_model(app_config)
try:
self.config_path.parent.mkdir(parents=True, exist_ok=True)
self.config_path.write_text(config_toml_content, encoding="utf-8")
click.echo(
f"Configuration file created at: [green]{self.config_path}[/green]"
)
except Exception as e:
raise ConfigError(
f"Could not create configuration file at {self.config_path!s}. "
f"Please check permissions. Error: {e}",
)
return app_config
def load(self, update: Dict = {}) -> AppConfig:
"""
Loads the configuration and returns a populated, validated AppConfig object.
Args:
update: A dictionary of CLI overrides to apply to the loaded config.
Returns:
An instance of AppConfig with values from the user's .toml file.
Raises:
ConfigError: If the configuration file contains validation or parsing errors.
"""
if not self.config_path.exists():
return self._handle_first_run()
try:
with self.config_path.open("rb") as f:
config_dict = tomllib.load(f)
except tomllib.TOMLDecodeError as e:
raise ConfigError(
f"Error parsing configuration file '{self.config_path}':\n{e}"
)
# Apply CLI overrides on top of the loaded configuration
if update:
for section, values in update.items():
if section in config_dict:
config_dict[section].update(values)
else:
config_dict[section] = values
try:
app_config = AppConfig.model_validate(config_dict)
return app_config
except ValidationError as e:
error_message = (
f"Configuration error in '{self.config_path}'!\n"
f"Please correct the following issues:\n\n{e}"
)
raise ConfigError(error_message)