diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 3901107f3..63a668601 100644 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -390,12 +390,7 @@ class Model(object): """Get a mapping containing all values on this object formatted as human-readable strings. """ - # In the future, this could be made "lazy" to avoid computing - # fields unnecessarily. - out = {} - for key in self.keys(True): - out[key] = self._get_formatted(key, for_path) - return out + return FormattedMapping(self, for_path) def evaluate_template(self, template, for_path=False): """Evaluate a template (a string or a `Template` object) using @@ -430,6 +425,30 @@ class Model(object): return string +class FormattedMapping(object): + """A `dict`-like formatted view of a model. + + The accessor ``mapping[key]`` returns the formated version of + ``model[key]``. The formatting is handled by `model._format()`. + """ + # TODO Move all formatting logic here + # TODO Add caching + + def __init__(self, model, for_path=False): + self.for_path = for_path + self.model = model + self.model_keys = model.keys(True) + + def __getitem__(self, key): + if key in self.model_keys: + return self.model._get_formatted(key, self.for_path) + else: + raise KeyError(key) + + def __contains__(self, key): + return key in self.model_keys + + # Database controller and supporting interfaces. class Results(object): diff --git a/beets/library.py b/beets/library.py index 1df2b337e..3a2ccd62c 100644 --- a/beets/library.py +++ b/beets/library.py @@ -485,23 +485,7 @@ class Item(LibModel): """Get a mapping containing string-formatted values from either this item or the associated album, if any. """ - mapping = super(Item, self)._formatted_mapping(for_path) - - # Merge in album-level fields. - album = self.get_album() - if album: - for key in album.keys(True): - if key in Album.item_keys or key not in self._fields.keys(): - mapping[key] = album._get_formatted(key, for_path) - - # Use the album artist if the track artist is not set and - # vice-versa. - if not mapping['artist']: - mapping['artist'] = mapping['albumartist'] - if not mapping['albumartist']: - mapping['albumartist'] = mapping['artist'] - - return mapping + return FormattedItemMapping(self, for_path) def destination(self, fragment=False, basedir=None, platform=None, path_formats=None): @@ -573,6 +557,48 @@ class Item(LibModel): return normpath(os.path.join(basedir, subpath)) +class FormattedItemMapping(dbcore.db.FormattedMapping): + """A `dict`-like formatted view of an item that inherits album fields. + + The accessor ``mapping[key]`` returns the formated version of either + ``item[key]`` or ``album[key]``. Here `album` is the album + associated to `item` if it exists. + """ + # FIXME This class should be in the same module as `FormattedMapping` + + def __init__(self, item, for_path=False): + self.for_path = for_path + self.model = item + self.model_keys = item.keys(True) + self.album = item.get_album() + self.album_keys = [] + if self.album: + for key in self.album.keys(True): + if key in Album.item_keys or key not in item._fields.keys(): + self.album_keys.append(key) + + def get(self, key): + if key in self.album_keys: + return self.album._get_formatted(key, self.for_path) + elif key in self.model_keys: + return self.model._get_formatted(key, self.for_path) + else: + raise KeyError(key) + + def __getitem__(self, key): + value = self.get(key) + + if key == 'artist' and not value: + return self.get('albumartist') + if key == 'albumartist' and not value: + return self.get('artist') + + return value + + def __contains__(self, key): + return key in self.model_keys or key in self.album_keys + + class Album(LibModel): """Provides access to information about albums stored in a library. Reflects the library's "albums" table, including album