diff --git a/docs/changes.rst b/docs/changes.rst index 18c1f0ad..30e0c056 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -33,6 +33,18 @@ v0.11.0 (in development) - Specified that :attr:`mopidy.models.Playlist.last_modified` should be in UTC. +*Core API:* + +- 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. + + - :meth:`mopidy.core.LibraryController.find_exact` + - :meth:`mopidy.core.LibraryController.search` + - :meth:`mopidy.core.PlaylistsController.filter` + - :meth:`mopidy.core.TracklistController.filter` + - :meth:`mopidy.core.TracklistController.remove` + v0.10.0 (2012-12-12) ==================== diff --git a/mopidy/core/library.py b/mopidy/core/library.py index c1a89222..3c596a3a 100644 --- a/mopidy/core/library.py +++ b/mopidy/core/library.py @@ -17,23 +17,29 @@ class LibraryController(object): uri_scheme = urlparse.urlparse(uri).scheme return self.backends.with_library_by_uri_scheme.get(uri_scheme, None) - def find_exact(self, **query): + def find_exact(self, query=None, **kwargs): """ Search the library for tracks where ``field`` is ``values``. Examples:: # Returns results matching 'a' + find_exact({'any': ['a']}) find_exact(any=['a']) + # Returns results matching artist 'xyz' + find_exact({'artist': ['xyz']}) find_exact(artist=['xyz']) + # Returns results matching 'a' and 'b' and artist 'xyz' + find_exact({'any': ['a', 'b'], 'artist': ['xyz']}) find_exact(any=['a', 'b'], artist=['xyz']) :param query: one or more queries to search for :type query: dict :rtype: list of :class:`mopidy.models.Track` """ + query = query or kwargs futures = [ b.library.find_exact(**query) for b in self.backends.with_library] results = pykka.get_all(futures) @@ -72,23 +78,29 @@ class LibraryController(object): b.library.refresh(uri) for b in self.backends.with_library] pykka.get_all(futures) - def search(self, **query): + def search(self, query=None, **kwargs): """ Search the library for tracks where ``field`` contains ``values``. Examples:: # Returns results matching 'a' + search({'any': ['a']}) search(any=['a']) + # Returns results matching artist 'xyz' + search({'artist': ['xyz']}) search(artist=['xyz']) + # Returns results matching 'a' and 'b' and artist 'xyz' + search({'any': ['a', 'b'], 'artist': ['xyz']}) search(any=['a', 'b'], artist=['xyz']) :param query: one or more queries to search for :type query: dict :rtype: list of :class:`mopidy.models.Track` """ + query = query or kwargs futures = [ b.library.search(**query) for b in self.backends.with_library] results = pykka.get_all(futures) diff --git a/mopidy/core/playlists.py b/mopidy/core/playlists.py index 6a368ac6..62098c7f 100644 --- a/mopidy/core/playlists.py +++ b/mopidy/core/playlists.py @@ -70,21 +70,29 @@ class PlaylistsController(object): if backend: backend.playlists.delete(uri).get() - def filter(self, **criteria): + def filter(self, criteria=None, **kwargs): """ Filter playlists by the given criterias. Examples:: - filter(name='a') # Returns track with name 'a' - filter(uri='xyz') # Returns track with URI 'xyz' - filter(name='a', uri='xyz') # Returns track with name 'a' and URI - # 'xyz' + # Returns track with name 'a' + filter({'name': 'a'}) + filter(name='a') + + # Returns track with URI 'xyz' + filter({'uri': 'xyz'}) + filter(uri='xyz') + + # Returns track with name 'a' and URI 'xyz' + filter({'name': 'a', 'uri': 'xyz'}) + filter(name='a', uri='xyz') :param criteria: one or more criteria to match by :type criteria: dict :rtype: list of :class:`mopidy.models.Playlist` """ + criteria = criteria or kwargs matches = self.playlists for (key, value) in criteria.iteritems(): matches = filter(lambda p: getattr(p, key) == value, matches) diff --git a/mopidy/core/tracklist.py b/mopidy/core/tracklist.py index 656e15b1..402e6c09 100644 --- a/mopidy/core/tracklist.py +++ b/mopidy/core/tracklist.py @@ -103,21 +103,33 @@ class TracklistController(object): self._tl_tracks = [] self._increase_version() - def filter(self, **criteria): + def filter(self, criteria=None, **kwargs): """ Filter the tracklist by the given criterias. Examples:: - filter(tlid=7) # Returns track with TLID 7 (tracklist ID) - filter(id=1) # Returns track with ID 1 - filter(uri='xyz') # Returns track with URI 'xyz' - filter(id=1, uri='xyz') # Returns track with ID 1 and URI 'xyz' + # Returns track with TLID 7 (tracklist ID) + filter({'tlid': 7}) + filter(tlid=7) + + # Returns track with ID 1 + filter({'id': 1}) + filter(id=1) + + # Returns track with URI 'xyz' + filter({'uri': 'xyz'}) + filter(uri='xyz') + + # Returns track with ID 1 and URI 'xyz' + filter({'id': 1, 'uri': 'xyz'}) + filter(id=1, uri='xyz') :param criteria: on or more criteria to match by :type criteria: dict :rtype: list of :class:`mopidy.models.TlTrack` """ + criteria = criteria or kwargs matches = self._tl_tracks for (key, value) in criteria.iteritems(): if key == 'tlid': @@ -172,7 +184,7 @@ class TracklistController(object): self._tl_tracks = new_tl_tracks self._increase_version() - def remove(self, **criteria): + def remove(self, criteria=None, **kwargs): """ Remove the matching tracks from the tracklist. @@ -184,7 +196,7 @@ class TracklistController(object): :type criteria: dict :rtype: list of :class:`mopidy.models.TlTrack` that was removed """ - tl_tracks = self.filter(**criteria) + tl_tracks = self.filter(criteria, **kwargs) for tl_track in tl_tracks: position = self._tl_tracks.index(tl_track) del self._tl_tracks[position] diff --git a/tests/core/library_test.py b/tests/core/library_test.py index 1bd481de..a2c358d7 100644 --- a/tests/core/library_test.py +++ b/tests/core/library_test.py @@ -87,6 +87,21 @@ class CoreLibraryTest(unittest.TestCase): 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] + self.library1.find_exact.reset_mock() + self.library2.find_exact().get.return_value = [track2] + self.library2.find_exact.reset_mock() + + result = self.core.library.find_exact(dict(any=['a'])) + + self.assertIn(track1, result) + self.assertIn(track2, 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') @@ -101,3 +116,18 @@ class CoreLibraryTest(unittest.TestCase): self.assertIn(track2, 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] + self.library1.search.reset_mock() + self.library2.search().get.return_value = [track2] + self.library2.search.reset_mock() + + result = self.core.library.search(dict(any=['a'])) + + self.assertIn(track1, result) + self.assertIn(track2, result) + self.library1.search.assert_called_once_with(any=['a']) + self.library2.search.assert_called_once_with(any=['a']) diff --git a/tests/core/playlists_test.py b/tests/core/playlists_test.py index 949625fe..cea93c5b 100644 --- a/tests/core/playlists_test.py +++ b/tests/core/playlists_test.py @@ -27,12 +27,12 @@ class PlaylistsTest(unittest.TestCase): self.backend3.has_playlists().get.return_value = False self.backend3.playlists = None - self.pl1a = Playlist(tracks=[Track(uri='dummy1:a')]) - self.pl1b = Playlist(tracks=[Track(uri='dummy1:b')]) + 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(tracks=[Track(uri='dummy2:a')]) - self.pl2b = Playlist(tracks=[Track(uri='dummy2:b')]) + 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(audio=None, backends=[ @@ -103,6 +103,16 @@ class PlaylistsTest(unittest.TestCase): self.assertFalse(self.sp1.delete.called) self.assertFalse(self.sp2.delete.called) + def test_filter_returns_matching_playlists(self): + result = self.core.playlists.filter(name='A') + + self.assertEqual(2, len(result)) + + def test_filter_accepts_dict_instead_of_kwargs(self): + result = self.core.playlists.filter({'name': 'A'}) + + self.assertEqual(2, len(result)) + def test_lookup_selects_the_dummy1_backend(self): self.core.playlists.lookup('dummy1:a') diff --git a/tests/core/tracklist_test.py b/tests/core/tracklist_test.py new file mode 100644 index 00000000..550cfe63 --- /dev/null +++ b/tests/core/tracklist_test.py @@ -0,0 +1,49 @@ +from __future__ import unicode_literals + +from mopidy.core import Core +from mopidy.models import Track + +from tests import unittest + + +class TracklistTest(unittest.TestCase): + def setUp(self): + self.tracks = [ + Track(uri='a', name='foo'), + Track(uri='b', name='foo'), + Track(uri='c', name='bar') + ] + self.core = Core(audio=None, backends=[]) + self.tl_tracks = self.core.tracklist.add(self.tracks) + + def test_remove_removes_tl_tracks_matching_query(self): + tl_tracks = self.core.tracklist.remove(name='foo') + + self.assertEqual(2, len(tl_tracks)) + self.assertListEqual(self.tl_tracks[:2], tl_tracks) + + self.assertEqual(1, self.core.tracklist.length) + self.assertListEqual(self.tl_tracks[2:], self.core.tracklist.tl_tracks) + + def test_remove_works_with_dict_instead_of_kwargs(self): + tl_tracks = self.core.tracklist.remove({'name': 'foo'}) + + self.assertEqual(2, len(tl_tracks)) + self.assertListEqual(self.tl_tracks[:2], tl_tracks) + + self.assertEqual(1, self.core.tracklist.length) + self.assertListEqual(self.tl_tracks[2:], self.core.tracklist.tl_tracks) + + def test_filter_returns_tl_tracks_matching_query(self): + tl_tracks = self.core.tracklist.filter(name='foo') + + self.assertEqual(2, len(tl_tracks)) + self.assertListEqual(self.tl_tracks[:2], tl_tracks) + + def test_filter_works_with_dict_instead_of_kwargs(self): + tl_tracks = self.core.tracklist.filter({'name': 'foo'}) + + self.assertEqual(2, len(tl_tracks)) + self.assertListEqual(self.tl_tracks[:2], tl_tracks) + + # TODO Extract tracklist tests from the base backend tests