diff --git a/docs/changelog.rst b/docs/changelog.rst index 5155fc79..0fdcaa16 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -77,6 +77,17 @@ v1.0.0 (UNRELEASED) :meth:`mopidy.core.LibraryController.find_exact` to normalize and warn about bad queries from clients. (Fixes: :issue:`1067`, PR: :issue:`1073`) +- Add :meth:`mopidy.core.PlaylistsController.as_list`. (Fixes: :issue:`1057`, + PR: :issue:`1075`) + +- Add :meth:`mopidy.core.PlaylistsController.get_items`. (Fixes: :issue:`1057`, + PR: :issue:`1075`) + +- **Deprecated:** :meth:`mopidy.core.PlaylistsController.get_playlists`. Use + :meth:`~mopidy.core.PlaylistsController.as_list` and + :meth:`~mopidy.core.PlaylistsController.get_items` instead. (Fixes: + :issue:`1057`, PR: :issue:`1075`) + **Backend API** - Remove default implementation of diff --git a/mopidy/core/playlists.py b/mopidy/core/playlists.py index 15d35aa9..146b8058 100644 --- a/mopidy/core/playlists.py +++ b/mopidy/core/playlists.py @@ -16,12 +16,52 @@ class PlaylistsController(object): self.backends = backends self.core = core - """ - Get the available playlists. + def as_list(self): + """ + Get a list of the currently available playlists. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlists. In other words, no information about the playlists' content + is given. + + :rtype: list of :class:`mopidy.models.Ref` + + .. versionadded:: 1.0 + """ + futures = [ + b.playlists.as_list() + for b in self.backends.with_playlists.values()] + results = pykka.get_all(futures) + return list(itertools.chain(*results)) + + def get_items(self, uri): + """ + Get the items in a playlist specified by ``uri``. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlist's items. + + If a playlist with the given ``uri`` doesn't exist, it returns + :class:`None`. + + :rtype: list of :class:`mopidy.models.Ref`, or :class:`None` + + .. versionadded:: 1.0 + """ + uri_scheme = urlparse.urlparse(uri).scheme + backend = self.backends.with_playlists.get(uri_scheme, None) + if backend: + return backend.playlists.get_items(uri).get() - Returns a list of :class:`mopidy.models.Playlist`. - """ def get_playlists(self, include_tracks=True): + """ + Get the available playlists. + + :rtype: list of :class:`mopidy.models.Playlist` + + .. deprecated:: 1.0 + Use :meth:`as_list` and :meth:`get_items` instead. + """ futures = [b.playlists.playlists for b in self.backends.with_playlists.values()] results = pykka.get_all(futures) @@ -33,7 +73,7 @@ class PlaylistsController(object): playlists = deprecated_property(get_playlists) """ .. deprecated:: 1.0 - Use :meth:`get_playlists` instead. + Use :meth:`as_list` and :meth:`get_items` instead. """ def create(self, name, uri_scheme=None): diff --git a/tests/core/test_playlists.py b/tests/core/test_playlists.py index 55a75767..232631d7 100644 --- a/tests/core/test_playlists.py +++ b/tests/core/test_playlists.py @@ -5,19 +5,37 @@ import unittest import mock from mopidy import backend, core -from mopidy.models import Playlist, Track +from mopidy.models import Playlist, Ref, Track class PlaylistsTest(unittest.TestCase): def setUp(self): # noqa: N802 + self.plr1a = Ref.playlist(name='A', uri='dummy1:pl:a') + self.plr1b = Ref.playlist(name='B', uri='dummy1:pl:b') + self.plr2a = Ref.playlist(name='A', uri='dummy2:pl:a') + self.plr2b = Ref.playlist(name='B', uri='dummy2:pl:b') + + self.pl1a = Playlist(name='A', tracks=[Track(uri='dummy1:t:a')]) + self.pl1b = Playlist(name='B', tracks=[Track(uri='dummy1:t:b')]) + self.pl2a = Playlist(name='A', tracks=[Track(uri='dummy2:t:a')]) + self.pl2b = Playlist(name='B', tracks=[Track(uri='dummy2:t:b')]) + + self.sp1 = mock.Mock(spec=backend.PlaylistsProvider) + self.sp1.as_list.return_value.get.return_value = [ + self.plr1a, self.plr1b] + self.sp1.playlists.get.return_value = [self.pl1a, self.pl1b] + + self.sp2 = mock.Mock(spec=backend.PlaylistsProvider) + self.sp2.as_list.return_value.get.return_value = [ + self.plr2a, self.plr2b] + self.sp2.playlists.get.return_value = [self.pl2a, self.pl2b] + self.backend1 = mock.Mock() self.backend1.uri_schemes.get.return_value = ['dummy1'] - self.sp1 = mock.Mock(spec=backend.PlaylistsProvider) self.backend1.playlists = self.sp1 self.backend2 = mock.Mock() self.backend2.uri_schemes.get.return_value = ['dummy2'] - self.sp2 = mock.Mock(spec=backend.PlaylistsProvider) self.backend2.playlists = self.sp2 # A backend without the optional playlists provider @@ -26,17 +44,34 @@ class PlaylistsTest(unittest.TestCase): self.backend3.has_playlists().get.return_value = False self.backend3.playlists = None - self.pl1a = Playlist(name='A', tracks=[Track(uri='dummy1:a')]) - self.pl1b = Playlist(name='B', tracks=[Track(uri='dummy1:b')]) - self.sp1.playlists.get.return_value = [self.pl1a, self.pl1b] - - self.pl2a = Playlist(name='A', tracks=[Track(uri='dummy2:a')]) - self.pl2b = Playlist(name='B', tracks=[Track(uri='dummy2:b')]) - self.sp2.playlists.get.return_value = [self.pl2a, self.pl2b] - self.core = core.Core(mixer=None, backends=[ self.backend3, self.backend1, self.backend2]) + def test_as_list_combines_result_from_backends(self): + result = self.core.playlists.as_list() + + self.assertIn(self.plr1a, result) + self.assertIn(self.plr1b, result) + self.assertIn(self.plr2a, result) + self.assertIn(self.plr2b, result) + + def test_get_items_selects_the_matching_backend(self): + ref = Ref.track() + self.sp2.get_items.return_value.get.return_value = [ref] + + result = self.core.playlists.get_items('dummy2:pl:a') + + self.assertEqual([ref], result) + self.assertFalse(self.sp1.get_items.called) + self.sp2.get_items.assert_called_once_with('dummy2:pl:a') + + def test_get_items_with_unknown_uri_scheme_does_nothing(self): + result = self.core.playlists.get_items('unknown:a') + + self.assertIsNone(result) + self.assertFalse(self.sp1.delete.called) + self.assertFalse(self.sp2.delete.called) + def test_get_playlists_combines_result_from_backends(self): result = self.core.playlists.playlists