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
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
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 copy import deepcopy
from .freezedry import freezedryable, rehydrate
from preserve import preservable, restore
class InvalidConfigError(Exception):
@ -51,7 +51,7 @@ class InvalidConfigError(Exception):
# map directly to Dictionaries (Tables), and Lists (Arrays)
class _ConfigDefinition(ABC):
class _ValueSpecification(ABC):
def __init__(self, default=None, optional=False, helptext=""):
self.default = default
self.optional = optional
@ -60,14 +60,14 @@ class _ConfigDefinition(ABC):
@abstractmethod
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.
"""
pass
@freezedryable
class BoolDef(_ConfigDefinition):
@preservable
class BoolSpec(_ValueSpecification):
def __init__(self, default=None, optional=False, helptext=""):
super().__init__(default, optional, helptext)
@ -76,8 +76,8 @@ class BoolDef(_ConfigDefinition):
raise InvalidConfigError("Config value must be a boolean")
@freezedryable
class IntDef(_ConfigDefinition):
@preservable
class IntSpec(_ValueSpecification):
def __init__(self, default=None, minval=None, maxval=None,
optional=False, helptext=""):
super().__init__(default, optional, helptext)
@ -95,8 +95,8 @@ class IntDef(_ConfigDefinition):
str(self.maxval))
@freezedryable
class StringDef(_ConfigDefinition):
@preservable
class StringSpec(_ValueSpecification):
def __init__(self, default="", minlength=None, maxlength=None,
optional=False, helptext=""):
super().__init__(default, optional, helptext)
@ -114,53 +114,53 @@ class StringDef(_ConfigDefinition):
str(self.maxlength))
@freezedryable
class DictDef(_ConfigDefinition):
@preservable
class DictSpec(_ValueSpecification):
def __init__(self, default=None, optional=False, helptext=""):
super().__init__(default, optional, helptext)
self.def_dict = {}
self.spec_dict = {}
def add_def(self, name, newdef):
if not isinstance(newdef, _ConfigDefinition):
raise TypeError("Config definiton must be an instance of a "
"ConfigDefinition subclass")
def add_spec(self, name, newspec):
if not isinstance(newspec, _ValueSpecification):
raise TypeError("Config specification must be an instance of a "
"_ValueSpecification subclass")
if not isinstance(name, str):
raise TypeError("Config definition name must be a string")
self.def_dict[name] = newdef
return newdef
raise TypeError("Config specification name must be a string")
self.spec_dict[name] = newspec
return newspec
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.
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())
for missing_key in def_set - value_set:
if not self.def_dict[missing_key].optional:
for missing_key in spec_set - value_set:
if not self.spec_dict[missing_key].optional:
raise InvalidConfigError("Dict must contain key: " +
missing_key)
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: " +
extra_key)
for key, value in value_dict.items():
try:
self.def_dict[key].validate(value)
self.spec_dict[key].validate(value)
except InvalidConfigError as e:
e.args = ("Key: " + key,) + e.args
raise
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
defaults that successfully validate.
@ -169,21 +169,21 @@ class DictDef(_ConfigDefinition):
required ones
Returns:
Dict containing the structure that should be passed back in (with values) to comply
with this ConfigDefinition.
with this config specification.
"""
template = {}
for key, confdef in self.def_dict.items():
if confdef.optional and (not include_optional):
for key, confspec in self.spec_dict.items():
if confspec.optional and (not include_optional):
continue
if hasattr(confdef, "get_template"):
template[key] = confdef.get_template(include_optional)
if hasattr(confspec, "get_template"):
template[key] = confspec.get_template(include_optional)
else:
template[key] = confdef.default
template[key] = confspec.default
return template
class _ListDefMixin():
class _ListSpecMixin():
def validate(self, value_list):
if not isinstance(value_list, list):
raise InvalidConfigError("Config item must be a list")
@ -201,35 +201,35 @@ class _ListDefMixin():
return [self.default]
@freezedryable
class BoolListDef(_ListDefMixin, BoolDef):
@preservable
class BoolListSpec(_ListSpecMixin, BoolSpec):
pass
@freezedryable
class IntListDef(_ListDefMixin, IntDef):
@preservable
class IntListSpec(_ListSpecMixin, IntSpec):
pass
@freezedryable
class StringListDef(_ListDefMixin, StringDef):
@preservable
class StringListSpec(_ListSpecMixin, StringSpec):
pass
@freezedryable
class DictListDef(_ListDefMixin, DictDef):
@preservable
class DictListSpec(_ListSpecMixin, DictSpec):
pass
@freezedryable
class ConfDefinition(DictDef):
@preservable
class ConfigSpecification(DictSpec):
pass
class ConfigManager():
def __init__(self):
self.root_config = {}
self.confdefs = {}
self.confspecs = {}
self.frozen_config = {}
@staticmethod
@ -297,32 +297,32 @@ class ConfigManager():
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:
bundle_name (str) : The name to store the config definition under.
confdef (ConfigDefinition): The populated ConfigDefinition to store.
bundle_name (str) : The name to store the config specification under.
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
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.
"""
return list(self.root_config.keys() - self.confdefs.keys())
return list(self.root_config.keys() - self.confspecs.keys())
def _overlay(self, src, dest):
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.
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
it against the corresponding config definition stored in the ConfigManager.
If ``conf_def`` is supplied, it gets used instead. Returns a validated
it against the corresponding config specification stored in the ConfigManager.
If ``conf_spec`` is supplied, it gets used instead. Returns a copy of the validated
config bundle dict.
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
be modified, but future ConfigManager manipulations won't change the returned config
bundle.
Args:
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):
conf_def = self.confdefs[bundle_name]
if not isinstance(conf_spec, ConfigSpecification):
conf_spec = self.confspecs[bundle_name]
if bundle_name not in self.root_config:
raise InvalidConfigError(
"Config must contain dict: " + bundle_name)
try:
conf_def.validate(self.root_config[bundle_name])
conf_spec.validate(self.root_config[bundle_name])
except InvalidConfigError as e:
e.args = ("Bundle: " + bundle_name,) + e.args
raise
@ -366,11 +366,11 @@ class ConfigManager():
def get_config_bundles(self, bundle_names):
"""
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:
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.
Returns:
@ -378,8 +378,8 @@ class ConfigManager():
"""
config_values = {}
if isinstance(bundle_names, dict):
for name, conf_def in bundle_names.items():
config_values[name] = self.get_config_bundle(name, conf_def)
for name, conf_spec in bundle_names.items():
config_values[name] = self.get_config_bundle(name, conf_spec)
else:
for name in bundle_names:
config_values[name] = self.get_config_bundle(name)
@ -419,4 +419,4 @@ def update_toml_message(filepath, message):
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