Fix naming from old scheme. Closes #4

master
Tom Wilson 6 years ago
parent ccd5a50892
commit 2da1486d8e

@ -1,6 +1,6 @@
""" """
Configuration management module. Enables configuration to be validated against Configuration management module. Enables configuration to be validated against
requirement definitions before being loaded and used. requirement specifications before being loaded and used.
Compatible with both raw config data structures and TOML files, config data must Compatible with both raw config data structures and TOML files, config data must
start with a root dict containing named "config bundles". These are intended to start with a root dict containing named "config bundles". These are intended to
@ -41,7 +41,7 @@ import toml
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from copy import deepcopy from copy import deepcopy
from .freezedry import freezedryable, rehydrate from preserve import preservable, restore
class InvalidConfigError(Exception): class InvalidConfigError(Exception):
@ -51,7 +51,7 @@ class InvalidConfigError(Exception):
# map directly to Dictionaries (Tables), and Lists (Arrays) # map directly to Dictionaries (Tables), and Lists (Arrays)
class _ConfigDefinition(ABC): class _ValueSpecification(ABC):
def __init__(self, default=None, optional=False, helptext=""): def __init__(self, default=None, optional=False, helptext=""):
self.default = default self.default = default
self.optional = optional self.optional = optional
@ -60,14 +60,14 @@ class _ConfigDefinition(ABC):
@abstractmethod @abstractmethod
def validate(self, value): def validate(self, value):
""" """
Checks the supplied value to confirm that it complies with this ConfigDefinition. Checks the supplied value to confirm that it complies with this specification.
Raises InvalidConfigError on failure. Raises InvalidConfigError on failure.
""" """
pass pass
@freezedryable @preservable
class BoolDef(_ConfigDefinition): class BoolSpec(_ValueSpecification):
def __init__(self, default=None, optional=False, helptext=""): def __init__(self, default=None, optional=False, helptext=""):
super().__init__(default, optional, helptext) super().__init__(default, optional, helptext)
@ -76,8 +76,8 @@ class BoolDef(_ConfigDefinition):
raise InvalidConfigError("Config value must be a boolean") raise InvalidConfigError("Config value must be a boolean")
@freezedryable @preservable
class IntDef(_ConfigDefinition): class IntSpec(_ValueSpecification):
def __init__(self, default=None, minval=None, maxval=None, def __init__(self, default=None, minval=None, maxval=None,
optional=False, helptext=""): optional=False, helptext=""):
super().__init__(default, optional, helptext) super().__init__(default, optional, helptext)
@ -95,8 +95,8 @@ class IntDef(_ConfigDefinition):
str(self.maxval)) str(self.maxval))
@freezedryable @preservable
class StringDef(_ConfigDefinition): class StringSpec(_ValueSpecification):
def __init__(self, default="", minlength=None, maxlength=None, def __init__(self, default="", minlength=None, maxlength=None,
optional=False, helptext=""): optional=False, helptext=""):
super().__init__(default, optional, helptext) super().__init__(default, optional, helptext)
@ -114,53 +114,53 @@ class StringDef(_ConfigDefinition):
str(self.maxlength)) str(self.maxlength))
@freezedryable @preservable
class DictDef(_ConfigDefinition): class DictSpec(_ValueSpecification):
def __init__(self, default=None, optional=False, helptext=""): def __init__(self, default=None, optional=False, helptext=""):
super().__init__(default, optional, helptext) super().__init__(default, optional, helptext)
self.def_dict = {} self.spec_dict = {}
def add_def(self, name, newdef): def add_spec(self, name, newspec):
if not isinstance(newdef, _ConfigDefinition): if not isinstance(newspec, _ValueSpecification):
raise TypeError("Config definiton must be an instance of a " raise TypeError("Config specification must be an instance of a "
"ConfigDefinition subclass") "_ValueSpecification subclass")
if not isinstance(name, str): if not isinstance(name, str):
raise TypeError("Config definition name must be a string") raise TypeError("Config specification name must be a string")
self.def_dict[name] = newdef self.spec_dict[name] = newspec
return newdef return newspec
def validate(self, value_dict): def validate(self, value_dict):
""" """
Checks the supplied value to confirm that it complies with this ConfigDefinition. Checks the supplied value to confirm that it complies with this specification.
Raises InvalidConfigError on failure. Raises InvalidConfigError on failure.
This *can* modify the supplied value dict, inserting defaults for any child This *can* modify the supplied value dict, inserting defaults for any child
ConfigDefinitions that are marked as optional. config specifications that are marked as optional.
""" """
def_set = set(self.def_dict.keys()) spec_set = set(self.spec_dict.keys())
value_set = set(value_dict.keys()) value_set = set(value_dict.keys())
for missing_key in def_set - value_set: for missing_key in spec_set - value_set:
if not self.def_dict[missing_key].optional: if not self.spec_dict[missing_key].optional:
raise InvalidConfigError("Dict must contain key: " + raise InvalidConfigError("Dict must contain key: " +
missing_key) missing_key)
else: else:
value_dict[missing_key] = self.def_dict[missing_key].default value_dict[missing_key] = self.spec_dict[missing_key].default
for extra_key in value_set - def_set: for extra_key in value_set - spec_set:
raise InvalidConfigError("Dict contains unknown key: " + raise InvalidConfigError("Dict contains unknown key: " +
extra_key) extra_key)
for key, value in value_dict.items(): for key, value in value_dict.items():
try: try:
self.def_dict[key].validate(value) self.spec_dict[key].validate(value)
except InvalidConfigError as e: except InvalidConfigError as e:
e.args = ("Key: " + key,) + e.args e.args = ("Key: " + key,) + e.args
raise raise
def get_template(self, include_optional=False): def get_template(self, include_optional=False):
""" """
Return a config dict with the minimum structure required for this ConfigDefinition. Return a config dict with the minimum structure required for this specification.
Default values will be included, though not all required fields will necessarily have Default values will be included, though not all required fields will necessarily have
defaults that successfully validate. defaults that successfully validate.
@ -169,21 +169,21 @@ class DictDef(_ConfigDefinition):
required ones required ones
Returns: Returns:
Dict containing the structure that should be passed back in (with values) to comply Dict containing the structure that should be passed back in (with values) to comply
with this ConfigDefinition. with this config specification.
""" """
template = {} template = {}
for key, confdef in self.def_dict.items(): for key, confspec in self.spec_dict.items():
if confdef.optional and (not include_optional): if confspec.optional and (not include_optional):
continue continue
if hasattr(confdef, "get_template"): if hasattr(confspec, "get_template"):
template[key] = confdef.get_template(include_optional) template[key] = confspec.get_template(include_optional)
else: else:
template[key] = confdef.default template[key] = confspec.default
return template return template
class _ListDefMixin(): class _ListSpecMixin():
def validate(self, value_list): def validate(self, value_list):
if not isinstance(value_list, list): if not isinstance(value_list, list):
raise InvalidConfigError("Config item must be a list") raise InvalidConfigError("Config item must be a list")
@ -201,35 +201,35 @@ class _ListDefMixin():
return [self.default] return [self.default]
@freezedryable @preservable
class BoolListDef(_ListDefMixin, BoolDef): class BoolListSpec(_ListSpecMixin, BoolSpec):
pass pass
@freezedryable @preservable
class IntListDef(_ListDefMixin, IntDef): class IntListSpec(_ListSpecMixin, IntSpec):
pass pass
@freezedryable @preservable
class StringListDef(_ListDefMixin, StringDef): class StringListSpec(_ListSpecMixin, StringSpec):
pass pass
@freezedryable @preservable
class DictListDef(_ListDefMixin, DictDef): class DictListSpec(_ListSpecMixin, DictSpec):
pass pass
@freezedryable @preservable
class ConfDefinition(DictDef): class ConfigSpecification(DictSpec):
pass pass
class ConfigManager(): class ConfigManager():
def __init__(self): def __init__(self):
self.root_config = {} self.root_config = {}
self.confdefs = {} self.confspecs = {}
self.frozen_config = {} self.frozen_config = {}
@staticmethod @staticmethod
@ -297,32 +297,32 @@ class ConfigManager():
frozen_value[names[-1]] = target_field[names[-1]] frozen_value[names[-1]] = target_field[names[-1]]
def add_confdef(self, bundle_name, confdef): def add_confspec(self, bundle_name, confspec):
""" """
Stores a ConfigDefinition for future use when validating the corresponding config bundle Stores a ConfigSpecification for future use when validating the corresponding config bundle
Args: Args:
bundle_name (str) : The name to store the config definition under. bundle_name (str) : The name to store the config specification under.
confdef (ConfigDefinition): The populated ConfigDefinition to store. confspec (ConfigSpecification): The populated ConfigSpecification to store.
""" """
self.confdefs[bundle_name] = confdef self.confspecs[bundle_name] = confspec
def add_confdefs(self, confdefs): def add_confspecs(self, confspecs):
""" """
Stores multiple ConfigDefinitions at once for future use when validating the corresponding Stores multiple ConfigSpecifications at once for future use when validating the corresponding
config bundles config bundles
Args: Args:
confdefs : A dict of populated ConfigDefinitions to store, using their keys as names. confspecs : A dict of populated ConfigSpecifications to store, using their keys as names.
""" """
self.confdefs.update(confdefs) self.confspecs.update(confspecs)
def list_missing_confdefs(self): def list_missing_confspecs(self):
""" """
Returns a list of config bundle names that do not have a corresponding ConfigDefinition Returns a list of config bundle names that do not have a corresponding ConfigSpecification
stored in the ConfigManager. stored in the ConfigManager.
""" """
return list(self.root_config.keys() - self.confdefs.keys()) return list(self.root_config.keys() - self.confspecs.keys())
def _overlay(self, src, dest): def _overlay(self, src, dest):
for key in src: for key in src:
@ -333,31 +333,31 @@ class ConfigManager():
# Otherwise it's either an existing value to be replaced or needs to be added. # Otherwise it's either an existing value to be replaced or needs to be added.
dest[key] = src[key] dest[key] = src[key]
def get_config_bundle(self, bundle_name, conf_def=None): def get_config_bundle(self, bundle_name, conf_spec=None):
""" """
Get a config bundle called ``bundle_name`` and validate Get a config bundle called ``bundle_name`` and validate
it against the corresponding config definition stored in the ConfigManager. it against the corresponding config specification stored in the ConfigManager.
If ``conf_def`` is supplied, it gets used instead. Returns a validated If ``conf_spec`` is supplied, it gets used instead. Returns a copy of the validated
config bundle dict. config bundle dict.
Note that as part of validation, optional keys that are missing will be Note that as part of validation, optional keys that are missing will be
filled in with their default values (see ``DictDef``). This function will copy filled in with their default values (see ``DictSpec``). This function will copy
the config bundle *after* validation, and so config loaded in the ConfManager will the config bundle *after* validation, and so config loaded in the ConfManager will
be modified, but future ConfigManager manipulations won't change the returned config be modified, but future ConfigManager manipulations won't change the returned config
bundle. bundle.
Args: Args:
config_name: (str) Name of the config dict to find. config_name: (str) Name of the config dict to find.
conf_def: (ConfDefinition) Optional config definition to validate against. conf_spec: (ConfigSpecification) Optional config specification to validate against.
""" """
if not isinstance(conf_def, ConfDefinition): if not isinstance(conf_spec, ConfigSpecification):
conf_def = self.confdefs[bundle_name] conf_spec = self.confspecs[bundle_name]
if bundle_name not in self.root_config: if bundle_name not in self.root_config:
raise InvalidConfigError( raise InvalidConfigError(
"Config must contain dict: " + bundle_name) "Config must contain dict: " + bundle_name)
try: try:
conf_def.validate(self.root_config[bundle_name]) conf_spec.validate(self.root_config[bundle_name])
except InvalidConfigError as e: except InvalidConfigError as e:
e.args = ("Bundle: " + bundle_name,) + e.args e.args = ("Bundle: " + bundle_name,) + e.args
raise raise
@ -366,11 +366,11 @@ class ConfigManager():
def get_config_bundles(self, bundle_names): def get_config_bundles(self, bundle_names):
""" """
Get multiple config bundles from the root dict at once, validating each one with the Get multiple config bundles from the root dict at once, validating each one with the
corresponding confdef stored in the ConfigManager. See ``get_config_bundle`` corresponding ConfigSpecification stored in the ConfigManager. See ``get_config_bundle``
Args: Args:
bundle_names: A list of config bundle names to get. If dictionary is supplied, uses bundle_names: A list of config bundle names to get. If dictionary is supplied, uses
the values as ConfigDefinitions rather than looking up a stored one in the the values as ConfigSpecifications rather than looking up a stored one in the
ConfigManager. ConfigManager.
Returns: Returns:
@ -378,8 +378,8 @@ class ConfigManager():
""" """
config_values = {} config_values = {}
if isinstance(bundle_names, dict): if isinstance(bundle_names, dict):
for name, conf_def in bundle_names.items(): for name, conf_spec in bundle_names.items():
config_values[name] = self.get_config_bundle(name, conf_def) config_values[name] = self.get_config_bundle(name, conf_spec)
else: else:
for name in bundle_names: for name in bundle_names:
config_values[name] = self.get_config_bundle(name) config_values[name] = self.get_config_bundle(name)
@ -419,4 +419,4 @@ def update_toml_message(filepath, message):
def gen_comment(string): def gen_comment(string):
return '\n# shepherd_message: ' + '\n# '.join(string.replace('#', '').splitlines()) + '\n' return '\n# config-spec_message: ' + '\n# '.join(string.replace('#', '').splitlines()) + '\n'

Loading…
Cancel
Save