Merge pull request #981 from adamcik/feature/core-get-images
Add library.get_images()
This commit is contained in:
commit
638740541b
@ -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
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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``.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user