diff --git a/beets/library.py b/beets/library.py index e4fbd1d49..40051c354 100644 --- a/beets/library.py +++ b/beets/library.py @@ -794,6 +794,10 @@ class Album(LibModel): _search_fields = ('album', 'albumartist', 'genre') + _types = { + 'path': PathType(), + } + _sorts = { 'albumartist': SmartArtistSort, 'artist': SmartArtistSort, diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index e09043cbb..02a7a9478 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -839,8 +839,8 @@ def _setup(options, lib=None): if lib is None: lib = _open_library(config) plugins.send("library_opened", lib=lib) - library.Item._types = plugins.types(library.Item) - library.Album._types = plugins.types(library.Album) + library.Item._types.update(plugins.types(library.Item)) + library.Album._types.update(plugins.types(library.Album)) return subcommands, plugins, lib diff --git a/test/helper.py b/test/helper.py index 64db9e8b1..e929eecff 100644 --- a/test/helper.py +++ b/test/helper.py @@ -199,8 +199,11 @@ class TestHelper(object): beets.config['plugins'] = plugins beets.plugins.load_plugins(plugins) beets.plugins.find_plugins() - Item._types = beets.plugins.types(Item) - Album._types = beets.plugins.types(Album) + # Take a backup of the original _types to restore when unloading + Item._original_types = dict(Item._types) + Album._original_types = dict(Album._types) + Item._types.update(beets.plugins.types(Item)) + Album._types.update(beets.plugins.types(Album)) def unload_plugins(self): """Unload all plugins and remove the from the configuration. @@ -209,8 +212,8 @@ class TestHelper(object): beets.config['plugins'] = [] beets.plugins._classes = set() beets.plugins._instances = {} - Item._types = {} - Album._types = {} + Item._types = Item._original_types + Album._types = Album._original_types def create_importer(self, item_count=1, album_count=1): """Create files to import and return corresponding session. diff --git a/test/test_query.py b/test/test_query.py index c76ddf11b..55450d0ad 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -59,9 +59,12 @@ class AnyFieldQueryTest(_common.LibTestCase): class AssertsMixin(object): - def assert_matched(self, results, titles): + def assert_items_matched(self, results, titles): self.assertEqual([i.title for i in results], titles) + def assert_albums_matched(self, results, albums): + self.assertEqual([a.album for a in results], albums) + # A test case class providing a library with some dummy data and some # assertions involving that data. @@ -89,8 +92,8 @@ class DummyDataTestCase(_common.TestCase, AssertsMixin): self.lib.add(item) self.lib.add_album(items[:2]) - def assert_matched_all(self, results): - self.assert_matched(results, [ + def assert_items_matched_all(self, results): + self.assert_items_matched(results, [ 'foo bar', 'baz qux', 'beets 4 eva', @@ -101,72 +104,72 @@ class GetTest(DummyDataTestCase): def test_get_empty(self): q = '' results = self.lib.items(q) - self.assert_matched_all(results) + self.assert_items_matched_all(results) def test_get_none(self): q = None results = self.lib.items(q) - self.assert_matched_all(results) + self.assert_items_matched_all(results) def test_get_one_keyed_term(self): q = 'title:qux' results = self.lib.items(q) - self.assert_matched(results, ['baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_get_one_keyed_regexp(self): q = r'artist::t.+r' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_get_one_unkeyed_term(self): q = 'three' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_get_one_unkeyed_regexp(self): q = r':x$' results = self.lib.items(q) - self.assert_matched(results, ['baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_get_no_matches(self): q = 'popebear' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) def test_invalid_key(self): q = 'pope:bear' results = self.lib.items(q) # Matches nothing since the flexattr is not present on the # objects. - self.assert_matched(results, []) + self.assert_items_matched(results, []) def test_term_case_insensitive(self): q = 'oNE' results = self.lib.items(q) - self.assert_matched(results, ['foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_regexp_case_sensitive(self): q = r':oNE' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) q = r':one' results = self.lib.items(q) - self.assert_matched(results, ['foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_term_case_insensitive_with_key(self): q = 'artist:thrEE' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_key_case_insensitive(self): q = 'ArTiST:three' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_unkeyed_term_matches_multiple_columns(self): q = 'baz' results = self.lib.items(q) - self.assert_matched(results, [ + self.assert_items_matched(results, [ 'foo bar', 'baz qux', ]) @@ -174,7 +177,7 @@ class GetTest(DummyDataTestCase): def test_unkeyed_regexp_matches_multiple_columns(self): q = r':z$' results = self.lib.items(q) - self.assert_matched(results, [ + self.assert_items_matched(results, [ 'foo bar', 'baz qux', ]) @@ -182,41 +185,41 @@ class GetTest(DummyDataTestCase): def test_keyed_term_matches_only_one_column(self): q = 'title:baz' results = self.lib.items(q) - self.assert_matched(results, ['baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_keyed_regexp_matches_only_one_column(self): q = r'title::baz' results = self.lib.items(q) - self.assert_matched(results, [ + self.assert_items_matched(results, [ 'baz qux', ]) def test_multiple_terms_narrow_search(self): q = 'qux baz' results = self.lib.items(q) - self.assert_matched(results, [ + self.assert_items_matched(results, [ 'baz qux', ]) def test_multiple_regexps_narrow_search(self): q = r':baz :qux' results = self.lib.items(q) - self.assert_matched(results, ['baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_mixed_terms_regexps_narrow_search(self): q = r':baz qux' results = self.lib.items(q) - self.assert_matched(results, ['baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_single_year(self): q = 'year:2001' results = self.lib.items(q) - self.assert_matched(results, ['foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_year_range(self): q = 'year:2000..2002' results = self.lib.items(q) - self.assert_matched(results, [ + self.assert_items_matched(results, [ 'foo bar', 'baz qux', ]) @@ -224,22 +227,22 @@ class GetTest(DummyDataTestCase): def test_singleton_true(self): q = 'singleton:true' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_singleton_false(self): q = 'singleton:false' results = self.lib.items(q) - self.assert_matched(results, ['foo bar', 'baz qux']) + self.assert_items_matched(results, ['foo bar', 'baz qux']) def test_compilation_true(self): q = 'comp:true' results = self.lib.items(q) - self.assert_matched(results, ['foo bar', 'baz qux']) + self.assert_items_matched(results, ['foo bar', 'baz qux']) def test_compilation_false(self): q = 'comp:false' results = self.lib.items(q) - self.assert_matched(results, ['beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_unknown_field_name_no_results(self): q = 'xyzzy:nonsense' @@ -266,7 +269,7 @@ class GetTest(DummyDataTestCase): q = u'title:caf\xe9' results = self.lib.items(q) - self.assert_matched(results, [u'caf\xe9']) + self.assert_items_matched(results, [u'caf\xe9']) def test_numeric_search_positive(self): q = dbcore.query.NumericQuery('year', '2001') @@ -343,75 +346,118 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): super(PathQueryTest, self).setUp() self.i.path = '/a/b/c.mp3' self.i.title = 'path item' + self.i.album = 'path album' self.i.store() + self.lib.add_album([self.i]) def test_path_exact_match(self): q = 'path:/a/b/c.mp3' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, []) def test_parent_directory_no_slash(self): q = 'path:/a' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['path album']) def test_parent_directory_with_slash(self): q = 'path:/a/' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['path album']) def test_no_match(self): q = 'path:/xyzzy/' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) + + results = self.lib.albums(q) + self.assert_albums_matched(results, []) def test_fragment_no_match(self): q = 'path:/b/' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) + + results = self.lib.albums(q) + self.assert_albums_matched(results, []) def test_nonnorm_path(self): q = 'path:/x/../a/b' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['path album']) def test_slashed_query_matches_path(self): q = '/a/b' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['path album']) def test_non_slashed_does_not_match_path(self): q = 'c.mp3' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) + + results = self.lib.albums(q) + self.assert_albums_matched(results, []) def test_slashes_in_explicit_field_does_not_match_path(self): q = 'title:/a/b' results = self.lib.items(q) - self.assert_matched(results, []) + self.assert_items_matched(results, []) - def test_path_regex(self): + def test_path_item_regex(self): q = 'path::\\.mp3$' results = self.lib.items(q) - self.assert_matched(results, ['path item']) + self.assert_items_matched(results, ['path item']) + + def test_path_album_regex(self): + q = 'path::b' + results = self.lib.albums(q) + self.assert_albums_matched(results, ['path album']) def test_escape_underscore(self): - self.add_item(path='/a/_/title.mp3', title='with underscore') + self.add_album(path='/a/_/title.mp3', title='with underscore', + album='album with underscore') q = 'path:/a/_' results = self.lib.items(q) - self.assert_matched(results, ['with underscore']) + self.assert_items_matched(results, ['with underscore']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['album with underscore']) def test_escape_percent(self): - self.add_item(path='/a/%/title.mp3', title='with percent') + self.add_album(path='/a/%/title.mp3', title='with percent', + album='album with percent') q = 'path:/a/%' results = self.lib.items(q) - self.assert_matched(results, ['with percent']) + self.assert_items_matched(results, ['with percent']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['album with percent']) def test_escape_backslash(self): - self.add_item(path=r'/a/\x/title.mp3', title='with backslash') + self.add_album(path=r'/a/\x/title.mp3', title='with backslash', + album='album with backslash') q = r'path:/a/\\x' results = self.lib.items(q) - self.assert_matched(results, ['with backslash']) + self.assert_items_matched(results, ['with backslash']) + + results = self.lib.albums(q) + self.assert_albums_matched(results, ['album with backslash']) class IntQueryTest(unittest.TestCase, TestHelper): @@ -517,11 +563,11 @@ class DefaultSearchFieldsTest(DummyDataTestCase): def test_items_matches_title(self): items = self.lib.items('beets') - self.assert_matched(items, ['beets 4 eva']) + self.assert_items_matched(items, ['beets 4 eva']) def test_items_does_not_match_year(self): items = self.lib.items('2001') - self.assert_matched(items, []) + self.assert_items_matched(items, []) class NoneQueryTest(unittest.TestCase, TestHelper):