mirror of
https://github.com/beetbox/beets.git
synced 2025-12-08 01:23:09 +01:00
flexattrs work for Albums
A second base class, LibModel, maintains a reference to the Library and should take care of database-related tasks like load and store. This is the beginning of the end of the terrible incongruity between Item and Album objects (only the latter had a library reference). More refactoring to come. One large side effect: Album objects no longer automatically store modifications. You have to call album.store(). Several places in the code assume otherwise; they need cleaning up. ResultIterator is now polymorphic (it takes a type parameter, which must be a subclass of LibModel).
This commit is contained in:
parent
abfad7b2a9
commit
276ce14dd2
3 changed files with 125 additions and 160 deletions
274
beets/library.py
274
beets/library.py
|
|
@ -318,12 +318,12 @@ class FlexModel(object):
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
if key.startswith('_'):
|
if key.startswith('_'):
|
||||||
return super(FlexModel, self).__getattr__(key)
|
raise AttributeError('model has no attribute {0!r}'.format(key))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return self[key]
|
return self[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(key)
|
raise AttributeError('no such field {0!r}'.format(key))
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
if key.startswith('_'):
|
if key.startswith('_'):
|
||||||
|
|
@ -331,17 +331,77 @@ class FlexModel(object):
|
||||||
else:
|
else:
|
||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
class Item(FlexModel):
|
class LibModel(FlexModel):
|
||||||
_fields = ITEM_FIELDS
|
"""A model base class that includes a reference to a Library object.
|
||||||
|
It knows how to load and store itself from the database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_table = None
|
||||||
|
"""The main SQLite table name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_flex_table = None
|
||||||
|
"""The flex field SQLite table name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, lib=None, **values):
|
||||||
|
self._lib = lib
|
||||||
|
super(LibModel, self).__init__(**values)
|
||||||
|
|
||||||
|
def store(self, store_id=None, store_all=False):
|
||||||
|
"""Save the object's metadata into the library database. If
|
||||||
|
store_id is specified, use it instead of the current id. If
|
||||||
|
store_all is true, save the entire record instead of just the
|
||||||
|
dirty fields.
|
||||||
|
"""
|
||||||
|
if store_id is None:
|
||||||
|
store_id = self.id
|
||||||
|
|
||||||
|
# Build assignments for query.
|
||||||
|
assignments = ''
|
||||||
|
subvars = []
|
||||||
|
for key in self._fields:
|
||||||
|
if (key != 'id') and (key in self._dirty or store_all):
|
||||||
|
assignments += key + '=?,'
|
||||||
|
value = getattr(item, key)
|
||||||
|
# Wrap path strings in buffers so they get stored
|
||||||
|
# "in the raw".
|
||||||
|
if key == 'path' and isinstance(value, str):
|
||||||
|
value = buffer(value)
|
||||||
|
subvars.append(value)
|
||||||
|
assignments = assignments[:-1] # Knock off last ,
|
||||||
|
|
||||||
|
with self._lib.transaction() as tx:
|
||||||
|
# Main table update.
|
||||||
|
if assignments:
|
||||||
|
query = 'UPDATE {0} SET {1} WHERE id=?'.format(
|
||||||
|
self._table, assignments
|
||||||
|
)
|
||||||
|
subvars.append(store_id)
|
||||||
|
tx.mutate(query, subvars)
|
||||||
|
|
||||||
|
# Flexible attributes.
|
||||||
|
for key, value in self._values_flex.items():
|
||||||
|
tx.mutate(
|
||||||
|
'INSERT INTO {0} '
|
||||||
|
'(entity_id, key, value) '
|
||||||
|
'VALUES (?, ?, ?);'.format(self._flex_table),
|
||||||
|
(store_id, key, value),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.clear_dirty()
|
||||||
|
|
||||||
|
class Item(LibModel):
|
||||||
|
_fields = ITEM_KEYS
|
||||||
|
_table = 'items'
|
||||||
|
_flex_table = 'item_attributes'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_path(cls, path):
|
def from_path(cls, path):
|
||||||
"""Creates a new item from the media file at the specified path.
|
"""Creates a new item from the media file at the specified path.
|
||||||
"""
|
"""
|
||||||
# Initiate with values that aren't read from files.
|
# Initiate with values that aren't read from files.
|
||||||
i = cls({
|
i = cls(album_id=None)
|
||||||
'album_id': None,
|
|
||||||
})
|
|
||||||
i.read(path)
|
i.read(path)
|
||||||
i.mtime = i.current_mtime() # Initial mtime.
|
i.mtime = i.current_mtime() # Initial mtime.
|
||||||
return i
|
return i
|
||||||
|
|
@ -829,9 +889,10 @@ class PathQuery(Query):
|
||||||
|
|
||||||
class ResultIterator(object):
|
class ResultIterator(object):
|
||||||
"""An iterator into an item query result set. The iterator lazily
|
"""An iterator into an item query result set. The iterator lazily
|
||||||
constructs Item objects that reflect database rows.
|
constructs LibModel objects that reflect database rows.
|
||||||
"""
|
"""
|
||||||
def __init__(self, rows, lib, query=None):
|
def __init__(self, model_class, rows, lib, query=None):
|
||||||
|
self.model_class = model_class
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.rowiter = iter(self.rows)
|
self.rowiter = iter(self.rows)
|
||||||
self.lib = lib
|
self.lib = lib
|
||||||
|
|
@ -844,15 +905,17 @@ class ResultIterator(object):
|
||||||
for row in self.rowiter: # Iterate until we get a hit.
|
for row in self.rowiter: # Iterate until we get a hit.
|
||||||
with self.lib.transaction() as tx:
|
with self.lib.transaction() as tx:
|
||||||
flex_rows = tx.query(
|
flex_rows = tx.query(
|
||||||
'SELECT * FROM item_attributes WHERE entity_id=?',
|
'SELECT * FROM {0} WHERE entity_id=?'.format(
|
||||||
|
self.model_class._flex_table
|
||||||
|
),
|
||||||
(row['id'],)
|
(row['id'],)
|
||||||
)
|
)
|
||||||
values = dict(row)
|
values = dict(row)
|
||||||
values.update({row['key']: row['value'] for row in flex_rows})
|
values.update({row['key']: row['value'] for row in flex_rows})
|
||||||
item = Item(**values)
|
obj = self.model_class(self.lib, **values)
|
||||||
if self.query and not self.query.match(item):
|
if self.query and not self.query.match(obj):
|
||||||
continue
|
continue
|
||||||
return item
|
return obj
|
||||||
raise StopIteration() # Reached the end of the DB rows.
|
raise StopIteration() # Reached the end of the DB rows.
|
||||||
|
|
||||||
# Regular expression for parse_query_part, below.
|
# Regular expression for parse_query_part, below.
|
||||||
|
|
@ -977,54 +1040,6 @@ def get_query(val, album=False):
|
||||||
raise ValueError('query must be None or have type Query or str')
|
raise ValueError('query must be None or have type Query or str')
|
||||||
|
|
||||||
|
|
||||||
# An abstract library.
|
|
||||||
|
|
||||||
class BaseAlbum(object):
|
|
||||||
"""Represents an album in the library, which in turn consists of a
|
|
||||||
collection of items in the library.
|
|
||||||
|
|
||||||
This base version just reflects the metadata of the album's items
|
|
||||||
and therefore isn't particularly useful. The items are referenced
|
|
||||||
by the record's album and artist fields. Implementations can add
|
|
||||||
album-level metadata or use distinct backing stores.
|
|
||||||
"""
|
|
||||||
def __init__(self, library, record):
|
|
||||||
super(BaseAlbum, self).__setattr__('_library', library)
|
|
||||||
super(BaseAlbum, self).__setattr__('_record', record)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
"""Get the value for an album attribute."""
|
|
||||||
if key in self._record:
|
|
||||||
return self._record[key]
|
|
||||||
else:
|
|
||||||
raise AttributeError('no such field %s' % key)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
"""Set the value of an album attribute, modifying each of the
|
|
||||||
album's items.
|
|
||||||
"""
|
|
||||||
if key in self._record:
|
|
||||||
# Reflect change in this object.
|
|
||||||
self._record[key] = value
|
|
||||||
# Modify items.
|
|
||||||
if key in ALBUM_KEYS_ITEM:
|
|
||||||
items = self._library.items(albumartist=self.albumartist,
|
|
||||||
album=self.album)
|
|
||||||
for item in items:
|
|
||||||
setattr(item, key, value)
|
|
||||||
self._library.store(item)
|
|
||||||
else:
|
|
||||||
super(BaseAlbum, self).__setattr__(key, value)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
"""Refresh this album's cached metadata from the library.
|
|
||||||
"""
|
|
||||||
items = self._library.items(artist=self.artist, album=self.album)
|
|
||||||
item = iter(items).next()
|
|
||||||
for key in ALBUM_KEYS_ITEM:
|
|
||||||
self._record[key] = getattr(item, key)
|
|
||||||
|
|
||||||
|
|
||||||
# The Library: interface to the database.
|
# The Library: interface to the database.
|
||||||
|
|
||||||
class Transaction(object):
|
class Transaction(object):
|
||||||
|
|
@ -1328,11 +1343,11 @@ class Library(object):
|
||||||
flexins = 'INSERT INTO item_attributes ' \
|
flexins = 'INSERT INTO item_attributes ' \
|
||||||
' (entity_id, key, value)' \
|
' (entity_id, key, value)' \
|
||||||
' VALUES (?, ?, ?, ?)'
|
' VALUES (?, ?, ?, ?)'
|
||||||
for key, value in item.flexattrs.items():
|
for key, value in item._values_flex.items():
|
||||||
if value is not None:
|
if value is not None:
|
||||||
tx.mutate(flexins, (new_id, key, value))
|
tx.mutate(flexins, (new_id, key, value))
|
||||||
|
|
||||||
item._clear_dirty()
|
item.clear_dirty()
|
||||||
item.id = new_id
|
item.id = new_id
|
||||||
self._memotable = {}
|
self._memotable = {}
|
||||||
return new_id
|
return new_id
|
||||||
|
|
@ -1344,9 +1359,8 @@ class Library(object):
|
||||||
if load_id is None:
|
if load_id is None:
|
||||||
load_id = item.id
|
load_id = item.id
|
||||||
stored_item = self.get_item(load_id)
|
stored_item = self.get_item(load_id)
|
||||||
item.update(stored_item.record)
|
item.update(dict(stored_item))
|
||||||
item.update(stored_item.flexattrs)
|
item.clear_dirty()
|
||||||
item._clear_dirty()
|
|
||||||
|
|
||||||
def store(self, item, store_id=None, store_all=False):
|
def store(self, item, store_id=None, store_all=False):
|
||||||
"""Save the item's metadata into the library database. If
|
"""Save the item's metadata into the library database. If
|
||||||
|
|
@ -1361,7 +1375,7 @@ class Library(object):
|
||||||
assignments = ''
|
assignments = ''
|
||||||
subvars = []
|
subvars = []
|
||||||
for key in ITEM_KEYS:
|
for key in ITEM_KEYS:
|
||||||
if (key != 'id') and (item.dirty[key] or store_all):
|
if (key != 'id') and (key in item._dirty or store_all):
|
||||||
assignments += key + '=?,'
|
assignments += key + '=?,'
|
||||||
value = getattr(item, key)
|
value = getattr(item, key)
|
||||||
# Wrap path strings in buffers so they get stored
|
# Wrap path strings in buffers so they get stored
|
||||||
|
|
@ -1382,10 +1396,10 @@ class Library(object):
|
||||||
flexins = 'INSERT INTO item_attributes ' \
|
flexins = 'INSERT INTO item_attributes ' \
|
||||||
' (entity_id, key, value)' \
|
' (entity_id, key, value)' \
|
||||||
' VALUES (?, ?, ?)'
|
' VALUES (?, ?, ?)'
|
||||||
for key, value in item.flexattrs.items():
|
for key, value in item._values_flex.items():
|
||||||
tx.mutate(flexins, (store_id, key, value))
|
tx.mutate(flexins, (store_id, key, value))
|
||||||
|
|
||||||
item._clear_dirty()
|
item.clear_dirty()
|
||||||
self._memotable = {}
|
self._memotable = {}
|
||||||
|
|
||||||
def remove(self, item, delete=False, with_album=True):
|
def remove(self, item, delete=False, with_album=True):
|
||||||
|
|
@ -1479,18 +1493,7 @@ class Library(object):
|
||||||
subvals,
|
subvals,
|
||||||
)
|
)
|
||||||
|
|
||||||
if where:
|
return ResultIterator(Album, rows, self, None if where else query)
|
||||||
# Fast query.
|
|
||||||
return [Album(self, dict(res)) for res in rows]
|
|
||||||
else:
|
|
||||||
# Slow query.
|
|
||||||
# FIXME both should be iterators.
|
|
||||||
out = []
|
|
||||||
for row in rows:
|
|
||||||
album = Album(self, dict(res))
|
|
||||||
if query.match(album):
|
|
||||||
out.append(album)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def items(self, query=None, artist=None, album=None, title=None):
|
def items(self, query=None, artist=None, album=None, title=None):
|
||||||
"""Returns a sequence of the items matching the given artist,
|
"""Returns a sequence of the items matching the given artist,
|
||||||
|
|
@ -1519,12 +1522,7 @@ class Library(object):
|
||||||
subvals
|
subvals
|
||||||
)
|
)
|
||||||
|
|
||||||
if where:
|
return ResultIterator(Item, rows, self, None if where else query)
|
||||||
# Fast query.
|
|
||||||
return ResultIterator(rows, self)
|
|
||||||
else:
|
|
||||||
# Slow query.
|
|
||||||
return ResultIterator(rows, self, query)
|
|
||||||
|
|
||||||
|
|
||||||
# Convenience accessors.
|
# Convenience accessors.
|
||||||
|
|
@ -1550,13 +1548,11 @@ class Library(object):
|
||||||
if album_id is None:
|
if album_id is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
with self.transaction() as tx:
|
albums = self.albums(MatchQuery('id', album_id))
|
||||||
rows = tx.query(
|
try:
|
||||||
'SELECT * FROM albums WHERE id=?',
|
return albums.next()
|
||||||
(album_id,)
|
except StopIteration:
|
||||||
)
|
return None
|
||||||
if rows:
|
|
||||||
return Album(self, dict(rows[0]))
|
|
||||||
|
|
||||||
def add_album(self, items):
|
def add_album(self, items):
|
||||||
"""Create a new album in the database with metadata derived
|
"""Create a new album in the database with metadata derived
|
||||||
|
|
@ -1587,71 +1583,33 @@ class Library(object):
|
||||||
self.store(item)
|
self.store(item)
|
||||||
|
|
||||||
# Construct the new Album object.
|
# Construct the new Album object.
|
||||||
record = {}
|
album_values['id'] = album_id
|
||||||
for key in ALBUM_KEYS:
|
album = Album(self, **album_values)
|
||||||
# Unset (non-item) fields default to None.
|
|
||||||
record[key] = album_values.get(key)
|
|
||||||
record['id'] = album_id
|
|
||||||
album = Album(self, record)
|
|
||||||
|
|
||||||
return album
|
return album
|
||||||
|
|
||||||
class Album(BaseAlbum):
|
class Album(LibModel):
|
||||||
"""Provides access to information about albums stored in a
|
"""Provides access to information about albums stored in a
|
||||||
library. Reflects the library's "albums" table, including album
|
library. Reflects the library's "albums" table, including album
|
||||||
art.
|
art.
|
||||||
"""
|
"""
|
||||||
def __init__(self, lib, record):
|
_fields = ALBUM_KEYS
|
||||||
# Decode Unicode paths in database.
|
_table = 'album'
|
||||||
if 'artpath' in record and isinstance(record['artpath'], unicode):
|
_flex_table = 'album_attributes'
|
||||||
record['artpath'] = bytestring_path(record['artpath'])
|
|
||||||
super(Album, self).__init__(lib, record)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
"""Set the value of an album attribute."""
|
"""Set the value of an album attribute."""
|
||||||
if key == 'id':
|
if key == 'artpath':
|
||||||
raise AttributeError("can't modify album id")
|
if isinstance(value, unicode):
|
||||||
|
|
||||||
elif key in ALBUM_KEYS:
|
|
||||||
# Make sure paths are bytestrings.
|
|
||||||
if key == 'artpath' and isinstance(value, unicode):
|
|
||||||
value = bytestring_path(value)
|
value = bytestring_path(value)
|
||||||
|
elif isinstance(value, buffer):
|
||||||
# Reflect change in this object.
|
value = bytes(value)
|
||||||
self._record[key] = value
|
super(Album, self).__setitem__(key, value)
|
||||||
|
|
||||||
# Store art path as a buffer.
|
|
||||||
if key == 'artpath' and isinstance(value, str):
|
|
||||||
value = buffer(value)
|
|
||||||
|
|
||||||
# Change album table.
|
|
||||||
sql = 'UPDATE albums SET %s=? WHERE id=?' % key
|
|
||||||
with self._library.transaction() as tx:
|
|
||||||
tx.mutate(sql, (value, self.id))
|
|
||||||
|
|
||||||
# Possibly make modification on items as well.
|
|
||||||
if key in ALBUM_KEYS_ITEM:
|
|
||||||
for item in self.items():
|
|
||||||
setattr(item, key, value)
|
|
||||||
self._library.store(item)
|
|
||||||
|
|
||||||
else:
|
|
||||||
object.__setattr__(self, key, value)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
value = super(Album, self).__getattr__(key)
|
|
||||||
|
|
||||||
# Unwrap art path from buffer object.
|
|
||||||
if key == 'artpath' and isinstance(value, buffer):
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
"""Returns an iterable over the items associated with this
|
"""Returns an iterable over the items associated with this
|
||||||
album.
|
album.
|
||||||
"""
|
"""
|
||||||
return self._library.items(MatchQuery('album_id', self.id))
|
return self._lib.items(MatchQuery('album_id', self.id))
|
||||||
|
|
||||||
def remove(self, delete=False, with_items=True):
|
def remove(self, delete=False, with_items=True):
|
||||||
"""Removes this album and all its associated items from the
|
"""Removes this album and all its associated items from the
|
||||||
|
|
@ -1666,11 +1624,11 @@ class Album(BaseAlbum):
|
||||||
if artpath:
|
if artpath:
|
||||||
util.remove(artpath)
|
util.remove(artpath)
|
||||||
|
|
||||||
with self._library.transaction() as tx:
|
with self._lib.transaction() as tx:
|
||||||
if with_items:
|
if with_items:
|
||||||
# Remove items.
|
# Remove items.
|
||||||
for item in self.items():
|
for item in self.items():
|
||||||
self._library.remove(item, delete, False)
|
self._lib.remove(item, delete, False)
|
||||||
|
|
||||||
# Remove album from database.
|
# Remove album from database.
|
||||||
tx.mutate(
|
tx.mutate(
|
||||||
|
|
@ -1701,22 +1659,22 @@ class Album(BaseAlbum):
|
||||||
# Prune old path when moving.
|
# Prune old path when moving.
|
||||||
if not copy:
|
if not copy:
|
||||||
util.prune_dirs(os.path.dirname(old_art),
|
util.prune_dirs(os.path.dirname(old_art),
|
||||||
self._library.directory)
|
self._lib.directory)
|
||||||
|
|
||||||
def move(self, copy=False, basedir=None):
|
def move(self, copy=False, basedir=None):
|
||||||
"""Moves (or copies) all items to their destination. Any album
|
"""Moves (or copies) all items to their destination. Any album
|
||||||
art moves along with them. basedir overrides the library base
|
art moves along with them. basedir overrides the library base
|
||||||
directory for the destination.
|
directory for the destination.
|
||||||
"""
|
"""
|
||||||
basedir = basedir or self._library.directory
|
basedir = basedir or self._lib.directory
|
||||||
|
|
||||||
# Move items.
|
# Move items.
|
||||||
items = list(self.items())
|
items = list(self.items())
|
||||||
for item in items:
|
for item in items:
|
||||||
self._library.move(item, copy, basedir=basedir, with_album=False)
|
self._lib.move(item, copy, basedir=basedir, with_album=False)
|
||||||
|
|
||||||
# Move art.
|
# Move art.
|
||||||
self.move_art(copy)
|
self.move_art(lib, copy)
|
||||||
|
|
||||||
def item_dir(self):
|
def item_dir(self):
|
||||||
"""Returns the directory containing the album's first item,
|
"""Returns the directory containing the album's first item,
|
||||||
|
|
@ -1743,7 +1701,7 @@ class Album(BaseAlbum):
|
||||||
filename_tmpl = Template(beets.config['art_filename'].get(unicode))
|
filename_tmpl = Template(beets.config['art_filename'].get(unicode))
|
||||||
subpath = format_for_path(self.evaluate_template(filename_tmpl))
|
subpath = format_for_path(self.evaluate_template(filename_tmpl))
|
||||||
subpath = util.sanitize_path(subpath,
|
subpath = util.sanitize_path(subpath,
|
||||||
replacements=self._library.replacements)
|
replacements=self._lib.replacements)
|
||||||
subpath = bytestring_path(subpath)
|
subpath = bytestring_path(subpath)
|
||||||
|
|
||||||
_, ext = os.path.splitext(image)
|
_, ext = os.path.splitext(image)
|
||||||
|
|
@ -1783,8 +1741,8 @@ class Album(BaseAlbum):
|
||||||
"""
|
"""
|
||||||
# Get template field values.
|
# Get template field values.
|
||||||
mapping = {}
|
mapping = {}
|
||||||
for key in ALBUM_KEYS:
|
for key, value in dict(self).items():
|
||||||
mapping[key] = format_for_path(getattr(self, key), key)
|
mapping[key] = format_for_path(value, key)
|
||||||
|
|
||||||
mapping['artpath'] = displayable_path(mapping['artpath'])
|
mapping['artpath'] = displayable_path(mapping['artpath'])
|
||||||
mapping['path'] = displayable_path(self.item_dir())
|
mapping['path'] = displayable_path(self.item_dir())
|
||||||
|
|
@ -1800,6 +1758,14 @@ class Album(BaseAlbum):
|
||||||
# Perform substitution.
|
# Perform substitution.
|
||||||
return template.substitute(mapping, funcs)
|
return template.substitute(mapping, funcs)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""Refresh this album's cached metadata from the library.
|
||||||
|
"""
|
||||||
|
items = self._lib.items(artist=self.artist, album=self.album)
|
||||||
|
item = iter(items).next()
|
||||||
|
for key in ALBUM_KEYS_ITEM:
|
||||||
|
self[key] = item[key]
|
||||||
|
|
||||||
|
|
||||||
# Default path template resources.
|
# Default path template resources.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1162,8 +1162,9 @@ def modify_items(lib, mods, query, write, move, album, confirm):
|
||||||
else:
|
else:
|
||||||
lib.move(obj)
|
lib.move(obj)
|
||||||
|
|
||||||
# When modifying items, we have to store them to the database.
|
if album:
|
||||||
if not album:
|
obj.store()
|
||||||
|
else:
|
||||||
lib.store(obj)
|
lib.store(obj)
|
||||||
|
|
||||||
# Apply tags if requested.
|
# Apply tags if requested.
|
||||||
|
|
|
||||||
|
|
@ -70,12 +70,10 @@ def compile_inline(python_code, album):
|
||||||
is_expr = True
|
is_expr = True
|
||||||
|
|
||||||
def _dict_for(obj):
|
def _dict_for(obj):
|
||||||
|
out = dict(obj)
|
||||||
if album:
|
if album:
|
||||||
out = dict(obj._record)
|
|
||||||
out['items'] = list(obj.items())
|
out['items'] = list(obj.items())
|
||||||
return out
|
return out
|
||||||
else:
|
|
||||||
return dict(obj)
|
|
||||||
|
|
||||||
if is_expr:
|
if is_expr:
|
||||||
# For expressions, just evaluate and return the result.
|
# For expressions, just evaluate and return the result.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue