diff --git a/beets/library.py b/beets/library.py index e77769548..1b991cbe1 100644 --- a/beets/library.py +++ b/beets/library.py @@ -69,6 +69,7 @@ ALBUM_FIELDS = [ ('album', 'text'), ('artpath', 'text'), ] +ALBUM_KEYS = [f[0] for f in ALBUM_FIELDS] # Default search fields for various granularities. ARTIST_DEFAULT_FIELDS = ('artist',) @@ -206,8 +207,7 @@ class Item(object): def __getattr__(self, key): """If key is an item attribute (i.e., a column in the database), - returns the record entry for that key. Otherwise, performs an - ordinary getattr. + returns the record entry for that key. """ if key in ITEM_KEYS: return self.record[key] @@ -528,6 +528,31 @@ class ResultIterator(object): return Item(row) +# Album information proxy objects. + +class AlbumInfo(object): + """Provides access to information about albums stored in a library. + """ + def __init__(self, library, album, artist): + self._library = library + self._album = album + self._artist = artist + + def __getattr__(self, key): + """Get an album field's value.""" + if key in ALBUM_KEYS: + return self._library._album_get(self._album, self._artist, key) + else: + return getattr(self, key) + + def __setattr__(self, key, value): + """Set an album field.""" + if key in ALBUM_KEYS: + self._library._album_set(self._album, self._artist, key, value) + else: + super(AlbumInfo, self).__setattr__(key, value) + + # An abstract library. class BaseLibrary(object): @@ -651,6 +676,28 @@ class BaseLibrary(object): return sorted(out, compare) + # Album information. + # Provides access to information about items at an album + # granularity. AlbumInfo proxy objects are used to access fields; + # they invoke _album_get and _album_set. + + def albuminfo(self, artist, album): + """Given an artist and album name, return an AlbumInfo proxy + object whose attributes correspond to information about the + album. + """ + return AlbumInfo(self, artist, album) + + def _album_get(self, artist, album, key): + """For the album specified, returns the value associated with + the key.""" + raise NotImplementedError() + + def _album_set(self, artist, album, key, value): + """Sets the indicated album's value for key.""" + raise NotImplementedError() + + # Concrete DB-backed library. class Library(BaseLibrary): diff --git a/test/test_db.py b/test/test_db.py index 8ddc54a26..48a29ef05 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -321,6 +321,23 @@ class MigrationTest(unittest.TestCase): except sqlite3.OperationalError: self.fail("select failed") +class AlbumInfoTest(unittest.TestCase): + def setUp(self): + self.lib = beets.library.Library(':memory:') + self.i = item() + self.lib.add(self.i) + + def test_albuminfo_reflects_metadata(self): + ai = self.lib.albuminfo(self.i.artist, self.i.album) + self.assertEqual(ai.artist, self.i.artist) + self.assertEqual(ai.album, self.i.album) + + def test_albuminfo_stores_art(self): + ai = self.lib.albuminfo(self.i.artist, self.i.album) + ai.artpath = '/my/great/art' + new_ai = self.lib.albuminfo(self.i.artist, self.i.album) + self.assertEqual(new_ai.artpath, '/my/great/art') + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)