confit: fix unicode and numeric validation

This commit is contained in:
Adrian Sampson 2012-08-30 23:00:01 -07:00
parent fe2a68722f
commit 8bc563fafc
3 changed files with 55 additions and 19 deletions

View file

@ -664,8 +664,8 @@ def _raw_main(args, configfh):
config['library'].get(confit.as_filename),
config['directory'].get(confit.as_filename),
config['paths'].get(dict), # FIXME
config['art_filename'].get(basestring),
config['timeout'].get(float), # FIXME int okay
config['art_filename'].get(unicode),
config['timeout'].get(confit.as_number),
config['replace'].get(dict),
)
except sqlite3.OperationalError:

View file

@ -840,9 +840,9 @@ def list_func(lib, config, opts, args):
if not fmt:
# If no format is specified, fall back to a default.
if opts.album:
fmt = config['list_format_album'].get(basestring)
fmt = config['list_format_album'].get(unicode)
else:
fmt = config['list_format_item'].get(basestring)
fmt = config['list_format_item'].get(unicode)
list_items(lib, decargs(args), opts.album, opts.path, fmt)
list_cmd.func = list_func
default_commands.append(list_cmd)

View file

@ -35,6 +35,7 @@ DEFAULT_FILENAME = 'config_default.yaml'
PY3 = sys.version_info[0] == 3
STRING = str if PY3 else unicode
NUMERIC_TYPES = [int, float] if PY3 else [int, float, long]
def iter_first(sequence):
"""Get the first element from an iterable or raise a ValueError if
@ -126,8 +127,8 @@ class ConfigView(object):
# Check type of value.
if not isinstance(value, typ):
raise ConfigTypeError(
"{0} must by of type {1}, not {2}".format(
self.name, typ, type(value)
"{0} must be of type {1}, not {2}".format(
self.name, typ.__name__, type(value).__name__
)
)
@ -190,8 +191,11 @@ class ConfigView(object):
try:
cur_keys = dic.keys()
except AttributeError:
raise ConfigTypeError('%s must be a dict, not %s' %
(self.name, STRING(type(dic))))
raise ConfigTypeError(
'{0} must be a dict, not {1}'.format(
self.name, type(dic).__name__
)
)
keys.update(cur_keys)
return keys
@ -224,8 +228,11 @@ class ConfigView(object):
try:
it = iter(collection)
except TypeError:
raise ConfigTypeError('%s must be an iterable, not %s' %
(self.name, STRING(type(collection))))
raise ConfigTypeError(
'{0} must be an iterable, not {1}'.format(
self.name, type(collection).__name__
)
)
for value in it:
yield value
@ -275,9 +282,11 @@ class Subview(ConfigView):
continue
except TypeError:
# Not subscriptable.
raise ConfigTypeError("%s must be a collection, not %s" %
(self.parent.name,
STRING(type(collection))))
raise ConfigTypeError(
"{0} must be a collection, not {1}".format(
self.parent.name, type(collection).__name__
)
)
yield value
@property
@ -358,6 +367,38 @@ def as_choice(choices):
return value
return f
def as_number(view, value):
"""Ensure that a value is of numeric type."""
for typ in NUMERIC_TYPES:
if isinstance(value, typ):
return value
raise ConfigTypeError(
'{0} must be numeric, not {1}'.format(
view.name, type(value).__name__
)
)
# YAML.
class Loader(yaml.SafeLoader):
"""A customized YAML safe loader that reads all strings as Unicode
objects.
"""
def _construct_unicode(self, node):
return self.construct_scalar(node)
Loader.add_constructor('tag:yaml.org,2002:str', Loader._construct_unicode)
def load_yaml(filename):
"""Read a YAML document from a file. If the file cannot be read or
parsed, a ConfigReadError is raised.
"""
try:
with open(filename, 'r') as f:
return yaml.load(f, Loader=Loader)
except (IOError, yaml.error.YAMLError) as exc:
raise ConfigReadError(filename, exc)
# Main interface.
@ -416,12 +457,7 @@ class Configuration(RootView):
"""
self.sources = []
for filename in self._filenames():
try:
with open(filename, 'r') as f:
data = yaml.load(f)
except (IOError, yaml.error.YAMLError) as exc:
raise ConfigReadError(filename, exc)
self.sources.append(data)
self.sources.append(load_yaml(filename))
def add_args(self, namespace):
"""Add parsed command-line arguments, generated by a library