core: Add exact to search() and deprecate find_exact()

Backends that still implement find_exact will be called without exact as an
argument to search, and we will continue to use find_exact. Please remove
find_exact from such backends and switch to the new search API.
This commit is contained in:
Thomas Adamcik 2015-03-24 09:26:11 +01:00
parent 3e361d4870
commit 141c14ad45
3 changed files with 58 additions and 69 deletions

View File

@ -103,6 +103,11 @@ v1.0.0 (UNRELEASED)
- **Deprecated:** :meth:`mopidy.core.PlaylistsController.filter`. Use
:meth:`~mopidy.core.PlaylistsController.as_list` and filter yourself.
- Add ``exact`` to :meth:`mopidy.core.LibraryController.search`.
- **Deprecated:** :meth:`mopidy.core.LibraryController.find_exact`. Use
:meth:`mopidy.core.LibraryController.search` with ``exact`` set.
**Backend API**
- Remove default implementation of

View File

@ -127,62 +127,12 @@ class LibraryController(object):
return results
def find_exact(self, query=None, uris=None, **kwargs):
"""
Search the library for tracks where ``field`` is ``values``.
"""Search the library for tracks where ``field`` is ``values``.
.. deprecated:: 1.0
Previously, if the query was empty, and the backend could support
it, all available tracks were returned. This has not changed, but
it is strongly discouraged. No new code should rely on this
If ``uris`` is given, the search is limited to results from within the
URI roots. For example passing ``uris=['file:']`` will limit the search
to the local backend.
Examples::
# Returns results matching 'a' from any backend
find_exact({'any': ['a']})
find_exact(any=['a'])
# Returns results matching artist 'xyz' from any backend
find_exact({'artist': ['xyz']})
find_exact(artist=['xyz'])
# Returns results matching 'a' and 'b' and artist 'xyz' from any
# backend
find_exact({'any': ['a', 'b'], 'artist': ['xyz']})
find_exact(any=['a', 'b'], artist=['xyz'])
# Returns results matching 'a' if within the given URI roots
# "file:///media/music" and "spotify:"
find_exact(
{'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
:type uris: list of strings or :class:`None`
:rtype: list of :class:`mopidy.models.SearchResult`
Use :meth:`search` with ``exact`` set.
"""
query = _normalize_query(query or kwargs)
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]
return self.search(query=query, uris=uris, exact=True, **kwargs)
def lookup(self, uri=None, uris=None):
"""
@ -248,7 +198,7 @@ class LibraryController(object):
for b in self.backends.with_library.values()]
pykka.get_all(futures)
def search(self, query=None, uris=None, **kwargs):
def search(self, query=None, uris=None, exact=False, **kwargs):
"""
Search the library for tracks where ``field`` contains ``values``.
@ -287,12 +237,29 @@ class LibraryController(object):
:param uris: zero or more URI roots to limit the search to
:type uris: list of strings or :class:`None`
:rtype: list of :class:`mopidy.models.SearchResult`
.. versionadded:: 1.0
The ``exact`` keyword argument, which replaces :meth:`find_exact`.
"""
query = _normalize_query(query or kwargs)
futures = [
backend.library.search(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'):
# Backends with find_exact probably don't have support for
# search with the exact kwarg, so give them the legacy calls.
if exact:
futures.append(backend.library.find_exact(
query=query, uris=backend_uris))
else:
futures.append(backend.library.search(
query=query, uris=backend_uris))
else:
# Assume backends without find_exact are up to date. Worst case
# the exact gets swallowed by the **kwargs and things hopefully
# still work.
futures.append(backend.library.search(
query=query, uris=backend_uris, exact=exact))
return [result for result in pykka.get_all(futures) if result]

View File

@ -291,16 +291,16 @@ class CoreLibraryTest(unittest.TestCase):
self.assertIn(result1, result)
self.assertIn(result2, result)
self.library1.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
self.library2.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
def test_search_with_uris_selects_dummy1_backend(self):
self.core.library.search(
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo', 'dummy3:'])
self.library1.search.assert_called_once_with(
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'])
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'], exact=False)
self.assertFalse(self.library2.search.called)
def test_search_with_uris_selects_both_backends(self):
@ -308,9 +308,9 @@ class CoreLibraryTest(unittest.TestCase):
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo', 'dummy2:'])
self.library1.search.assert_called_once_with(
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'])
query=dict(any=['a']), uris=['dummy1:', 'dummy1:foo'], exact=False)
self.library2.search.assert_called_once_with(
query=dict(any=['a']), uris=['dummy2:'])
query=dict(any=['a']), uris=['dummy2:'], exact=False)
def test_search_filters_out_none(self):
track1 = Track(uri='dummy1:a')
@ -326,9 +326,9 @@ class CoreLibraryTest(unittest.TestCase):
self.assertIn(result1, result)
self.assertNotIn(None, result)
self.library1.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
self.library2.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
def test_search_accepts_query_dict_instead_of_kwargs(self):
track1 = Track(uri='dummy1:a')
@ -346,14 +346,14 @@ class CoreLibraryTest(unittest.TestCase):
self.assertIn(result1, result)
self.assertIn(result2, result)
self.library1.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
self.library2.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
query=dict(any=['a']), uris=None, exact=False)
def test_search_normalises_bad_queries(self):
self.core.library.search({'any': 'foobar'})
self.library1.search.assert_called_once_with(
query={'any': ['foobar']}, uris=None)
query={'any': ['foobar']}, uris=None, exact=False)
def test_find_exact_normalises_bad_queries(self):
self.core.library.find_exact({'any': 'foobar'})
@ -367,7 +367,7 @@ class LegacyLibraryProvider(backend.LibraryProvider):
class LegacyCoreLibraryTest(unittest.TestCase):
def test_backend_with_find_exact_still_works(self):
def test_backend_with_find_exact_gets_find_exact_call(self):
b1 = mock.Mock()
b1.uri_schemes.get.return_value = ['dummy1']
b1.library = mock.Mock(spec=LegacyLibraryProvider)
@ -383,3 +383,20 @@ class LegacyCoreLibraryTest(unittest.TestCase):
query=dict(any=['a']), uris=None)
b2.library.search.assert_called_once_with(
query=dict(any=['a']), uris=None, exact=True)
def test_backend_with_find_exact_gets_search_without_exact_arg(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.search(query={'any': ['a']})
b1.library.search.assert_called_once_with(
query=dict(any=['a']), uris=None)
b2.library.search.assert_called_once_with(
query=dict(any=['a']), uris=None, exact=False)