Source code for cfgtree.models

# coding: utf-8
"""
Configuration Tree management.
"""
# Standard Libraries
import json
import logging
import os

# Cfgtree modules
from cfgtree.dictxpath import get_node_by_xpath
from cfgtree.dictxpath import make_xpath
from cfgtree.dictxpath import set_node_by_xpath

log = logging.getLogger(__name__)

# flake8: F404


[docs]class ConfigBaseModel(object): """Main configuration class You need to inherit from this base class and implement the following members: - ``model``: hierarchical dictionary representing the configuration tree - ``environ_var_prefix``: prefix for environment variable, to avoid conflicts - ``storage``: class to use for configuration storage - ``cmd_line_parser``: which command line argument parser to use Usage: .. code-block:: python from cfgtree import ConfigBaseModel from cfgtree.cmdline_parsers.argparse import ArgparseCmdlineParser class MyAppConfig(ConfigBaseModel): # All environment variables should start by MYAPP_ to avoid collision with other # application's or system's environment variables environ_var_prefix = "MYAPP_" # My configuration should be read from a single JSON file storage = JsonConfigFile( # User can overwrite the configuration file name with this environment variable environ_var="MYAPP_COMMON_CONFIG_FILE", # or this command line parameter long_param="--config-file", short_param="-c", # If not set, search for the `config.json` file in the current directory default_filename="config.json", ) # Use `argparse` to parse the command line cmd_line_parser = ArgparseCmdlineParser() # Here is the main settings model for the application model = { # Redefine configfile with ConfigFileCfg so that it appears in --help "configfile": ConfigFileCfg(long_param="--config-file", short_param="-c", summary="Configuration file"), # can holds a version information for the storage file "version": VersionCfg(), "general": { "verbose": BoolCfg(short_param='-v', long_param="--verbose", summary='Enable verbose output logs'), "logfile": StringCfg(short_param="-l", summary='Output log to file'), }, } # As an example, the 'verbose' setting can then be configured by: # - environment variable "MYAPP_GENERAL_VERBOSE" # - command line option ``--verbose` # - key ``general.verbose`` in configuration file ``config.json`` cfg = MyAppConfig() # Read cfg.get_cfg_value("group1.dict_opt.key1") # Write cfg.set_cfg_value("group1.dict_opt.key1", "newval") """ model = None """Main configuration tree model""" environ_var_prefix = None """Prefix for all environment variable (to avoid conflict with other app) Example:: environ_var_prefix="MYAPP_" """ storage = None """Configuration storage class to use with this model Should be a class inheriting from ConfigBaseStorage""" cmd_line_parser = None """Command line parser to use. Should be a class inheriting from CmdlineParsersBase""" autosave = False """Automatically write on change on file support Note some storage might automatically reflect all changes """ def __init__(self, model=None, environ_var_prefix=None, storage=None, cmd_line_parser=None, autosave=False): """Construct your configuration object Optional arguments. model ([type], optional): Defaults to None. Model to define environ_var_prefix ([type], optional): Defaults to None. environment variable prefix storage ([type], optional): Defaults to None. storage class cmd_line_parser ([type], optional): Defaults to None. command line parser autosave ([type], optional): Defaults to None. command line parser """ if model and self.model is None: self.model = model if environ_var_prefix and self.environ_var_prefix is None: self.environ_var_prefix = environ_var_prefix if storage and self.storage is None: self.storage = storage if cmd_line_parser and self.cmd_line_parser is None: self.cmd_line_parser = cmd_line_parser if autosave and self.autosave: self.autosave = autosave self._inject_names() def _inject_names(self, root=None, xpath=None): """ Inject configuration item name defined in the cfgtree dict inside each `_Cfg`. """ if root is None: if self.model is None: return root = self.model # pylint: disable=no-member for name, item in root.items(): if isinstance(item, dict): self._inject_names(root=item, xpath=make_xpath(xpath, name)) else: item.name = name item.xpath = make_xpath(xpath, name) item.environ_var_prefix = self.environ_var_prefix if item.ignore_in_cfg: # log.debug("Create cfg node '%s': ignored (handled later)", item.xpath) continue log.debug("Create cfg node: '%s' (name: '%s', cmd line: '%s'), default : %r", item.xpath, item.name, item.long_param, item.safe_value) # pylint: enable=no-member
[docs] def enable_autosave(self): self.autosave = True
[docs] def disable_autosave(self): self.autosave = False
[docs] def set_cfg_value(self, xpath, value): """ Set a value in cfgtree. """ set_node_by_xpath(self.model, xpath, value, extend=True, setter_attr="set_value") if self.autosave: self.save_configuration()
[docs] def get_cfg_value(self, xpath, default=None): """ Get a value from cfgtree. """ return get_node_by_xpath(self.model, xpath, default=default).value
[docs] def find_configuration_values(self, argv=None): """Main cfgtree entrypoint""" self._load_configuration(argv=argv) self._load_environment_variables("", self.model) self._load_cmd_line_arg(argv)
def _load_cmd_line_arg(self, argv=None): if self.cmd_line_parser: self.cmd_line_parser.parse_cmd_line(self.model, argv) def _load_configuration(self, argv=None): log.debug("Looking for configuration") self.storage.find_storage(self.model, argv=argv) bare_cfg = self.storage.get_bare_config_dict() self._load_cfg_dict(bare_cfg)
[docs] def save_configuration(self): """Save configuration to storage""" log.debug("Saving configuration file") bare_cfg = self._dict(safe=False) self.storage.save_bare_config_dict(bare_cfg)
def _load_cfg_dict(self, cfg, xpath=None): for k, v in cfg.items(): xp = make_xpath(xpath, k) if isinstance(v, dict): self._load_cfg_dict(v, xp) else: try: self.set_cfg_value(xp, v) except KeyError: log.error("Unable to load value '%s' from configuration file, " "no matching item in configuration tree (invalid '%s')", k, xp) def _load_environment_variables(self, xpath, root): """ Inject value from environment variable. """ for name, item in root.items(): if isinstance(item, dict): self._load_environment_variables(make_xpath(xpath, name), item) elif item.environ_var and item.environ_var in os.environ: if item.ignore_in_cfg: log.debug("Ignoring environment variable %s", item.environ_var) val = item.read_environ_var() log.debug("Found environment variable '%s': %s (conf: %s)", item.environ_var, val, item.xpath) item.value = val def _dict(self, root=None, safe=False): """ Return the configuration as a dictionnary. """ if root is None: root = self.model d = {} # pylint: disable=no-member for name, item in root.items(): if isinstance(item, dict): d[name] = self._dict(root=item, safe=safe) else: if item.ignore_in_cfg: continue elif safe: d[name] = item.safe_value else: d[name] = item.value # pylint: enable=no-member return d
[docs] def json(self, safe=False): """Dumps current configuration tree into a human readable json""" return json.dumps(self._dict(safe=safe), sort_keys=True, indent=4, separators=(',', ': '))