diff --git a/docs/changelog.rst b/docs/changelog.rst index 09bf743a..94f35433 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -138,6 +138,11 @@ v1.0.0 (UNRELEASED) - Remove :attr:`mopidy.backend.PlaylistsProvider.playlists` property. +- Removed ``find_exact`` from :class:`mopidy.backend.LibraryProvider` and + added an ``exact`` param to :meth:`mopidy.backend.LibraryProvider.search` + to replace the old code path. Core will continue supporting backends that + have not upgraded for now. + **Commands** - Make the ``mopidy`` command print a friendly error message if the diff --git a/mopidy/backend.py b/mopidy/backend.py index 02a624d9..63184853 100644 --- a/mopidy/backend.py +++ b/mopidy/backend.py @@ -119,15 +119,6 @@ class LibraryProvider(object): result[uri] = [models.Image(uri=u) for u in image_uris] return result - # TODO: replace with search(query, exact=True, ...) - def find_exact(self, query=None, uris=None): - """ - See :meth:`mopidy.core.LibraryController.find_exact`. - - *MAY be implemented by subclass.* - """ - pass - def lookup(self, uri): """ See :meth:`mopidy.core.LibraryController.lookup`. @@ -144,11 +135,14 @@ class LibraryProvider(object): """ pass - def search(self, query=None, uris=None): + def search(self, query=None, uris=None, exact=False): """ See :meth:`mopidy.core.LibraryController.search`. *MAY be implemented by subclass.* + + .. versionadded:: 1.0 + The ``exact`` param which replaces the old ``find_exact``. """ pass diff --git a/mopidy/core/library.py b/mopidy/core/library.py index 2ab90bef..80c61bbb 100644 --- a/mopidy/core/library.py +++ b/mopidy/core/library.py @@ -160,6 +160,12 @@ class LibraryController(object): {'any': ['a']}, uris=['file:///media/music', 'spotify:']) find_exact(any=['a'], uris=['file:///media/music', 'spotify:']) + .. versionchanged:: 1.0 + This method now calls + :meth:`~mopidy.backend.LibraryProvider.search` on the backends + instead of the deprecated ``find_exact``. If the backend still + implements ``find_exact`` we will continue to use it for now. + :param query: one or more queries to search for :type query: dict :param uris: zero or more URI roots to limit the search to @@ -167,10 +173,15 @@ class LibraryController(object): :rtype: list of :class:`mopidy.models.SearchResult` """ query = _normalize_query(query or kwargs) - futures = [ - backend.library.find_exact(query=query, uris=backend_uris) - for (backend, backend_uris) - in self._get_backends_to_uris(uris).items()] + futures = [] + for backend, backend_uris in self._get_backends_to_uris(uris).items(): + if hasattr(backend.library, 'find_exact'): + futures.append(backend.library.find_exact( + query=query, uris=backend_uris)) + else: + futures.append(backend.library.search( + query=query, uris=backend_uris, exact=True)) + return [result for result in pykka.get_all(futures) if result] def lookup(self, uri=None, uris=None): diff --git a/tests/core/test_library.py b/tests/core/test_library.py index 9a23d874..98b25f38 100644 --- a/tests/core/test_library.py +++ b/tests/core/test_library.py @@ -212,54 +212,50 @@ class CoreLibraryTest(unittest.TestCase): result1 = SearchResult(tracks=[track1]) result2 = SearchResult(tracks=[track2]) - self.library1.find_exact().get.return_value = result1 - self.library1.find_exact.reset_mock() - self.library2.find_exact().get.return_value = result2 - self.library2.find_exact.reset_mock() + self.library1.search.return_value.get.return_value = result1 + self.library2.search.return_value.get.return_value = result2 result = self.core.library.find_exact(any=['a']) self.assertIn(result1, result) self.assertIn(result2, result) - self.library1.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) - self.library2.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) + self.library1.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) + self.library2.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) def test_find_exact_with_uris_selects_dummy1_backend(self): self.core.library.find_exact( any=['a'], uris=['dummy1:', 'dummy1:foo', 'dummy3:']) - self.library1.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo']) - self.assertFalse(self.library2.find_exact.called) + self.library1.search.assert_called_once_with( + query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'], exact=True) + self.assertFalse(self.library2.search.called) def test_find_exact_with_uris_selects_both_backends(self): self.core.library.find_exact( any=['a'], uris=['dummy1:', 'dummy1:foo', 'dummy2:']) - self.library1.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo']) - self.library2.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=['dummy2:']) + self.library1.search.assert_called_once_with( + query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'], exact=True) + self.library2.search.assert_called_once_with( + query=dict(any=['a']), uris=['dummy2:'], exact=True) def test_find_exact_filters_out_none(self): track1 = Track(uri='dummy1:a') result1 = SearchResult(tracks=[track1]) - self.library1.find_exact().get.return_value = result1 - self.library1.find_exact.reset_mock() - self.library2.find_exact().get.return_value = None - self.library2.find_exact.reset_mock() + self.library1.search.return_value.get.return_value = result1 + self.library2.search.return_value.get.return_value = None result = self.core.library.find_exact(any=['a']) self.assertIn(result1, result) self.assertNotIn(None, result) - self.library1.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) - self.library2.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) + self.library1.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) + self.library2.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) def test_find_accepts_query_dict_instead_of_kwargs(self): track1 = Track(uri='dummy1:a') @@ -267,19 +263,17 @@ class CoreLibraryTest(unittest.TestCase): result1 = SearchResult(tracks=[track1]) result2 = SearchResult(tracks=[track2]) - self.library1.find_exact().get.return_value = result1 - self.library1.find_exact.reset_mock() - self.library2.find_exact().get.return_value = result2 - self.library2.find_exact.reset_mock() + self.library1.search.return_value.get.return_value = result1 + self.library2.search.return_value.get.return_value = result2 result = self.core.library.find_exact(dict(any=['a'])) self.assertIn(result1, result) self.assertIn(result2, result) - self.library1.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) - self.library2.find_exact.assert_called_once_with( - query=dict(any=['a']), uris=None) + self.library1.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) + self.library2.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True) def test_search_combines_results_from_all_backends(self): track1 = Track(uri='dummy1:a') @@ -363,5 +357,29 @@ class CoreLibraryTest(unittest.TestCase): def test_find_exact_normalises_bad_queries(self): self.core.library.find_exact({'any': 'foobar'}) - self.library1.find_exact.assert_called_once_with( - query={'any': ['foobar']}, uris=None) + self.library1.search.assert_called_once_with( + query={'any': ['foobar']}, uris=None, exact=True) + + +class LegacyLibraryProvider(backend.LibraryProvider): + def find_exact(self, query=None, uris=None): + pass + + +class LegacyCoreLibraryTest(unittest.TestCase): + def test_backend_with_find_exact_still_works(self): + b1 = mock.Mock() + b1.uri_schemes.get.return_value = ['dummy1'] + b1.library = mock.Mock(spec=LegacyLibraryProvider) + + b2 = mock.Mock() + b2.uri_schemes.get.return_value = ['dummy2'] + b2.library = mock.Mock(spec=backend.LibraryProvider) + + c = core.Core(mixer=None, backends=[b1, b2]) + c.library.find_exact(query={'any': ['a']}) + + b1.library.find_exact.assert_called_once_with( + query=dict(any=['a']), uris=None) + b2.library.search.assert_called_once_with( + query=dict(any=['a']), uris=None, exact=True)