Source code for PyPoE.cli.config

"""
CLI config utilities

Overview
===============================================================================

+----------+------------------------------------------------------------------+
| Path     | PyPoE/cli/config.py                                              |
+----------+------------------------------------------------------------------+
| Version  | 1.0.0a0                                                          |
+----------+------------------------------------------------------------------+
| Revision | $Id: 2455f8f32d14f2910c3f21d015465883de77e524 $                  |
+----------+------------------------------------------------------------------+
| Author   | Omega_K2                                                         |
+----------+------------------------------------------------------------------+

Description
===============================================================================

Utilities to setup the config on the CLI interface.

Documentation
===============================================================================

Classes
-------------------------------------------------------------------------------

.. autoclass:: ConfigHelper
    :no-inherited-members:

Exceptions
-------------------------------------------------------------------------------

.. autoclass:: ConfigError

.. autoclass:: SetupError

Agreement
===============================================================================

See PyPoE/LICENSE
"""

# =============================================================================
# Imports
# =============================================================================

# Python
import sys
from collections import Iterable

# 3rd party
from configobj import ConfigObj
from validate import Validator

# self
from PyPoE.cli.core import console, Msg
from PyPoE.shared.config.validator import functions

# =============================================================================
# Globals
# =============================================================================

__all__ = ['ConfigError', 'SetupError', 'ConfigHelper']

# =============================================================================
# Exceptions
# =============================================================================


[docs]class ConfigError(ValueError): pass
[docs]class SetupError(ValueError): pass
# ============================================================================= # Classes # ============================================================================= #TODO: add link to configobj doc or doc the extra stuff separatly.
[docs]class ConfigHelper(ConfigObj): """ Extended regular config obj that can perform special tasks and extended handling. Generally the new options should be used over the direct usage of inherited functions. """
[docs] def __init__(self, *args, **kwargs): """ Raises ------ ValueError if the infile :py:class:`configobj.ConfigObj` parameter is not specified """ if 'infile' not in kwargs: raise ValueError('Must be initialized with infile') kwargs['raise_errors'] = True kwargs['configspec'] = ConfigObj() ConfigObj.__init__(self, *args, **kwargs) # Fix missing main sections for item in ['Config', 'Setup']: if item not in self: self.update({item: {}}) if item not in self.configspec: self.configspec.update({item: {}}) self.validator = Validator() self.validator.functions.update(functions) self._listeners = {}
@property def option(self): """ Returns config option section from the config handler. Returns ------- configobj.Section """ return self['Config'] @property def optionspec(self): """ Returns config option specification section from the config handler. Returns ------- configobj.Section """ return self.configspec['Config'] @property def setup(self): """ Returns config setup section from the config handler. Returns ------- configobj.Section """ return self['Setup'] @property def setupspec(self): """ Returns config setup specification section from the config handler. Returns ------- configobj.Section """ return self.configspec['Setup']
[docs] def add_option(self, key, specification): """ Adds (registers) a new config option with the specified key and specification. The key must be unique. Parameters ---------- key : str key to use for storage specification : str config specification string for validating values for this key Raises ------ KeyError if the key is a duplicate """ if key in self.optionspec: raise KeyError('Duplicate key: %s' % key) self.optionspec[key] = specification
[docs] def get_option(self, key, safe=True): """ Returns the handled value for the specified key from the config. If the safe option is specified the function will check if any setups for the specified key need to be formed and raises an Error if the setup is pending. If False this behaviour is disabled .. warning:: if the specified key is missing this method will shutdown the CLI with an error message to configure the key Parameters ---------- key : str key to retrieve the value for safe : bool whether to check setup is needed Returns ------- object handled value Raises ------ SetupError if the setup for the key was not performed """ if safe and key in self.setup: if not self.setup[key]['performed']: raise SetupError('Setup not performed.') try: value = self.option[key] except KeyError: console( 'Config variable "%s" is not configured. Consider running:' % key, msg=Msg.error) console('config set "%s" "<value>"' % key, msg=Msg.error) console('Exiting...', msg=Msg.error) sys.exit(-1) return self.validator.check(self.optionspec[key], value)
[docs] def set_option(self, key, value): """ Sets the key to the specified value. The function will also take care of the following: - invalidate setups registered for this key, if any - validate the value - execute listeners Parameters ---------- key : str the option key to set value : object the value to set the key to Raises ------ validate.ValidationError if the validation of the value failed """ if key in self.setup: self.setup[key]['performed'] = False # Raise ValidationError value = self.validator.check(self.optionspec[key], value) if key in self._listeners: for f in self._listeners[key]: f(key, value, self.option[key]) self.option[key] = value
[docs] def register_setup(self, key, funcs): """ Registers one or multiple functions that will be called to perform the setup for the specified config key. This will also create the according setup keys if non existent .. note:: Setup variables should be registered using this function before using any other 'setup' related functions. Parameters ---------- key : str config key to register the setup for funcs : callable or Iterable[callable] a function or iterable of functions to be called when the setup for the specified key is performed Raises ------ TypeError if funcs is not callable """ if key not in self.setup: self.setup.update({ key: { 'performed': False, }, }) self.setupspec.update({ key: { 'performed': 'boolean()', }, }) if isinstance(funcs, Iterable): for f in funcs: if not callable(f): raise TypeError('Callable expected.') elif not callable(funcs): raise TypeError('Callabe expected.') else: funcs = (funcs, ) self.setup[key].functions = funcs
[docs] def add_setup_listener(self, config_key, function): """ Adds a listener for the specified config key that triggers when the config value was changed. Function should take 3 arguments: * key: The key that was changed * value: the new value * old_value: the old value Parameters ---------- config_key : str config key to register the listener for function : callable callable to add as listener Raises ------ TypeError if function is not callable """ if not callable(function): raise TypeError('Callabe expected.') if config_key in self._listeners: self._listeners[config_key].append(function) else: self._listeners[config_key] = [function, ]
[docs] def add_setup_variable(self, setup_key, variable_key, specification): """ Adds a setup variable, i.e. a variable related to a specific setup For example this is useful to store additional information required to check whether a new run of setup is needed. Parameters ---------- setup_key : str the setup key to register the variable for variable_key : str the key of the variable itself (must be unique) specification : str the config specification to use for the variable Raises ------ KeyError if the setup key does not exist KeyError if the variable key is a duplicate """ if setup_key not in self.setupspec: raise KeyError('Setup key "%s" is invalid' % setup_key) if variable_key in self.setupspec[setup_key]: raise KeyError('Duplicate key: %s' % variable_key) self.setupspec[setup_key][variable_key] = specification
[docs] def get_setup_variable(self, setup_key, variable_key): """ Returns the stored variable for the specified setup key Parameters ---------- setup_key : str the setup key to retrieve the variable for variable_key : str the config key of the variable to retrieve Returns ------- object the value of the variable """ return self.setup[setup_key][variable_key]
[docs] def set_setup_variable(self, setup_key, variable_key, value): """ Sets the value for the specified setup key and variable Parameters ---------- setup_key : str the setup key to set the variable for variable_key : str the config key of the variable to set value : object the value to set the variable to Raises ------ validate.ValidationError if the validation of the value failed """ # Raise ValidationError value = self.validator.check(self.setupspec[setup_key][variable_key], value) self.setup[setup_key][variable_key] = value
[docs] def needs_setup(self, key): """ Returns whether the specified config key requires setup or not. .. warning :: This does not return whether the setup is performed, only whether this is a config key that requires setup. If you want to know whether setup was performed use is_setup. Parameters ---------- key : str the setup key to check Returns ------- bool True if setup needs to be performed """ return key in self.setup
[docs] def is_setup(self, variable): """ Returns whether the specified config key has it's setup performed Parameters ---------- key : str the setup key to check Returns ------- bool True if setup is performed """ return self.setup[variable]['performed']
[docs] def setup_or_raise(self, variable): """ Returns True if setup is performed for the specified config variable and raises an error if it isn't. Parameters ---------- variable : str config variable to check against Returns ------- True if setup is performed Raises ------ SetupError if setup is not performed """ if not self.is_setup(variable): raise SetupError('Setup for %s not performed' % variable) return True