Source code for src.config

import collections
import argparse
import pathlib
import yaml
import sys

from src import _cfgmap

__version__ = "0.7"
__author__ = "Anilyka Barry"
__github__ = "https://github.com/Spireblight/Spireblight"
__botname__ = "Spireblight"

# This is the minimal development config needed.
DEFAULT_DEV_CONFIG = {
    "twitch": {
        "enabled": False,
        "oauth_token": "<Get one at https://twitchapps.com/tmi/>",
        "channel": "baalorlord",
    },
    "discord": {
        "enabled": False,
        "oauth_token": "<token unset>"
    },
    "server": {"debug": False, "secret": "<i-haven't-set-a-secret>"},
}

curpath: pathlib.Path = None
config: _cfgmap.Config

parser = argparse.ArgumentParser(__botname__)

parser.add_argument("--config-file", action="append", help="Add a config file to use")
parser.add_argument("--version", action="version", version=f"{__botname__} {__version__}")
parser.add_argument("--no-twitch", action="store_true", help="Disable the Twitch bot")
parser.add_argument("--no-discord", action="store_true", help="Disable the Discord bot")
parser.add_argument("--channel", help="Which Twitch channel to join")

[docs] def load_default_config(): """Load the default configuration into memory. :raises RuntimeError: If the default config file cannot be found :return: The configuration object, ready for use :rtype: _cfgmap.Config """ global curpath if curpath is None: curpath = pathlib.Path(".") conf = None for i in range(3): f = None try: f = (curpath / "default-config.yml").open() except (FileNotFoundError, PermissionError, IsADirectoryError): # you never know curpath = curpath / ".." else: conf = yaml.safe_load(f) break # this will break out of the loop, but finally always happens :> finally: if f is not None: f.close() if conf is None: raise RuntimeError("No default config could be found.") return _cfgmap.Config(**conf)
[docs] def parse_launch_args(argv: list[str] = None): """Parse the launch arguments, and find the files to load. :param argv: The command-line arguments to use, sys.argv if not specified. :type argv: list[str] :raises RuntimeError: If a specified config file could not be found. :return: A tuple of the files to load, and the override values. :rtype: tuple[list[pathlib.Path], dict] """ if argv is None: argv = sys.argv[1:] # first arg is just the program name # this is important; both testing and docgen use their own args # we should only care about what we actually use args, _ = parser.parse_known_args(argv) files: list[pathlib.Path] = [] override = collections.defaultdict(dict) if args.config_file: for file in args.config_file: file: str curfile = curpath / file if not curfile.is_file(): raise RuntimeError(f"Config file {file!r} could not be found.") files.append(curfile) else: # none specified, use the dev one devfile = curpath / "dev-config.yml" if not devfile.exists(): # make our own with devfile.open("w") as f: yaml.dump(DEFAULT_DEV_CONFIG, f) print("No dev-config.yml file found.") print("A skeleton has been written for you.") files.append(devfile) if args.channel: override["twitch"]["channel"] = args.channel if args.no_twitch: override["twitch"]["enabled"] = False if args.no_discord: override["discord"]["enabled"] = False return files, override
[docs] def load_user_config(conf: _cfgmap.Config, file: pathlib.Path): """Modify in-place the config with the user config. :param conf: The configuration object. :type conf: _cfgmap.Config :param file: A Path object pointing to the file. :type file: pathlib.Path """ with file.open() as f: cf = yaml.safe_load(f) conf.update(cf)
[docs] def load(): """Load the full configuration for the program.""" global config config = load_default_config() files, override = parse_launch_args() for file in files: load_user_config(config, file) config.update(override)