From d16e8c8b129cf00001817f56c47ac3b92e7e3fd2 Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Thu, 30 Dec 2021 00:40:37 +0100 Subject: [PATCH] Add browse_collections method According to the API page, it's possible to find collections that are linked to a bunch of other entities: https://musicbrainz.org/doc/MusicBrainz_API#Linked_entities --- examples/browse.py | 34 ++++++++++++++++++++++++++++++++++ musicbrainzngs/musicbrainz.py | 32 ++++++++++++++++++++++++++++---- test/test_browse.py | 7 +++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100755 examples/browse.py diff --git a/examples/browse.py b/examples/browse.py new file mode 100755 index 00000000..2739f612 --- /dev/null +++ b/examples/browse.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" +Browse entities on musicbrainz +""" +import json +import sys +from argparse import ArgumentParser + +import musicbrainzngs + +musicbrainzngs.set_useragent( + "python-musicbrainzngs-example", + "0.1", + "https://github.com/alastair/python-musicbrainzngs/", +) + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument( + "entity_type", + choices=["collection"], + help="The type of entity we're trying to get" + ) + parser.add_argument("linked_entity_type", help="The entity type it's linked to") + parser.add_argument("mbid", help="The ID of the linked entity") + args = parser.parse_args() + + result = {} + if args.entity_type == "collection": + result = musicbrainzngs.browse_collections(args.linked_entity_type, args.mbid) + else: + print("Invalid entity type passed", file=sys.stderr) + exit(1) + print(json.dumps(result, indent=2)) diff --git a/musicbrainzngs/musicbrainz.py b/musicbrainzngs/musicbrainz.py index ae1d9a48..a55480a1 100644 --- a/musicbrainzngs/musicbrainz.py +++ b/musicbrainzngs/musicbrainz.py @@ -83,6 +83,7 @@ } VALID_BROWSE_INCLUDES = { 'artist': ["aliases"] + TAG_INCLUDES + RATING_INCLUDES + RELATION_INCLUDES, + 'collection': [], 'event': ["aliases"] + TAG_INCLUDES + RATING_INCLUDES + RELATION_INCLUDES, 'label': ["aliases"] + TAG_INCLUDES + RATING_INCLUDES + RELATION_INCLUDES, 'recording': ["artist-credits", "isrcs"] + TAG_INCLUDES + RATING_INCLUDES + RELATION_INCLUDES, @@ -94,6 +95,11 @@ 'work': ["aliases", "annotation"] + TAG_INCLUDES + RATING_INCLUDES + RELATION_INCLUDES, } +VALID_BROWSE_LINK_ENTITIES = { + 'collection': ["area", "artist", "editor", "event", "label", "place", "recording", "release", "release-group", + "work"] +} + #: These can be used to filter whenever releases are includes or browsed VALID_RELEASE_TYPES = [ "nat", @@ -284,6 +290,10 @@ def _docstring_browse(entity): includes = list(VALID_BROWSE_INCLUDES.get(entity, [])) return _docstring_impl("includes", includes) +def _docstring_browse_links(entity): + includes = list(VALID_BROWSE_LINK_ENTITIES.get(entity, [])) + return _docstring_impl("link_entities", includes) + def _docstring_search(entity): search_fields = list(VALID_SEARCH_FIELDS.get(entity, [])) return _docstring_impl("fields", search_fields) @@ -710,7 +720,7 @@ def _get_auth_type(entity, id, includes): return AUTH_NO -def _do_mb_query(entity, id, includes=[], params={}): +def _do_mb_query(entity, id, includes=[], params={}, auth_type=None): """Make a single GET call to the MusicBrainz XML API. `entity` is a string indicated the type of object to be retrieved. The id may be empty, in which case the query is a search. `includes` is a list @@ -722,7 +732,7 @@ def _do_mb_query(entity, id, includes=[], params={}): if not isinstance(includes, list): includes = [includes] _check_includes(entity, includes) - auth_required = _get_auth_type(entity, id, includes) + auth_required = auth_type or _get_auth_type(entity, id, includes) args = dict(params) if len(includes) > 0: inc = " ".join(includes) @@ -1064,7 +1074,7 @@ def get_works_by_iswc(iswc, includes=[]): return _do_mb_query("iswc", iswc, includes) -def _browse_impl(entity, includes, limit, offset, params, release_status=[], release_type=[]): +def _browse_impl(entity, includes, limit, offset, params, release_status=[], release_type=[], auth_type=None): includes = includes if isinstance(includes, list) else [includes] valid_includes = VALID_BROWSE_INCLUDES[entity] _check_includes_impl(includes, valid_includes) @@ -1078,7 +1088,7 @@ def _browse_impl(entity, includes, limit, offset, params, release_status=[], rel if offset: p["offset"] = offset filterp = _check_filter_and_make_params(entity, includes, release_status, release_type) p.update(filterp) - return _do_mb_query(entity, "", includes, p) + return _do_mb_query(entity, "", includes, p, auth_type=auth_type) # Browse methods # Browse include are a subset of regular get includes, so we check them here @@ -1096,6 +1106,20 @@ def browse_artists(recording=None, release=None, release_group=None, "work": work} return _browse_impl("artist", includes, limit, offset, params) +@_docstring_browse_links("collection") +def browse_collections(entity_type, entity_mbid, includes=None, limit=None, offset=None): + """Get all collections linked to a given entity. + You need to give one MusicBrainz ID. + + *Available entity_types*: {link_entities}""" + includes = includes or [] + valid_linked_entities = VALID_BROWSE_LINK_ENTITIES["collection"] + if entity_type not in valid_linked_entities: + raise ValueError("Not a valid linked entity type", valid_linked_entities) + + params = dict([(entity_type, entity_mbid)]) + return _browse_impl("collection", includes, limit, offset, params, auth_type=AUTH_IFSET) + @_docstring_browse("event") def browse_events(area=None, artist=None, place=None, includes=[], limit=None, offset=None): diff --git a/test/test_browse.py b/test/test_browse.py index 0e02bcd6..ae052016 100644 --- a/test/test_browse.py +++ b/test/test_browse.py @@ -59,6 +59,13 @@ def test_browse_artist(self): musicbrainzngs.browse_artists(work=work) self.assertEqual("https://musicbrainz.org/ws/2/artist/?work=deb27b88-cf41-4f7c-b3aa-bc3268bc3c02", self.opener.get_url()) + def test_browse_collection(self): + musicbrainzngs.browse_collections("release", "5fa83c79-ef7d-42ad-8a09-30a1d4c35f63") + self.assertEqual("https://musicbrainz.org/ws/2/collection/?release=5fa83c79-ef7d-42ad-8a09-30a1d4c35f63", self.opener.get_url()) + + with self.assertRaises(ValueError): + musicbrainzngs.browse_collections("I don't exist", "just whatever") + def test_browse_event(self): area = "f03d09b3-39dc-4083-afd6-159e3f0d462f" musicbrainzngs.browse_events(area=area)