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 = { backend: backend.playlists.as_list() for backend in set(self.backends.with_playlists.values()) } results = [] for b, future in futures.items(): try: with _backend_error_handling(b, reraise=NotImplementedError): playlists = future.get() if playlists is not None: validation.check_instances(playlists, Ref) results.extend(playlists) except NotImplementedError: backend_name = b.actor_ref.actor_class.__name__ logger.warning( "%s does not implement playlists.as_list(). " "Please upgrade it.", backend_name, ) return results
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 = { backend: backend.playlists.as_list() for backend in set(self.backends.with_playlists.values())} results = [] for b, future in futures.items(): try: with _backend_error_handling(b, reraise=NotImplementedError): playlists = future.get() if playlists is not None: validation.check_instances(playlists, Ref) results.extend(playlists) except NotImplementedError: backend_name = b.actor_ref.actor_class.__name__ logger.warning( '%s does not implement playlists.as_list(). ' 'Please upgrade it.', backend_name) return results
def get_distinct(self, field, query=None): """ List distinct values for a given field from the library. This has mainly been added to support the list commands the MPD protocol supports in a more sane fashion. Other frontends are not recommended to use this method. :param string field: One of ``track``, ``artist``, ``albumartist``, ``album``, ``composer``, ``performer``, ``date`` or ``genre``. :param dict query: Query to use for limiting results, see :meth:`search` for details about the query format. :rtype: set of values corresponding to the requested field type. .. versionadded:: 1.0 """ validation.check_choice(field, validation.DISTINCT_FIELDS) query is None or validation.check_query(query) # TODO: normalize? result = set() futures = {b: b.library.get_distinct(field, query) for b in self.backends.with_library.values()} for backend, future in futures.items(): with _backend_error_handling(backend): values = future.get() if values is not None: validation.check_instances(values, compat.text_type) result.update(values) return result
def get_distinct(self, field, query=None): """ List distinct values for a given field from the library. This has mainly been added to support the list commands the MPD protocol supports in a more sane fashion. Other frontends are not recommended to use this method. :param string field: One of ``track``, ``artist``, ``albumartist``, ``album``, ``composer``, ``performer``, ``date`` or ``genre``. :param dict query: Query to use for limiting results, see :meth:`search` for details about the query format. :rtype: set of values corresponding to the requested field type. .. versionadded:: 1.0 """ validation.check_choice(field, validation.DISTINCT_FIELDS) query is None or validation.check_query(query) # TODO: normalize? result = set() futures = { b: b.library.get_distinct(field, query) for b in self.backends.with_library.values() } for backend, future in futures.items(): with _backend_error_handling(backend): values = future.get() if values is not None: validation.check_instances(values, str) result.update(values) return result
def lookup(self, uris): """ Lookup the given URIs. If the URI expands to multiple tracks, the returned list will contain them all. :param uris: track URIs :type uris: list of string :rtype: {uri: list of :class:`mopidy.models.Track`} """ validation.check_uris(uris) futures = {} results = {u: [] for u in uris} # TODO: lookup(uris) to backend APIs for backend, backend_uris in self._get_backends_to_uris(uris).items(): if backend_uris: for u in backend_uris: futures[(backend, u)] = backend.library.lookup(u) for (backend, u), future in futures.items(): with _backend_error_handling(backend): result = future.get() if result is not None: validation.check_instances(result, models.Track) # TODO Consider making Track.uri field mandatory, and # then remove this filtering of tracks without URIs. results[u] = [r for r in result if r.uri] return results
def test_check_instances_with_invalid_values(): with raises(exceptions.ValidationError): validation.check_instances("abc", str) with raises(exceptions.ValidationError): validation.check_instances(["abc", 123], str) with raises(exceptions.ValidationError): validation.check_instances(None, str) with raises(exceptions.ValidationError): validation.check_instances([None], str) with raises(exceptions.ValidationError): validation.check_instances(iter(["abc"]), str)
def test_check_instances_with_invalid_values(): with raises(exceptions.ValidationError): validation.check_instances('abc', compat.string_types) with raises(exceptions.ValidationError): validation.check_instances(['abc', 123], compat.string_types) with raises(exceptions.ValidationError): validation.check_instances(None, compat.string_types) with raises(exceptions.ValidationError): validation.check_instances([None], compat.string_types) with raises(exceptions.ValidationError): validation.check_instances(iter(['abc']), compat.string_types)
def lookup(self, uri=None, uris=None): """ Lookup the given URIs. If the URI expands to multiple tracks, the returned list will contain them all. :param uri: track URI :type uri: string or :class:`None` :param uris: track URIs :type uris: list of string or :class:`None` :rtype: list of :class:`mopidy.models.Track` if uri was set or {uri: list of :class:`mopidy.models.Track`} if uris was set. .. versionadded:: 1.0 The ``uris`` argument. .. deprecated:: 1.0 The ``uri`` argument. Use ``uris`` instead. """ if sum(o is not None for o in [uri, uris]) != 1: raise ValueError('Exactly one of "uri" or "uris" must be set') uris is None or validation.check_uris(uris) uri is None or validation.check_uri(uri) if uri: deprecation.warn('core.library.lookup:uri_arg') if uri is not None: uris = [uri] futures = {} results = {u: [] for u in uris} # TODO: lookup(uris) to backend APIs for backend, backend_uris in self._get_backends_to_uris(uris).items(): if backend_uris: for u in backend_uris: futures[(backend, u)] = backend.library.lookup(u) for (backend, u), future in futures.items(): with _backend_error_handling(backend): result = future.get() if result is not None: validation.check_instances(result, models.Track) # TODO Consider making Track.uri field mandatory, and # then remove this filtering of tracks without URIs. results[u] = [r for r in result if r.uri] if uri: return results[uri] return results
def _browse(self, uri): scheme = urllib.parse.urlparse(uri).scheme backend = self.backends.with_library_browse.get(scheme) if not backend: return [] with _backend_error_handling(backend): result = backend.library.browse(uri).get() validation.check_instances(result, models.Ref) return result return []
def _browse(self, uri): scheme = urlparse.urlparse(uri).scheme backend = self.backends.with_library_browse.get(scheme) if not backend: return [] with _backend_error_handling(backend): result = backend.library.browse(uri).get() validation.check_instances(result, models.Ref) return result return []
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 """ validation.check_uri(uri) uri_scheme = urllib.parse.urlparse(uri).scheme backend = self.backends.with_playlists.get(uri_scheme, None) if not backend: return None with _backend_error_handling(backend): items = backend.playlists.get_items(uri).get() items is None or validation.check_instances(items, Ref) return items return None
def filter(self, criteria=None, **kwargs): """ Filter the tracklist by the given criterias. A criteria consists of a model field to check and a list of values to compare it against. If the model field matches one of the values, it may be returned. Only tracks that matches all the given criterias are returned. Examples:: # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID) filter({'tlid': [1, 2, 3, 4]}) # Returns track with URIs 'xyz' or 'abc' filter({'uri': ['xyz', 'abc']}) # Returns track with a matching TLIDs (1, 3 or 6) and a # matching URI ('xyz' or 'abc') filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']}) :param criteria: on or more criteria to match by :type criteria: dict, of (string, list) pairs :rtype: list of :class:`mopidy.models.TlTrack` .. deprecated:: 1.1 Providing the criteria via ``kwargs``. """ if kwargs: deprecation.warn('core.tracklist.filter:kwargs_criteria') criteria = criteria or kwargs tlids = criteria.pop('tlid', []) validation.check_query(criteria, validation.TRACKLIST_FIELDS) validation.check_instances(tlids, int) matches = self._tl_tracks for (key, values) in criteria.items(): matches = [ ct for ct in matches if getattr(ct.track, key) in values ] if tlids: matches = [ct for ct in matches if ct.tlid in tlids] return matches
def filter(self, criteria=None, **kwargs): """ Filter the tracklist by the given criterias. A criteria consists of a model field to check and a list of values to compare it against. If the model field matches one of the values, it may be returned. Only tracks that matches all the given criterias are returned. Examples:: # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID) filter({'tlid': [1, 2, 3, 4]}) # Returns track with URIs 'xyz' or 'abc' filter({'uri': ['xyz', 'abc']}) # Returns track with a matching TLIDs (1, 3 or 6) and a # matching URI ('xyz' or 'abc') filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']}) :param criteria: on or more criteria to match by :type criteria: dict, of (string, list) pairs :rtype: list of :class:`mopidy.models.TlTrack` .. deprecated:: 1.1 Providing the criteria via ``kwargs``. """ if kwargs: deprecation.warn('core.tracklist.filter:kwargs_criteria') criteria = criteria or kwargs tlids = criteria.pop('tlid', []) validation.check_query(criteria, validation.TRACKLIST_FIELDS) validation.check_instances(tlids, int) matches = self._tl_tracks for (key, values) in criteria.items(): matches = [ ct for ct in matches if getattr(ct.track, key) in values] if tlids: matches = [ct for ct in matches if ct.tlid in tlids] return matches
def filter(self, criteria): """ Filter the tracklist by the given criteria. Each rule in the criteria consists of a model field and a list of values to compare it against. If the model field matches any of the values, it may be returned. Only tracks that match all the given criteria are returned. Examples:: # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID) filter({'tlid': [1, 2, 3, 4]}) # Returns track with URIs 'xyz' or 'abc' filter({'uri': ['xyz', 'abc']}) # Returns track with a matching TLIDs (1, 3 or 6) and a # matching URI ('xyz' or 'abc') filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']}) :param criteria: one or more rules to match by :type criteria: dict, of (string, list) pairs :rtype: list of :class:`mopidy.models.TlTrack` """ tlids = criteria.pop("tlid", []) validation.check_query(criteria, validation.TRACKLIST_FIELDS) validation.check_instances(tlids, int) matches = self._tl_tracks for (key, values) in criteria.items(): matches = [ ct for ct in matches if getattr(ct.track, key) in values ] if tlids: matches = [ct for ct in matches if ct.tlid in tlids] return matches
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 uris: list of URIs to find images for :type uris: list of string :rtype: {uri: tuple of :class:`mopidy.models.Image`} .. versionadded:: 1.0 """ validation.check_uris(uris) futures = { backend: 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 backend, future in futures.items(): with _backend_error_handling(backend): if future.get() is None: continue validation.check_instance(future.get(), Mapping) for uri, images in future.get().items(): if uri not in uris: raise exceptions.ValidationError( f"Got unknown image URI: {uri}") validation.check_instances(images, models.Image) results[uri] += tuple(images) return results
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 uris: list of URIs to find images for :type uris: list of string :rtype: {uri: tuple of :class:`mopidy.models.Image`} .. versionadded:: 1.0 """ validation.check_uris(uris) futures = { backend: 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 backend, future in futures.items(): with _backend_error_handling(backend): if future.get() is None: continue validation.check_instance(future.get(), collections.Mapping) for uri, images in future.get().items(): if uri not in uris: raise exceptions.ValidationError("Got unknown image URI: %s" % uri) validation.check_instances(images, models.Image) results[uri] += tuple(images) return results
def test_check_instances_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_instances([1], compat.string_types) assert 'Expected a list of basestring, not [1]' == str(excinfo.value)
def add(self, tracks=None, at_position=None, uris=None): """ Add tracks to the tracklist. If ``uris`` is given instead of ``tracks``, the URIs are looked up in the library and the resulting tracks are added to the tracklist. If ``at_position`` is given, the tracks are inserted at the given position in the tracklist. If ``at_position`` is not given, the tracks are appended to the end of the tracklist. Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. :param tracks: tracks to add :type tracks: list of :class:`mopidy.models.Track` or :class:`None` :param at_position: position in tracklist to add tracks :type at_position: int or :class:`None` :param uris: list of URIs for tracks to add :type uris: list of string or :class:`None` :rtype: list of :class:`mopidy.models.TlTrack` .. versionadded:: 1.0 The ``uris`` argument. .. deprecated:: 1.0 The ``tracks`` argument. Use ``uris``. """ if sum(o is not None for o in [tracks, uris]) != 1: raise ValueError('Exactly one of "tracks" or "uris" must be set') tracks is None or validation.check_instances(tracks, Track) uris is None or validation.check_uris(uris) validation.check_integer(at_position or 0) if tracks: deprecation.warn("core.tracklist.add:tracks_arg") if tracks is None: tracks = [] track_map = self.core.library.lookup(uris=uris) for uri in uris: tracks.extend(track_map[uri]) tl_tracks = [] max_length = self.core._config["core"]["max_tracklist_length"] for track in tracks: if self.get_length() >= max_length: raise exceptions.TracklistFull( f"Tracklist may contain at most {max_length:d} tracks." ) tl_track = TlTrack(self._next_tlid, track) self._next_tlid += 1 if at_position is not None: self._tl_tracks.insert(at_position, tl_track) at_position += 1 else: self._tl_tracks.append(tl_track) tl_tracks.append(tl_track) if tl_tracks: self._increase_version() return tl_tracks
def test_check_instances_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_instances([1], str) assert "Expected a list of str, not [1]" == str(excinfo.value)
def test_check_instances_with_valid_values(): validation.check_instances([], int) validation.check_instances([1, 2], int) validation.check_instances((1, 2), int)
def add(self, tracks=None, at_position=None, uri=None, uris=None): """ Add tracks to the tracklist. If ``uri`` is given instead of ``tracks``, the URI is looked up in the library and the resulting tracks are added to the tracklist. If ``uris`` is given instead of ``uri`` or ``tracks``, the URIs are looked up in the library and the resulting tracks are added to the tracklist. If ``at_position`` is given, the tracks are inserted at the given position in the tracklist. If ``at_position`` is not given, the tracks are appended to the end of the tracklist. Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. :param tracks: tracks to add :type tracks: list of :class:`mopidy.models.Track` or :class:`None` :param at_position: position in tracklist to add tracks :type at_position: int or :class:`None` :param uri: URI for tracks to add :type uri: string or :class:`None` :param uris: list of URIs for tracks to add :type uris: list of string or :class:`None` :rtype: list of :class:`mopidy.models.TlTrack` .. versionadded:: 1.0 The ``uris`` argument. .. deprecated:: 1.0 The ``tracks`` and ``uri`` arguments. Use ``uris``. """ if sum(o is not None for o in [tracks, uri, uris]) != 1: raise ValueError( 'Exactly one of "tracks", "uri" or "uris" must be set') tracks is None or validation.check_instances(tracks, Track) uri is None or validation.check_uri(uri) uris is None or validation.check_uris(uris) validation.check_integer(at_position or 0) if tracks: deprecation.warn('core.tracklist.add:tracks_arg') if uri: deprecation.warn('core.tracklist.add:uri_arg') if tracks is None: if uri is not None: uris = [uri] tracks = [] track_map = self.core.library.lookup(uris=uris) for uri in uris: tracks.extend(track_map[uri]) tl_tracks = [] max_length = self.core._config['core']['max_tracklist_length'] for track in tracks: if self.get_length() >= max_length: raise exceptions.TracklistFull( 'Tracklist may contain at most %d tracks.' % max_length) tl_track = TlTrack(self._next_tlid, track) self._next_tlid += 1 if at_position is not None: self._tl_tracks.insert(at_position, tl_track) at_position += 1 else: self._tl_tracks.append(tl_track) tl_tracks.append(tl_track) if tl_tracks: self._increase_version() return tl_tracks