Source code for confii.loaders.ini_loader

"""Loader for INI configuration files."""

import configparser
from pathlib import Path
from typing import Any, Dict, Optional

from confii.loaders.loader import Loader


[docs] class IniLoader(Loader): """Loader for INI configuration files. IniLoader loads configuration data from INI (.ini or .cfg) files using Python's ``configparser.RawConfigParser``. The raw parser is used to avoid interpolation of ``%`` characters that may appear in configuration values such as connection strings or format patterns. Each INI section becomes a top-level key in the resulting dictionary, with the section's key-value pairs nested underneath. Scalar values are automatically coerced to appropriate Python types (int, float, bool, None) via ``parse_scalar_value``. Attributes: source: Path to the INI configuration file. config: Loaded configuration dictionary. Example: >>> from confii.loaders import IniLoader >>> from confii import Config >>> >>> loader = IniLoader("database.ini") >>> config = Config(loaders=[loader]) >>> print(config.database.host) Note: Missing files are handled gracefully and return None instead of raising an exception. This allows for optional configuration files. The ``DEFAULT`` section in INI files is not included as a separate key; its values are inherited by other sections per standard configparser behavior. """
[docs] def __init__(self, source: str): """Initialize the INI loader. Args: source: Path to the INI file. """ super().__init__(source)
[docs] def load(self) -> Optional[Dict[str, Any]]: """Load configuration from an INI file. Reads the INI file, parses each section into a nested dictionary, and coerces scalar values to their appropriate Python types. Returns: Dictionary containing the loaded configuration keyed by section name, or None if the file does not exist or is unreadable. Raises: configparser.MissingSectionHeaderError: If the file is not valid INI format (missing section headers). Example: >>> loader = IniLoader("app.ini") >>> config_dict = loader.load() >>> if config_dict: ... print(config_dict["database"]["host"]) """ if not Path(self.source).exists(): return None # Use RawConfigParser to avoid interpolation of % characters parser = configparser.RawConfigParser() read_ok = parser.read(self.source) if not read_ok: # parser.read() silently ignores unreadable files return None config: Dict[str, Any] = {} for section in parser.sections(): config[section] = {} for key, value in parser.items(section): from confii.utils.type_coercion import parse_scalar_value config[section][key] = parse_scalar_value(value) return config