diff options
Diffstat (limited to 'src/home/config')
-rw-r--r-- | src/home/config/__init__.py | 12 | ||||
-rw-r--r-- | src/home/config/_configs.py | 5 | ||||
-rw-r--r-- | src/home/config/config.py | 170 | ||||
-rw-r--r-- | src/home/config/validators/__init__.py | 2 | ||||
-rw-r--r-- | src/home/config/validators/_util.py | 11 | ||||
-rw-r--r-- | src/home/config/validators/_validators.py | 32 |
6 files changed, 165 insertions, 67 deletions
diff --git a/src/home/config/__init__.py b/src/home/config/__init__.py index cc9c091..10c5bd9 100644 --- a/src/home/config/__init__.py +++ b/src/home/config/__init__.py @@ -1 +1,11 @@ -from .config import ConfigStore, config, is_development_mode, setup_logging +from .config import ( + Config, + config, + is_development_mode, + setup_logging, + app_config +) +from .validators import validate +from ._configs import ( + LinuxBoardsConfig +)
\ No newline at end of file diff --git a/src/home/config/_configs.py b/src/home/config/_configs.py new file mode 100644 index 0000000..2f98d06 --- /dev/null +++ b/src/home/config/_configs.py @@ -0,0 +1,5 @@ +from .config import ConfigUnit + + +class LinuxBoardsConfig(ConfigUnit): + NAME = 'linux_boards' diff --git a/src/home/config/config.py b/src/home/config/config.py index 4681685..2d49524 100644 --- a/src/home/config/config.py +++ b/src/home/config/config.py @@ -3,45 +3,116 @@ import yaml import logging import os +from . import validators from os.path import join, isdir, isfile from typing import Optional, Any, MutableMapping from argparse import ArgumentParser from ..util import parse_addr -def _get_config_path(name: str) -> str: - formats = ['toml', 'yaml'] +_my_validators = {} - dirname = join(os.environ['HOME'], '.config', name) - if isdir(dirname): - for fmt in formats: - filename = join(dirname, f'config.{fmt}') - if isfile(filename): - return filename +def _get_validator(name: str) -> Optional[callable]: + if hasattr(validators, f'{name}_validator'): + return getattr(validators, f'{name}_validator') + if name in _my_validators: + return _my_validators[name] + return None - raise IOError(f'config not found in {dirname}') - else: - filenames = [join(os.environ['HOME'], '.config', f'{name}.{format}') for format in formats] - for file in filenames: - if isfile(file): - return file +def add_validator(name: str, f: callable): + _my_validators[name] = f - raise IOError(f'config not found') +class ConfigUnit: + NAME = 'dumb' -class ConfigStore: data: MutableMapping[str, Any] + + @classmethod + def get_config_path(cls, name=None) -> str: + if name is None: + name = cls.NAME + if name is None: + raise ValueError('get_config_path: name is none') + + dirnames = ( + join(os.environ['HOME'], '.config', 'homekit'), + '/etc/homekit' + ) + + for dirname in dirnames: + if isdir(dirname): + for fmt in ('toml', 'yaml'): + filename = join(dirname, f'{name}.{fmt}') + if isfile(filename): + return filename + + raise IOError(f'config \'{name}\' not found') + + def __init__(self, name=None): + self.data = {} + + if self.NAME != 'dumb': + self.load_from(self.get_config_path()) + self.validate() + + elif name is not None: + self.NAME = name + + def load_from(self, path: str): + if path.endswith('.toml'): + self.data = toml.load(path) + elif path.endswith('.yaml'): + with open(path, 'r') as fd: + self.data = yaml.safe_load(fd) + + def validate(self): + v = _get_validator(self.NAME) + v(self.data) + + def __getitem__(self, key): + return self.data[key] + + def __setitem__(self, key, value): + raise NotImplementedError('overwriting config values is prohibited') + + def __contains__(self, key): + return key in self.data + + def get(self, key: str, default=None): + cur = self.data + pts = key.split('.') + for i in range(len(pts)): + k = pts[i] + if i < len(pts)-1: + if k not in cur: + raise KeyError(f'key {k} not found') + else: + return cur[k] if k in cur else default + cur = self.data[k] + raise KeyError(f'option {key} not found') + + def get_addr(self, key: str): + return parse_addr(self.get(key)) + + def items(self): + return self.data.items() + + +class Config: app_name: Optional[str] + app_config: ConfigUnit def __init__(self): - self.data = {} self.app_name = None + self.app_config = ConfigUnit() - def load(self, name: Optional[str] = None, - use_cli=True, - parser: ArgumentParser = None): + def load_app(self, + name: Optional[str] = None, + use_cli=True, + parser: ArgumentParser = None): self.app_name = name if (name is None) and (not use_cli): @@ -75,65 +146,32 @@ class ConfigStore: log_default_fmt = args.log_default_fmt if not no_config and path is None: - path = _get_config_path(name) - - if no_config: - self.data = {} - else: - if path.endswith('.toml'): - self.data = toml.load(path) - elif path.endswith('.yaml'): - with open(path, 'r') as fd: - self.data = yaml.safe_load(fd) - - if 'logging' in self: - if not log_file and 'file' in self['logging']: - log_file = self['logging']['file'] - if log_default_fmt and 'default_fmt' in self['logging']: - log_default_fmt = self['logging']['default_fmt'] + path = ConfigUnit.get_config_path(name=name) + + if not no_config: + self.app_config.load_from(path) + + if 'logging' in self.app_config: + if not log_file and 'file' in self.app_config['logging']: + log_file = self.app_config['logging']['file'] + if log_default_fmt and 'default_fmt' in self.app_config['logging']: + log_default_fmt = self.app_config['logging']['default_fmt'] setup_logging(log_verbose, log_file, log_default_fmt) if use_cli: return args - def __getitem__(self, key): - return self.data[key] - - def __setitem__(self, key, value): - raise NotImplementedError('overwriting config values is prohibited') - - def __contains__(self, key): - return key in self.data - - def get(self, key: str, default=None): - cur = self.data - pts = key.split('.') - for i in range(len(pts)): - k = pts[i] - if i < len(pts)-1: - if k not in cur: - raise KeyError(f'key {k} not found') - else: - return cur[k] if k in cur else default - cur = self.data[k] - raise KeyError(f'option {key} not found') - - def get_addr(self, key: str): - return parse_addr(self.get(key)) - - def items(self): - return self.data.items() - -config = ConfigStore() +config = Config() +app_config = config.app_config def is_development_mode() -> bool: if 'HK_MODE' in os.environ and os.environ['HK_MODE'] == 'dev': return True - return ('logging' in config) and ('verbose' in config['logging']) and (config['logging']['verbose'] is True) + return ('logging' in config.app_config) and ('verbose' in config.app_config['logging']) and (config.app_config['logging']['verbose'] is True) def setup_logging(verbose=False, log_file=None, default_fmt=False): diff --git a/src/home/config/validators/__init__.py b/src/home/config/validators/__init__.py new file mode 100644 index 0000000..0e75132 --- /dev/null +++ b/src/home/config/validators/__init__.py @@ -0,0 +1,2 @@ +from ._validators import * +from ._util import validate diff --git a/src/home/config/validators/_util.py b/src/home/config/validators/_util.py new file mode 100644 index 0000000..5227c40 --- /dev/null +++ b/src/home/config/validators/_util.py @@ -0,0 +1,11 @@ +import inspect + +from cerberus import Validator, DocumentError + + +def validate(schema, data): + v = Validator(schema) + if not v.validate(data): + frame = inspect.currentframe().f_back + caller_name = frame.f_code.co_name + raise DocumentError(f'{caller_name}: failed to validate data: ' + v.errors) diff --git a/src/home/config/validators/_validators.py b/src/home/config/validators/_validators.py new file mode 100644 index 0000000..cddc1b0 --- /dev/null +++ b/src/home/config/validators/_validators.py @@ -0,0 +1,32 @@ +from ._util import validate + +__all__ = [ + 'linux_boards_validator' +] + + +def linux_boards_validator(data) -> None: + validate({ + 'type': 'dict', + 'valuesrules': { + 'type': 'dict', + 'schema': { + 'mdns': {'type': 'string', 'required': True}, + 'board': {'type': 'string', 'required': True}, + 'network': {'type': 'list', 'required': True, 'empty': False}, + 'ram': {'type': 'integer', 'required': True}, + 'ext_hdd': { + 'type': 'list', + 'schema': { + 'type': 'dict', + 'schema': { + 'mountpoint': {'type': 'string', 'required': True}, + 'size': {'type': 'integer', 'required': True} + } + }, + }, + 'services': {'type': 'list', 'empty': False}, + 'online': {'type': 'boolean', 'required': True} + } + } + }, data) |