Merge pull request #287 from jodal/feature/search-result-model
Add SearchResult model
This commit is contained in:
commit
58cbbe8cb3
@ -76,8 +76,17 @@ v0.11.0 (in development)
|
||||
|
||||
- Specified that :attr:`mopidy.models.Playlist.last_modified` should be in UTC.
|
||||
|
||||
- Added :class:`mopidy.models.SearchResult` model to encapsulate search results
|
||||
consisting of more than just tracks.
|
||||
|
||||
*Core API:*
|
||||
|
||||
- Change the following methods to return :class:`mopidy.models.SearchResult`
|
||||
objects which can include both track results and other results:
|
||||
|
||||
- :meth:`mopidy.core.LibraryController.find_exact`
|
||||
- :meth:`mopidy.core.LibraryController.search`
|
||||
|
||||
- Change the following methods to accept either a dict with filters or kwargs.
|
||||
Previously they only accepted kwargs, which made them impossible to use from
|
||||
the Mopidy.js through JSON-RPC, which doesn't support kwargs.
|
||||
|
||||
@ -19,7 +19,7 @@ from __future__ import unicode_literals
|
||||
import pykka
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.models import Playlist
|
||||
from mopidy.models import Playlist, SearchResult
|
||||
|
||||
|
||||
class DummyBackend(pykka.ThreadingActor, base.Backend):
|
||||
@ -37,8 +37,8 @@ class DummyLibraryProvider(base.BaseLibraryProvider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
||||
self.dummy_library = []
|
||||
self.dummy_find_exact_result = []
|
||||
self.dummy_search_result = []
|
||||
self.dummy_find_exact_result = SearchResult()
|
||||
self.dummy_search_result = SearchResult()
|
||||
|
||||
def find_exact(self, **query):
|
||||
return self.dummy_find_exact_result
|
||||
|
||||
@ -4,7 +4,7 @@ import logging
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends import base
|
||||
from mopidy.models import Album
|
||||
from mopidy.models import Album, SearchResult
|
||||
|
||||
from .translator import parse_mpd_tag_cache
|
||||
|
||||
@ -70,7 +70,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
|
||||
result_tracks = filter(any_filter, result_tracks)
|
||||
else:
|
||||
raise LookupError('Invalid lookup field: %s' % field)
|
||||
return result_tracks
|
||||
return SearchResult(uri='file:search', tracks=result_tracks)
|
||||
|
||||
def search(self, **query):
|
||||
self._validate_query(query)
|
||||
@ -107,7 +107,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
|
||||
result_tracks = filter(any_filter, result_tracks)
|
||||
else:
|
||||
raise LookupError('Invalid lookup field: %s' % field)
|
||||
return result_tracks
|
||||
return SearchResult(uri='file:search', tracks=result_tracks)
|
||||
|
||||
def _validate_query(self, query):
|
||||
for (_, values) in query.iteritems():
|
||||
|
||||
@ -2,13 +2,14 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import time
|
||||
import urllib
|
||||
|
||||
import pykka
|
||||
from spotify import Link, SpotifyError
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends import base
|
||||
from mopidy.models import Track
|
||||
from mopidy.models import Track, SearchResult
|
||||
|
||||
from . import translator
|
||||
|
||||
@ -123,12 +124,16 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
|
||||
if not query:
|
||||
return self._get_all_tracks()
|
||||
|
||||
if 'uri' in query.keys():
|
||||
result = []
|
||||
for uri in query['uri']:
|
||||
tracks = self.lookup(uri)
|
||||
result += tracks
|
||||
return result
|
||||
uris = query.get('uri', [])
|
||||
if uris:
|
||||
tracks = []
|
||||
for uri in uris:
|
||||
tracks += self.lookup(uri)
|
||||
if len(uris) == 1:
|
||||
uri = uris[0]
|
||||
else:
|
||||
uri = 'spotify:search'
|
||||
return SearchResult(uri=uri, tracks=tracks)
|
||||
|
||||
spotify_query = self._translate_search_query(query)
|
||||
logger.debug('Spotify search query: %s' % spotify_query)
|
||||
@ -136,12 +141,16 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
|
||||
future = pykka.ThreadingFuture()
|
||||
|
||||
def callback(results, userdata=None):
|
||||
# TODO Include results from results.albums(), etc. too
|
||||
# TODO Consider launching a second search if results.total_tracks()
|
||||
# is larger than len(results.tracks())
|
||||
tracks = [
|
||||
translator.to_mopidy_track(t) for t in results.tracks()]
|
||||
future.set(tracks)
|
||||
search_result = SearchResult(
|
||||
uri='spotify:search:%s' % (
|
||||
urllib.quote(results.query().encode('utf-8'))),
|
||||
albums=[
|
||||
translator.to_mopidy_album(a) for a in results.albums()],
|
||||
artists=[
|
||||
translator.to_mopidy_artist(a) for a in results.artists()],
|
||||
tracks=[
|
||||
translator.to_mopidy_track(t) for t in results.tracks()])
|
||||
future.set(search_result)
|
||||
|
||||
if not self.backend.spotify.connected.wait(settings.SPOTIFY_TIMEOUT):
|
||||
logger.debug('Not connected: Spotify search cancelled')
|
||||
@ -149,7 +158,7 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
|
||||
|
||||
self.backend.spotify.session.search(
|
||||
spotify_query, callback,
|
||||
track_count=200, album_count=0, artist_count=0)
|
||||
album_count=200, artist_count=200, track_count=200)
|
||||
|
||||
try:
|
||||
return future.get(timeout=settings.SPOTIFY_TIMEOUT)
|
||||
@ -157,7 +166,7 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
|
||||
logger.debug(
|
||||
'Timeout: Spotify search did not return in %ds',
|
||||
settings.SPOTIFY_TIMEOUT)
|
||||
return []
|
||||
return SearchResult(uri='spotify:search')
|
||||
|
||||
def _get_all_tracks(self):
|
||||
# Since we can't search for the entire Spotify library, we return
|
||||
@ -165,7 +174,7 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
|
||||
tracks = []
|
||||
for playlist in self.backend.playlists.playlists:
|
||||
tracks += playlist.tracks
|
||||
return tracks
|
||||
return SearchResult(uri='spotify:search', tracks=tracks)
|
||||
|
||||
def _translate_search_query(self, mopidy_query):
|
||||
spotify_query = []
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import itertools
|
||||
import urlparse
|
||||
|
||||
import pykka
|
||||
@ -37,13 +36,12 @@ class LibraryController(object):
|
||||
|
||||
:param query: one or more queries to search for
|
||||
:type query: dict
|
||||
:rtype: list of :class:`mopidy.models.Track`
|
||||
:rtype: list of :class:`mopidy.models.SearchResult`
|
||||
"""
|
||||
query = query or kwargs
|
||||
futures = [
|
||||
b.library.find_exact(**query) for b in self.backends.with_library]
|
||||
results = pykka.get_all(futures)
|
||||
return list(itertools.chain(*results))
|
||||
return pykka.get_all(futures)
|
||||
|
||||
def lookup(self, uri):
|
||||
"""
|
||||
@ -98,10 +96,9 @@ class LibraryController(object):
|
||||
|
||||
:param query: one or more queries to search for
|
||||
:type query: dict
|
||||
:rtype: list of :class:`mopidy.models.Track`
|
||||
:rtype: list of :class:`mopidy.models.SearchResult`
|
||||
"""
|
||||
query = query or kwargs
|
||||
futures = [
|
||||
b.library.search(**query) for b in self.backends.with_library]
|
||||
results = pykka.get_all(futures)
|
||||
return list(itertools.chain(*results))
|
||||
return pykka.get_all(futures)
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import itertools
|
||||
|
||||
from mopidy.frontends.mpd import translator
|
||||
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
|
||||
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
|
||||
@ -10,6 +12,10 @@ QUERY_RE = (
|
||||
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
|
||||
|
||||
def _get_tracks(search_results):
|
||||
return list(itertools.chain(*[r.tracks for r in search_results]))
|
||||
|
||||
|
||||
@handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
|
||||
def count(context, tag, needle):
|
||||
"""
|
||||
@ -55,8 +61,8 @@ def find(context, mpd_query):
|
||||
query = translator.query_from_mpd_search_format(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.find_exact(**query).get()
|
||||
return translator.tracks_to_mpd_format(result)
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
return translator.tracks_to_mpd_format(_get_tracks(results))
|
||||
|
||||
|
||||
@handle_request(r'^findadd ' + QUERY_RE)
|
||||
@ -73,8 +79,8 @@ def findadd(context, mpd_query):
|
||||
query = translator.query_from_mpd_search_format(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.find_exact(**query).get()
|
||||
context.core.tracklist.add(result)
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
context.core.tracklist.add(_get_tracks(results))
|
||||
|
||||
|
||||
@handle_request(
|
||||
@ -179,8 +185,8 @@ def list_(context, field, mpd_query=None):
|
||||
|
||||
def _list_artist(context, query):
|
||||
artists = set()
|
||||
tracks = context.core.library.find_exact(**query).get()
|
||||
for track in tracks:
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
for track in _get_tracks(results):
|
||||
for artist in track.artists:
|
||||
if artist.name:
|
||||
artists.add(('Artist', artist.name))
|
||||
@ -189,8 +195,8 @@ def _list_artist(context, query):
|
||||
|
||||
def _list_album(context, query):
|
||||
albums = set()
|
||||
tracks = context.core.library.find_exact(**query).get()
|
||||
for track in tracks:
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
for track in _get_tracks(results):
|
||||
if track.album and track.album.name:
|
||||
albums.add(('Album', track.album.name))
|
||||
return albums
|
||||
@ -198,8 +204,8 @@ def _list_album(context, query):
|
||||
|
||||
def _list_date(context, query):
|
||||
dates = set()
|
||||
tracks = context.core.library.find_exact(**query).get()
|
||||
for track in tracks:
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
for track in _get_tracks(results):
|
||||
if track.date:
|
||||
dates.add(('Date', track.date))
|
||||
return dates
|
||||
@ -297,8 +303,8 @@ def search(context, mpd_query):
|
||||
query = translator.query_from_mpd_search_format(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.search(**query).get()
|
||||
return translator.tracks_to_mpd_format(result)
|
||||
results = context.core.library.search(**query).get()
|
||||
return translator.tracks_to_mpd_format(_get_tracks(results))
|
||||
|
||||
|
||||
@handle_request(r'^searchadd ' + QUERY_RE)
|
||||
@ -318,8 +324,8 @@ def searchadd(context, mpd_query):
|
||||
query = translator.query_from_mpd_search_format(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.search(**query).get()
|
||||
context.core.tracklist.add(result)
|
||||
results = context.core.library.search(**query).get()
|
||||
context.core.tracklist.add(_get_tracks(results))
|
||||
|
||||
|
||||
@handle_request(r'^searchaddpl "(?P<playlist_name>[^"]+)" ' + QUERY_RE)
|
||||
@ -341,14 +347,14 @@ def searchaddpl(context, playlist_name, mpd_query):
|
||||
query = translator.query_from_mpd_search_format(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.search(**query).get()
|
||||
results = context.core.library.search(**query).get()
|
||||
|
||||
playlists = context.core.playlists.filter(name=playlist_name).get()
|
||||
if playlists:
|
||||
playlist = playlists[0]
|
||||
else:
|
||||
playlist = context.core.playlists.create(playlist_name).get()
|
||||
tracks = list(playlist.tracks) + result
|
||||
tracks = list(playlist.tracks) + _get_tracks(results)
|
||||
playlist = playlist.copy(tracks=tracks)
|
||||
context.core.playlists.save(playlist)
|
||||
|
||||
|
||||
@ -318,3 +318,34 @@ class Playlist(ImmutableObject):
|
||||
def length(self):
|
||||
"""The number of tracks in the playlist. Read-only."""
|
||||
return len(self.tracks)
|
||||
|
||||
|
||||
class SearchResult(ImmutableObject):
|
||||
"""
|
||||
:param uri: search result URI
|
||||
:type uri: string
|
||||
:param tracks: matching tracks
|
||||
:type tracks: list of :class:`Track` elements
|
||||
:param artists: matching artists
|
||||
:type artists: list of :class:`Artist` elements
|
||||
:param albums: matching albums
|
||||
:type albums: list of :class:`Album` elements
|
||||
"""
|
||||
|
||||
# The search result URI. Read-only.
|
||||
uri = None
|
||||
|
||||
# The tracks matching the search query. Read-only.
|
||||
tracks = tuple()
|
||||
|
||||
# The artists matching the search query. Read-only.
|
||||
artists = tuple()
|
||||
|
||||
# The albums matching the search query. Read-only.
|
||||
albums = tuple()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__dict__['tracks'] = tuple(kwargs.pop('tracks', []))
|
||||
self.__dict__['artists'] = tuple(kwargs.pop('artists', []))
|
||||
self.__dict__['albums'] = tuple(kwargs.pop('albums', []))
|
||||
super(SearchResult, self).__init__(*args, **kwargs)
|
||||
|
||||
@ -53,53 +53,53 @@ class LibraryControllerTest(object):
|
||||
|
||||
def test_find_exact_no_hits(self):
|
||||
result = self.library.find_exact(track=['unknown track'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.find_exact(artist=['unknown artist'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.find_exact(album=['unknown artist'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
def test_find_exact_uri(self):
|
||||
track_1_uri = 'file://' + path_to_data_dir('uri1')
|
||||
result = self.library.find_exact(uri=track_1_uri)
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
track_2_uri = 'file://' + path_to_data_dir('uri2')
|
||||
result = self.library.find_exact(uri=track_2_uri)
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_find_exact_track(self):
|
||||
result = self.library.find_exact(track=['track1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.find_exact(track=['track2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_find_exact_artist(self):
|
||||
result = self.library.find_exact(artist=['artist1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.find_exact(artist=['artist2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_find_exact_album(self):
|
||||
result = self.library.find_exact(album=['album1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.find_exact(album=['album2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_find_exact_date(self):
|
||||
result = self.library.find_exact(date=['2001'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.find_exact(date=['2001-02-03'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.find_exact(date=['2002'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_find_exact_wrong_type(self):
|
||||
test = lambda: self.library.find_exact(wrong=['test'])
|
||||
@ -117,70 +117,70 @@ class LibraryControllerTest(object):
|
||||
|
||||
def test_search_no_hits(self):
|
||||
result = self.library.search(track=['unknown track'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.search(artist=['unknown artist'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.search(album=['unknown artist'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.search(uri=['unknown'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.search(any=['unknown'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
def test_search_uri(self):
|
||||
result = self.library.search(uri=['RI1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(uri=['RI2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_search_track(self):
|
||||
result = self.library.search(track=['Rack1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(track=['Rack2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_search_artist(self):
|
||||
result = self.library.search(artist=['Tist1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(artist=['Tist2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_search_album(self):
|
||||
result = self.library.search(album=['Bum1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(album=['Bum2'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_search_date(self):
|
||||
result = self.library.search(date=['2001'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(date=['2001-02-03'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
result = self.library.search(date=['2001-02-04'])
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(list(result[0].tracks), [])
|
||||
|
||||
result = self.library.search(date=['2002'])
|
||||
self.assertEqual(result, self.tracks[1:2])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[1:2])
|
||||
|
||||
def test_search_any(self):
|
||||
result = self.library.search(any=['Tist1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
result = self.library.search(any=['Rack1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
result = self.library.search(any=['Bum1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
result = self.library.search(any=['RI1'])
|
||||
self.assertEqual(result, self.tracks[:1])
|
||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||
|
||||
def test_search_wrong_type(self):
|
||||
test = lambda: self.library.search(wrong=['test'])
|
||||
|
||||
@ -4,7 +4,7 @@ import mock
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.core import Core
|
||||
from mopidy.models import Track
|
||||
from mopidy.models import SearchResult, Track
|
||||
|
||||
from tests import unittest
|
||||
|
||||
@ -75,59 +75,71 @@ class CoreLibraryTest(unittest.TestCase):
|
||||
def test_find_exact_combines_results_from_all_backends(self):
|
||||
track1 = Track(uri='dummy1:a')
|
||||
track2 = Track(uri='dummy2:a')
|
||||
self.library1.find_exact().get.return_value = [track1]
|
||||
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 = [track2]
|
||||
self.library2.find_exact().get.return_value = result2
|
||||
self.library2.find_exact.reset_mock()
|
||||
|
||||
result = self.core.library.find_exact(any=['a'])
|
||||
|
||||
self.assertIn(track1, result)
|
||||
self.assertIn(track2, result)
|
||||
self.assertIn(result1, result)
|
||||
self.assertIn(result2, result)
|
||||
self.library1.find_exact.assert_called_once_with(any=['a'])
|
||||
self.library2.find_exact.assert_called_once_with(any=['a'])
|
||||
|
||||
def test_find_accepts_query_dict_instead_of_kwargs(self):
|
||||
track1 = Track(uri='dummy1:a')
|
||||
track2 = Track(uri='dummy2:a')
|
||||
self.library1.find_exact().get.return_value = [track1]
|
||||
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 = [track2]
|
||||
self.library2.find_exact().get.return_value = result2
|
||||
self.library2.find_exact.reset_mock()
|
||||
|
||||
result = self.core.library.find_exact(dict(any=['a']))
|
||||
|
||||
self.assertIn(track1, result)
|
||||
self.assertIn(track2, result)
|
||||
self.assertIn(result1, result)
|
||||
self.assertIn(result2, result)
|
||||
self.library1.find_exact.assert_called_once_with(any=['a'])
|
||||
self.library2.find_exact.assert_called_once_with(any=['a'])
|
||||
|
||||
def test_search_combines_results_from_all_backends(self):
|
||||
track1 = Track(uri='dummy1:a')
|
||||
track2 = Track(uri='dummy2:a')
|
||||
self.library1.search().get.return_value = [track1]
|
||||
result1 = SearchResult(tracks=[track1])
|
||||
result2 = SearchResult(tracks=[track2])
|
||||
|
||||
self.library1.search().get.return_value = result1
|
||||
self.library1.search.reset_mock()
|
||||
self.library2.search().get.return_value = [track2]
|
||||
self.library2.search().get.return_value = result2
|
||||
self.library2.search.reset_mock()
|
||||
|
||||
result = self.core.library.search(any=['a'])
|
||||
|
||||
self.assertIn(track1, result)
|
||||
self.assertIn(track2, result)
|
||||
self.assertIn(result1, result)
|
||||
self.assertIn(result2, result)
|
||||
self.library1.search.assert_called_once_with(any=['a'])
|
||||
self.library2.search.assert_called_once_with(any=['a'])
|
||||
|
||||
def test_search_accepts_query_dict_instead_of_kwargs(self):
|
||||
track1 = Track(uri='dummy1:a')
|
||||
track2 = Track(uri='dummy2:a')
|
||||
self.library1.search().get.return_value = [track1]
|
||||
result1 = SearchResult(tracks=[track1])
|
||||
result2 = SearchResult(tracks=[track2])
|
||||
|
||||
self.library1.search().get.return_value = result1
|
||||
self.library1.search.reset_mock()
|
||||
self.library2.search().get.return_value = [track2]
|
||||
self.library2.search().get.return_value = result2
|
||||
self.library2.search.reset_mock()
|
||||
|
||||
result = self.core.library.search(dict(any=['a']))
|
||||
|
||||
self.assertIn(track1, result)
|
||||
self.assertIn(track2, result)
|
||||
self.assertIn(result1, result)
|
||||
self.assertIn(result2, result)
|
||||
self.library1.search.assert_called_once_with(any=['a'])
|
||||
self.library2.search.assert_called_once_with(any=['a'])
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mopidy.models import Album, Artist, Track
|
||||
from mopidy.models import Album, Artist, SearchResult, Track
|
||||
|
||||
from tests.frontends.mpd import protocol
|
||||
|
||||
@ -13,9 +13,8 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_findadd(self):
|
||||
self.backend.library.dummy_find_exact_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
tracks=[Track(uri='dummy:a', name='A')])
|
||||
self.assertEqual(self.core.tracklist.length.get(), 0)
|
||||
|
||||
self.sendRequest('findadd "title" "A"')
|
||||
@ -25,9 +24,8 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_searchadd(self):
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.backend.library.dummy_search_result = SearchResult(
|
||||
tracks=[Track(uri='dummy:a', name='A')])
|
||||
self.assertEqual(self.core.tracklist.length.get(), 0)
|
||||
|
||||
self.sendRequest('searchadd "title" "a"')
|
||||
@ -43,9 +41,8 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
||||
Track(uri='dummy:y', name='y'),
|
||||
])
|
||||
self.core.playlists.save(playlist)
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.backend.library.dummy_search_result = SearchResult(
|
||||
tracks=[Track(uri='dummy:a', name='A')])
|
||||
playlists = self.core.playlists.filter(name='my favs').get()
|
||||
self.assertEqual(len(playlists), 1)
|
||||
self.assertEqual(len(playlists[0].tracks), 2)
|
||||
@ -61,9 +58,8 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_searchaddpl_creates_missing_playlist(self):
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.backend.library.dummy_search_result = SearchResult(
|
||||
tracks=[Track(uri='dummy:a', name='A')])
|
||||
self.assertEqual(
|
||||
len(self.core.playlists.filter(name='my favs').get()), 0)
|
||||
|
||||
@ -185,6 +181,17 @@ class MusicDatabaseFindTest(protocol.BaseTestCase):
|
||||
|
||||
|
||||
class MusicDatabaseListTest(protocol.BaseTestCase):
|
||||
def test_list(self):
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
tracks=[
|
||||
Track(uri='dummy:a', name='A', artists=[
|
||||
Artist(name='A Artist')])])
|
||||
|
||||
self.sendRequest('list "artist" "artist" "foo"')
|
||||
|
||||
self.assertInResponse('Artist: A Artist')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_list_foo_returns_ack(self):
|
||||
self.sendRequest('list "foo"')
|
||||
self.assertEqualResponse('ACK [2@0] {list} incorrect arguments')
|
||||
@ -242,8 +249,8 @@ class MusicDatabaseListTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_list_artist_should_not_return_artists_without_names(self):
|
||||
self.backend.library.dummy_find_exact_result = [
|
||||
Track(artists=[Artist(name='')])]
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
tracks=[Track(artists=[Artist(name='')])])
|
||||
|
||||
self.sendRequest('list "artist"')
|
||||
self.assertNotInResponse('Artist: ')
|
||||
@ -301,8 +308,8 @@ class MusicDatabaseListTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_list_album_should_not_return_albums_without_names(self):
|
||||
self.backend.library.dummy_find_exact_result = [
|
||||
Track(album=Album(name=''))]
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
tracks=[Track(album=Album(name=''))])
|
||||
|
||||
self.sendRequest('list "album"')
|
||||
self.assertNotInResponse('Album: ')
|
||||
@ -356,7 +363,8 @@ class MusicDatabaseListTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_list_date_should_not_return_blank_dates(self):
|
||||
self.backend.library.dummy_find_exact_result = [Track(date='')]
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
tracks=[Track(date='')])
|
||||
|
||||
self.sendRequest('list "date"')
|
||||
self.assertNotInResponse('Date: ')
|
||||
|
||||
@ -4,7 +4,7 @@ import datetime
|
||||
import json
|
||||
|
||||
from mopidy.models import (
|
||||
Artist, Album, TlTrack, Track, Playlist,
|
||||
Artist, Album, TlTrack, Track, Playlist, SearchResult,
|
||||
ModelJSONEncoder, model_json_decoder)
|
||||
|
||||
from tests import unittest
|
||||
@ -862,10 +862,56 @@ class PlaylistTest(unittest.TestCase):
|
||||
|
||||
def test_ne(self):
|
||||
playlist1 = Playlist(
|
||||
uri='uri1', name='name2', tracks=[Track(uri='uri1')],
|
||||
uri='uri1', name='name1', tracks=[Track(uri='uri1')],
|
||||
last_modified=1)
|
||||
playlist2 = Playlist(
|
||||
uri='uri2', name='name2', tracks=[Track(uri='uri2')],
|
||||
last_modified=2)
|
||||
self.assertNotEqual(playlist1, playlist2)
|
||||
self.assertNotEqual(hash(playlist1), hash(playlist2))
|
||||
|
||||
|
||||
class SearchResultTest(unittest.TestCase):
|
||||
def test_uri(self):
|
||||
uri = 'an_uri'
|
||||
result = SearchResult(uri=uri)
|
||||
self.assertEqual(result.uri, uri)
|
||||
self.assertRaises(AttributeError, setattr, result, 'uri', None)
|
||||
|
||||
def test_tracks(self):
|
||||
tracks = [Track(), Track(), Track()]
|
||||
result = SearchResult(tracks=tracks)
|
||||
self.assertEqual(list(result.tracks), tracks)
|
||||
self.assertRaises(AttributeError, setattr, result, 'tracks', None)
|
||||
|
||||
def test_artists(self):
|
||||
artists = [Artist(), Artist(), Artist()]
|
||||
result = SearchResult(artists=artists)
|
||||
self.assertEqual(list(result.artists), artists)
|
||||
self.assertRaises(AttributeError, setattr, result, 'artists', None)
|
||||
|
||||
def test_albums(self):
|
||||
albums = [Album(), Album(), Album()]
|
||||
result = SearchResult(albums=albums)
|
||||
self.assertEqual(list(result.albums), albums)
|
||||
self.assertRaises(AttributeError, setattr, result, 'albums', None)
|
||||
|
||||
def test_invalid_kwarg(self):
|
||||
test = lambda: SearchResult(foo='baz')
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_repr_without_results(self):
|
||||
self.assertEquals(
|
||||
"SearchResult(albums=[], artists=[], tracks=[], uri=u'uri')",
|
||||
repr(SearchResult(uri='uri')))
|
||||
|
||||
def test_serialize_without_results(self):
|
||||
self.assertDictEqual(
|
||||
{'__model__': 'SearchResult', 'uri': 'uri'},
|
||||
SearchResult(uri='uri').serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
result1 = SearchResult(uri='uri')
|
||||
serialized = json.dumps(result1, cls=ModelJSONEncoder)
|
||||
result2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(result1, result2)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user