""" Implements loading and providing the configuration of the radar instance. Usage should be straight-forward: >>> from . import _config >>> config = _config.load() >>> config.locations ['New York City', 'Jersey City'] An example configuration file looks like this: [Waterspout Radar] db = .waterspout/db.json key = [Locations] New York City Jersey City """ import configparser import dataclasses import os import typing @dataclasses.dataclass class Config: db: str key: str locations: typing.List[str] class ConfigError(Exception): "Raised when configuration could not be loaded or was malformed." def effective_pathname(pathname: str = None) -> str: "Pathname of the configuration file based on optional *pathname*, environment, and default." return pathname or os.getenv("WATERSPOUT_RADAR_CONFIG") or "waterspout-radar.ini" def load(pathname: str) -> Config: "Loads configuration from a file at *pathname*. May raise ConfigError when content does not meet expectations." config = configparser.ConfigParser(allow_no_value=True) config.read(pathname) raw = {} errors = [] def _load_key(section, name, target=None, type=None, default=None, required=False): target = target or name try: value = config[section][name] if type: value = type(value) raw[target] = value except (KeyError, ValueError): if default: raw[target] = default if required: errors.append(f"Config is missing required field: {pathname}: {section}: {name}") _load_key("Waterspout Radar", "db", default="radar.json") _load_key("Waterspout Radar", "key", required=True) try: raw["locations"] = list(config["Locations"]) except KeyError: errors.append(f"Config is missing Locations: {pathname}") if errors: raise ConfigError(*errors) return Config(**raw)