Module botroyale.util.settings

Manages user settings.

Settings are configured using TOML. A built-in default config exists in the package, and a user-defined custom config exists in the usr dir. User-defined values take priority.

Expand source code Browse git
"""Manages user settings.

Settings are configured using TOML. A built-in default config exists in the
package, and a user-defined custom config exists in the usr dir. User-defined
values take priority.
"""
from typing import Any
import copy
import shutil
import warnings
from botroyale.util import PACKAGE_DIR
from botroyale.util.file import file_load, file_dump, toml_loads, get_usr_dir


def _create_missing_files():
    if not SETTINGS_FILE.is_file():
        file_dump(SETTINGS_FILE, "")
    if not DEFAULTS_FILE_USR.is_file():
        shutil.copy(DEFAULTS_FILE, DEFAULTS_FILE_USR)


DEFAULTS_FILE = PACKAGE_DIR / "default_settings.toml"
DEFAULTS = toml_loads(file_load(DEFAULTS_FILE))
SETTINGS_DIR = get_usr_dir("settings")
SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
SETTINGS_FILE = SETTINGS_DIR / "settings.toml"
DEFAULTS_FILE_USR = SETTINGS_DIR / "defaults.toml"
_create_missing_files()


def _import_settings():
    """Import settings from usr dir, fill missing entries from defaults.

    Ignores entries that don't exist in defaults.
    """
    # Gather settings from user and defaults
    settings = copy.deepcopy(DEFAULTS)
    usr_settings = {}
    if SETTINGS_FILE.is_file():
        usr_settings = toml_loads(file_load(SETTINGS_FILE))
    # Find all keys
    default_paths = set(_yield_setting_paths(settings))
    usr_paths = set(_yield_setting_paths(usr_settings))
    # Ignore paths that are not in defaults
    ignored_paths = usr_paths - default_paths
    for path in ignored_paths:
        warnings.warn(f'Unkown setting "{path}" (not in defaults)')
    shared_paths = default_paths & usr_paths
    for path in shared_paths:
        usr_value = _resolve_setting(usr_settings, path)
        _set_setting(settings, path, usr_value)
    return settings


def _resolve_setting(data, key_path):
    d = data
    for part in key_path.split("."):
        if part not in d:
            raise KeyError(f'Unkown setting "{key_path}" (not in defaults)')
        d = d[part]
    return d


def _set_setting(data, key_path, value):
    d = data
    path_parts = key_path.split(".")
    for part in path_parts[:-1]:
        if part not in d:
            raise KeyError(f'Unkown setting "{key_path}"')
        d = d[part]
    d[path_parts[-1]] = value


def _yield_setting_paths(d, path=None):
    path = [] if path is None else path
    for k, v in d.items():
        assert isinstance(k, str)
        new_path = [*path, k]
        if isinstance(v, dict):
            yield from _yield_setting_paths(v, path=new_path)
        else:
            yield ".".join(new_path)


class __Settings:
    data = _import_settings()


def get(setting_name: str) -> Any:
    """Get the value of *setting_name*."""
    v = _resolve_setting(__Settings.data, setting_name)
    return v


def clear():
    """Revert all settings data to defaults."""
    if SETTINGS_FILE.is_file():
        SETTINGS_FILE.unlink()
    if DEFAULTS_FILE_USR.is_file():
        DEFAULTS_FILE_USR.unlink()
    _create_missing_files()

Functions

def clear()

Revert all settings data to defaults.

Expand source code Browse git
def clear():
    """Revert all settings data to defaults."""
    if SETTINGS_FILE.is_file():
        SETTINGS_FILE.unlink()
    if DEFAULTS_FILE_USR.is_file():
        DEFAULTS_FILE_USR.unlink()
    _create_missing_files()
def get(setting_name: str) ‑> Any

Get the value of setting_name.

Expand source code Browse git
def get(setting_name: str) -> Any:
    """Get the value of *setting_name*."""
    v = _resolve_setting(__Settings.data, setting_name)
    return v