Merge pull request #981 from adamcik/feature/core-get-images

Add library.get_images()
This commit is contained in:
Stein Magnus Jodal 2015-02-13 00:33:29 +01:00
commit 638740541b
6 changed files with 129 additions and 4 deletions

View File

@ -25,6 +25,9 @@ v0.20.0 (UNRELEASED)
:meth:`mopidy.core.Playback.stop`. It was a leaky internal abstraction,
which was never intended to be used externally.
- Add :meth:`mopidy.core.LibraryController.get_images` for looking up images
for any URI backends know about. (Fixes :issue:`973`)
**Commands**
- Make the ``mopidy`` command print a friendly error message if the

View File

@ -92,6 +92,14 @@ class LibraryProvider(object):
"""
return []
def get_images(self, uris):
"""
See :meth:`mopidy.core.LibraryController.get_images`.
*MAY be implemented by subclass.*
"""
return {}
# TODO: replace with search(query, exact=True, ...)
def find_exact(self, query=None, uris=None):
"""

View File

@ -72,6 +72,30 @@ class LibraryController(object):
return []
return backend.library.browse(uri).get()
def get_images(self, uris):
"""Lookup the images for the given URIs
Backends can use this to return image URIs for any URI they know about
be it tracks, albums, playlists... The lookup result is a dictionary
mapping the provided URIs to lists of images.
Unknown URIs or URIs the corresponding backend couldn't find anything
for will simply return an empty list for that URI.
:param list uris: list of URIs to find images for
:rtype: {uri: tuple of :class:`mopidy.models.Image`}
"""
futures = [
backend.library.get_images(backend_uris)
for (backend, backend_uris)
in self._get_backends_to_uris(uris).items() if backend_uris]
results = {uri: tuple() for uri in uris}
for r in pykka.get_all(futures):
for uri, images in r.items():
results[uri] += tuple(images)
return results
def find_exact(self, query=None, uris=None, **kwargs):
"""
Search the library for tracks where ``field`` is ``values``.

View File

@ -212,6 +212,23 @@ class Ref(ImmutableObject):
return cls(**kwargs)
class Image(ImmutableObject):
"""
:param string uri: URI of the image
:param int width: Optional width of image or :class:`None`
:param int height: Optional height of image or :class:`None`
"""
#: The image URI. Read-only.
uri = None
#: Optional width of the image or :class:`None`. Read-only.
width = None
#: Optional height of the image or :class:`None`. Read-only.
height = None
class Artist(ImmutableObject):
"""
:param uri: artist URI

View File

@ -5,7 +5,7 @@ import unittest
import mock
from mopidy import backend, core
from mopidy.models import Ref, SearchResult, Track
from mopidy.models import Image, Ref, SearchResult, Track
class CoreLibraryTest(unittest.TestCase):
@ -14,6 +14,8 @@ class CoreLibraryTest(unittest.TestCase):
self.backend1 = mock.Mock()
self.backend1.uri_schemes.get.return_value = ['dummy1']
self.library1 = mock.Mock(spec=backend.LibraryProvider)
self.library1.get_images().get.return_value = {}
self.library1.get_images.reset_mock()
self.library1.root_directory.get.return_value = dummy1_root
self.backend1.library = self.library1
@ -21,6 +23,8 @@ class CoreLibraryTest(unittest.TestCase):
self.backend2 = mock.Mock()
self.backend2.uri_schemes.get.return_value = ['dummy2', 'du2']
self.library2 = mock.Mock(spec=backend.LibraryProvider)
self.library2.get_images().get.return_value = {}
self.library2.get_images.reset_mock()
self.library2.root_directory.get.return_value = dummy2_root
self.backend2.library = self.library2
@ -33,6 +37,50 @@ class CoreLibraryTest(unittest.TestCase):
self.core = core.Core(mixer=None, backends=[
self.backend1, self.backend2, self.backend3])
def test_get_images_returns_empty_dict_for_no_uris(self):
self.assertEqual({}, self.core.library.get_images([]))
def test_get_images_returns_empty_result_for_unknown_uri(self):
result = self.core.library.get_images(['dummy4:track'])
self.assertEqual({'dummy4:track': tuple()}, result)
def test_get_images_returns_empty_result_for_library_less_uri(self):
result = self.core.library.get_images(['dummy3:track'])
self.assertEqual({'dummy3:track': tuple()}, result)
def test_get_images_maps_uri_to_backend(self):
self.core.library.get_images(['dummy1:track'])
self.library1.get_images.assert_called_once_with(['dummy1:track'])
self.library2.get_images.assert_not_called()
def test_get_images_maps_uri_to_backends(self):
self.core.library.get_images(['dummy1:track', 'dummy2:track'])
self.library1.get_images.assert_called_once_with(['dummy1:track'])
self.library2.get_images.assert_called_once_with(['dummy2:track'])
def test_get_images_returns_images(self):
self.library1.get_images().get.return_value = {
'dummy1:track': [Image(uri='uri')]}
self.library1.get_images.reset_mock()
result = self.core.library.get_images(['dummy1:track'])
self.assertEqual({'dummy1:track': (Image(uri='uri'),)}, result)
def test_get_images_merges_results(self):
self.library1.get_images().get.return_value = {
'dummy1:track': [Image(uri='uri1')]}
self.library1.get_images.reset_mock()
self.library2.get_images().get.return_value = {
'dummy2:track': [Image(uri='uri2')]}
self.library2.get_images.reset_mock()
result = self.core.library.get_images(
['dummy1:track', 'dummy2:track', 'dummy3:track', 'dummy4:track'])
expected = {'dummy1:track': (Image(uri='uri1'),),
'dummy2:track': (Image(uri='uri2'),),
'dummy3:track': tuple(), 'dummy4:track': tuple()}
self.assertEqual(expected, result)
def test_browse_root_returns_dir_ref_for_each_lib_with_root_dir_name(self):
result = self.core.library.browse(None)

View File

@ -4,8 +4,8 @@ import json
import unittest
from mopidy.models import (
Album, Artist, ModelJSONEncoder, Playlist, Ref, SearchResult, TlTrack,
Track, model_json_decoder)
Album, Artist, Image, ModelJSONEncoder, Playlist, Ref, SearchResult,
TlTrack, Track, model_json_decoder)
class GenericCopyTest(unittest.TestCase):
@ -74,7 +74,7 @@ class RefTest(unittest.TestCase):
def test_invalid_kwarg(self):
with self.assertRaises(TypeError):
SearchResult(foo='baz')
Ref(foo='baz')
def test_repr_without_results(self):
self.assertEquals(
@ -130,6 +130,31 @@ class RefTest(unittest.TestCase):
self.assertEqual(ref.type, Ref.TRACK)
class ImageTest(unittest.TestCase):
def test_uri(self):
uri = 'an_uri'
image = Image(uri=uri)
self.assertEqual(image.uri, uri)
with self.assertRaises(AttributeError):
image.uri = None
def test_width(self):
image = Image(width=100)
self.assertEqual(image.width, 100)
with self.assertRaises(AttributeError):
image.width = None
def test_height(self):
image = Image(height=100)
self.assertEqual(image.height, 100)
with self.assertRaises(AttributeError):
image.height = None
def test_invalid_kwarg(self):
with self.assertRaises(TypeError):
Image(foo='baz')
class ArtistTest(unittest.TestCase):
def test_uri(self):
uri = 'an_uri'