Last active
August 29, 2015 14:00
-
-
Save ThinkChaos/11292316 to your computer and use it in GitHub Desktop.
Confit Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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