Skip to content

Instantly share code, notes, and snippets.

@ThinkChaos
Last active August 29, 2015 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThinkChaos/11292316 to your computer and use it in GitHub Desktop.
Save ThinkChaos/11292316 to your computer and use it in GitHub Desktop.
Confit Validation
from confit import BASESTRING, NotFoundError, PY3, TYPE_TYPES
DICT_ITEMS = dict.items if PY3 else dict.iteritems
class Value(object):
def __init__(self, check, default=None):
self.check, self.default = check, default
if default is None and not self._is_check(check):
self.check, self.default = None, check
def _is_check(self, check):
if check is None:
return False
if callable(check) or isinstance(check, TYPE_TYPES) or isinstance(check, set):
return True
if isinstance(check, tuple):
# Tuple of form: (callable, [args, [kwargs]]) or (callable, [kwargs])
# Save try/except blocks
check = dict(enumerate(check))
if not callable(check.get(0)):
return False
if isinstance(check.get(1), list):
return check.get(2) is None or isinstance(check.get(2), dict)
if isinstance(check.get(1), dict):
return check.get(2) is None or isinstance(check.get(2), list)
return False
class ValidConfig(object):
def __init__(self, config, valid):
self.valid = valid
self._validate(config)
self._complete_from(config)
def _validate(self, config):
for key, val in DICT_ITEMS(self.valid):
# when we're dealing with a hierarchy, go deeper
if isinstance(val, dict):
cfg = self._get_from_config(config, key)
setattr(self, key, ValidConfig(cfg, val))
continue
if not isinstance(val, Value):
try:
val = Value(*val)
except TypeError:
val = Value(val)
cfg = self._get_from(config, key, required=val.default is None)
if cfg is None:
# No value in config, using default
setattr(self, key, val.default)
elif self._check(val.check, cfg):
setattr(self, key, cfg)
elif val.default is not None:
setattr(self, key, val.default)
else:
raise ConfigValueError(key)
def _check(self, check, cfg):
# TODO: add support for more check types
if check is None:
return True
if isinstance(check, type):
return isinstance(cfg, check)
if callable(check):
return check(cfg)
if isinstance(check, tuple):
check = dict(enumerate(check))
func, args, kwargs = (
check.get(0, lambda x: False),
check.get(1, []),
check.get(2, {}),
)
if not kwargs and isinstance(args, dict):
args, kwargs = [], args
return func(attr, *args, **kwargs)
def _complete_from(self, config):
""" Keep config items that are not subject to checks. """
for key, val in config.items():
if not hasattr(self, key):
setattr(self, key, val.get())
def _get_from(self, config, key, required):
""" Get value from config or default when specified. """
try:
return config[key].get()
except NotFoundError:
if required:
raise
import confit
confit.ConfigView.validate = lambda config, valid: ValidConfig(config, valid)
if __name__ == '__main__':
config = confit.Configuration('ConfitExample', 'example')
valid = config.validate({
'directory': BASESTRING,
'import_write': Value(lambda x: False, 'YES (check failed)')
})
# Keeps values without checks
assert(valid.library == 'library.db')
# Passed check keeps config value
assert(valid.directory == '~/Music')
# Failed check uses default
assert(valid.import_write == 'YES (check failed)')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment