first stab at computed fields

The idea is to have uniform access to plugin-provided (computed) fields of
items and albums. This also permits queries over these computed fields.
This commit is contained in:
Adrian Sampson 2013-12-24 14:52:40 -08:00
parent b87de00ba4
commit 13b1e0f115
2 changed files with 72 additions and 5 deletions

View file

@ -301,6 +301,12 @@ class FlexModel(object):
"""
return list(self._fields) + self._values_flex.keys()
def items(self):
"""Iterate over (key, value) pairs that this object contains.
"""
for key in self.keys():
yield key, self[key]
def get(self, key, default=None):
"""Get the value for a given key or `default` if it does not
exist.
@ -311,10 +317,9 @@ class FlexModel(object):
return default
def __contains__(self, key):
"""Determine whether `key` is a fixed or flex attribute on this
object.
"""Determine whether `key` is an attribute on this object.
"""
return key in self._fields or key in self._values_flex
return key in self.keys()
# Convenient attribute access.
@ -335,7 +340,39 @@ class FlexModel(object):
self[key] = value
class LibModel(FlexModel):
class PluggableModel(FlexModel):
"""A base model class that adds plugin-provided fields to FlexModel.
"""
@classmethod
def _getters(cls):
"""Return a mapping from field names to getter functions.
"""
# We could cache this if it becomes a performance problem to
# gather the getter mapping every time.
raise NotImplementedError()
def __getitem__(self, key):
"""Get the value for a field, which may be a fixed attribute, a
flexible attribute, or a plugin-provided attribute.
"""
getters = self._getters()
if key in getters:
return getters[key](self)
return super(PluggableModel, self).__getitem__(key)
def keys(self, computed=False):
"""Get the fixed, flexible, and plugin-provided field names for
this object. The `computed` parameter controls whether computed
(plugin-provided) fields are included in the key list.
"""
base_keys = super(PluggableModel, self).keys()
if computed:
return base_keys + self._getters().keys()
else:
return base_keys
class LibModel(PluggableModel):
"""A model base class that includes a reference to a Library object.
It knows how to load and store itself from the database.
"""
@ -440,6 +477,10 @@ class Item(LibModel):
_flex_table = 'item_attributes'
_search_fields = ITEM_DEFAULT_FIELDS
@classmethod
def _getters(cls):
return plugins.item_field_getters()
@classmethod
def from_path(cls, path):
"""Creates a new item from the media file at the specified path.
@ -777,6 +818,10 @@ class Album(LibModel):
_flex_table = 'album_attributes'
_search_fields = ALBUM_DEFAULT_FIELDS
@classmethod
def _getters(cls):
return plugins.album_field_getters()
def __setitem__(self, key, value):
"""Set the value of an album attribute."""
if key == 'artpath':

View file

@ -56,7 +56,7 @@ class BeetsPlugin(object):
commands that should be added to beets' CLI.
"""
return ()
def queries(self):
"""Should return a dict mapping prefixes to PluginQuery
subclasses.
@ -346,6 +346,28 @@ def import_stages():
return stages
# New-style (lazy) plugin-provided fields.
def item_field_getters():
"""Get a dictionary mapping field names to unary functions that
compute the field's value.
"""
funcs = {}
for plugin in find_plugins():
if plugin.template_fields:
funcs.update(plugin.template_fields)
return funcs
def album_field_getters():
"""As above, for album fields.
"""
funcs = {}
for plugin in find_plugins():
if plugin.album_template_fields:
funcs.update(plugin.album_template_fields)
return funcs
# Event dispatch.
def event_handlers():