diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 152a2d6a1..dff2fb48c 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -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: diff --git a/beets/ui/commands.py b/beets/ui/commands.py index c27f2223c..65fe75fb0 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -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) diff --git a/beets/util/confit.py b/beets/util/confit.py index 147e4908b..c66355147 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -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