def filter(self, criteria=None, **kwargs): """ Filter playlists by the given criterias. Examples:: # Returns track with name 'a' filter({'name': 'a'}) # Returns track with URI 'xyz' filter({'uri': 'xyz'}) # Returns track with name 'a' and 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` .. deprecated:: 1.0 Use :meth:`as_list` and filter yourself. """ deprecation.warn('core.playlists.filter') criteria = criteria or kwargs validation.check_query(criteria, validation.PLAYLIST_FIELDS, list_values=False) matches = self.playlists # TODO: stop using self playlists for (key, value) in criteria.iteritems(): matches = filter(lambda p: getattr(p, key) == value, matches) return matches
def filter(self, criteria=None, **kwargs): """ Filter playlists by the given criterias. Examples:: # Returns track with name 'a' filter({'name': 'a'}) # Returns track with URI 'xyz' filter({'uri': 'xyz'}) # Returns track with name 'a' and 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` .. deprecated:: 1.0 Use :meth:`as_list` and filter yourself. """ deprecation.warn('core.playlists.filter') criteria = criteria or kwargs validation.check_query( criteria, validation.PLAYLIST_FIELDS, list_values=False) matches = self.playlists # TODO: stop using self playlists for (key, value) in criteria.iteritems(): matches = filter(lambda p: getattr(p, key) == value, matches) return matches
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 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 test_check_query_random_iterables(): for value in None, tuple(), list(), 'abc': with raises(exceptions.ValidationError): validation.check_query(value)
def test_check_query_valid_values(): for value in {}, {'any': []}, {'any': ['abc']}: validation.check_query(value)
def test_check_values_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_query({'any': 'abc'}) assert 'Expected "any" to be list of strings, not' in str(excinfo.value)
def test_check_query_invalid_values(): for value in '', None, 'foo', 123, [''], [None], iter(['abc']): with raises(exceptions.ValidationError): validation.check_query({'any': value})
def test_check_field_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_query({'wrong': ['abc']}) assert 'Expected query field to be one of ' in str(excinfo.value)
def test_check_mapping_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_query([]) assert 'Expected a query dictionary, not []' == str(excinfo.value)
def test_check_query_invalid_fields(): for value in "wrong", "bar", "foo", "tlid": with raises(exceptions.ValidationError): validation.check_query({value: []})
def test_check_query_valid_values(): for value in {}, {"any": []}, {"any": ["abc"]}: validation.check_query(value)
def test_check_query_invalid_values(): for value in "", None, "foo", 123, [""], [None], iter(["abc"]): with raises(exceptions.ValidationError): validation.check_query({"any": value})
def test_check_field_error_message(): with raises(exceptions.ValidationError) as excinfo: validation.check_query({"wrong": ["abc"]}) assert "Expected query field to be one of " in str(excinfo.value)
def search(self, query=None, uris=None, exact=False, **kwargs): """ Search the library for tracks where ``field`` contains ``values``. If ``uris`` is given, the search is limited to results from within the URI roots. For example passing ``uris=['file:']`` will limit the search to the local backend. Examples:: # Returns results matching 'a' in any backend search({'any': ['a']}) # Returns results matching artist 'xyz' in any backend search({'artist': ['xyz']}) # Returns results matching 'a' and 'b' and artist 'xyz' in any # backend search({'any': ['a', 'b'], 'artist': ['xyz']}) # Returns results matching 'a' if within the given URI roots # "file:///media/music" and "spotify:" search({'any': ['a']}, uris=['file:///media/music', 'spotify:']) # Returns results matching artist 'xyz' and 'abc' in any backend search({'artist': ['xyz', 'abc']}) :param query: one or more queries to search for :type query: dict :param uris: zero or more URI roots to limit the search to :type uris: list of string or :class:`None` :param exact: if the search should use exact matching :type exact: :class:`bool` :rtype: list of :class:`mopidy.models.SearchResult` .. versionadded:: 1.0 The ``exact`` keyword argument, which replaces :meth:`find_exact`. .. deprecated:: 1.0 Previously, if the query was empty, and the backend could support it, all available tracks were returned. This has not changed, but it is strongly discouraged. No new code should rely on this behavior. .. deprecated:: 1.1 Providing the search query via ``kwargs`` is no longer supported. """ query = _normalize_query(query or kwargs) uris is None or validation.check_uris(uris) query is None or validation.check_query(query) validation.check_boolean(exact) if kwargs: deprecation.warn('core.library.search:kwargs_query') if not query: deprecation.warn('core.library.search:empty_query') futures = {} for backend, backend_uris in self._get_backends_to_uris(uris).items(): futures[backend] = backend.library.search(query=query, uris=backend_uris, exact=exact) # Some of our tests check for LookupError to catch bad queries. This is # silly and should be replaced with query validation before passing it # to the backends. reraise = (TypeError, LookupError) results = [] for backend, future in futures.items(): try: with _backend_error_handling(backend, reraise=reraise): result = future.get() if result is not None: validation.check_instance(result, models.SearchResult) results.append(result) except TypeError: backend_name = backend.actor_ref.actor_class.__name__ logger.warning( '%s does not implement library.search() with "exact" ' 'support. Please upgrade it.', backend_name) return results
def search(self, query=None, uris=None, exact=False, **kwargs): """ Search the library for tracks where ``field`` contains ``values``. If ``uris`` is given, the search is limited to results from within the URI roots. For example passing ``uris=['file:']`` will limit the search to the local backend. Examples:: # Returns results matching 'a' in any backend search({'any': ['a']}) # Returns results matching artist 'xyz' in any backend search({'artist': ['xyz']}) # Returns results matching 'a' and 'b' and artist 'xyz' in any # backend search({'any': ['a', 'b'], 'artist': ['xyz']}) # Returns results matching 'a' if within the given URI roots # "file:///media/music" and "spotify:" search({'any': ['a']}, uris=['file:///media/music', 'spotify:']) # Returns results matching artist 'xyz' and 'abc' in any backend search({'artist': ['xyz', 'abc']}) :param query: one or more queries to search for :type query: dict :param uris: zero or more URI roots to limit the search to :type uris: list of string or :class:`None` :param exact: if the search should use exact matching :type exact: :class:`bool` :rtype: list of :class:`mopidy.models.SearchResult` .. versionadded:: 1.0 The ``exact`` keyword argument, which replaces :meth:`find_exact`. .. deprecated:: 1.0 Previously, if the query was empty, and the backend could support it, all available tracks were returned. This has not changed, but it is strongly discouraged. No new code should rely on this behavior. .. deprecated:: 1.1 Providing the search query via ``kwargs`` is no longer supported. """ query = _normalize_query(query or kwargs) uris is None or validation.check_uris(uris) query is None or validation.check_query(query) validation.check_boolean(exact) if kwargs: deprecation.warn("core.library.search:kwargs_query") if not query: deprecation.warn("core.library.search:empty_query") futures = {} for backend, backend_uris in self._get_backends_to_uris(uris).items(): futures[backend] = backend.library.search(query=query, uris=backend_uris, exact=exact) # Some of our tests check for LookupError to catch bad queries. This is # silly and should be replaced with query validation before passing it # to the backends. reraise = (TypeError, LookupError) results = [] for backend, future in futures.items(): try: with _backend_error_handling(backend, reraise=reraise): result = future.get() if result is not None: validation.check_instance(result, models.SearchResult) results.append(result) except TypeError: backend_name = backend.actor_ref.actor_class.__name__ logger.warning( '%s does not implement library.search() with "exact" ' "support. Please upgrade it.", backend_name ) return results
def test_check_query_invalid_fields(): for value in 'wrong', 'bar', 'foo', 'tlid': with raises(exceptions.ValidationError): validation.check_query({value: []})
def search(self, query, uris=None, exact=False): """ Search the library for tracks where ``field`` contains ``values``. ``field`` can be one of ``uri``, ``track_name``, ``album``, ``artist``, ``albumartist``, ``composer``, ``performer``, ``track_no``, ``genre``, ``date``, ``comment``, or ``any``. If ``uris`` is given, the search is limited to results from within the URI roots. For example passing ``uris=['file:']`` will limit the search to the local backend. Examples:: # Returns results matching 'a' in any backend search({'any': ['a']}) # Returns results matching artist 'xyz' in any backend search({'artist': ['xyz']}) # Returns results matching 'a' and 'b' and artist 'xyz' in any # backend search({'any': ['a', 'b'], 'artist': ['xyz']}) # Returns results matching 'a' if within the given URI roots # "file:///media/music" and "spotify:" search({'any': ['a']}, uris=['file:///media/music', 'spotify:']) # Returns results matching artist 'xyz' and 'abc' in any backend search({'artist': ['xyz', 'abc']}) :param query: one or more queries to search for :type query: dict :param uris: zero or more URI roots to limit the search to :type uris: list of string or :class:`None` :param exact: if the search should use exact matching :type exact: :class:`bool` :rtype: list of :class:`mopidy.models.SearchResult` .. versionadded:: 1.0 The ``exact`` keyword argument. """ query = _normalize_query(query) uris is None or validation.check_uris(uris) validation.check_query(query) validation.check_boolean(exact) if not query: return [] futures = {} for backend, backend_uris in self._get_backends_to_uris(uris).items(): futures[backend] = backend.library.search(query=query, uris=backend_uris, exact=exact) # Some of our tests check for LookupError to catch bad queries. This is # silly and should be replaced with query validation before passing it # to the backends. reraise = (TypeError, LookupError) results = [] for backend, future in futures.items(): try: with _backend_error_handling(backend, reraise=reraise): result = future.get() if result is not None: validation.check_instance(result, models.SearchResult) results.append(result) except TypeError: backend_name = backend.actor_ref.actor_class.__name__ logger.warning( '%s does not implement library.search() with "exact" ' "support. Please upgrade it.", backend_name, ) return results