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:
parent
3e361d4870
commit
141c14ad45
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user