First, we need to initialize the MusicBrainz client with a user agent. This helps identify the application when making
requests to the MusicBrainz API.
We will create a function music_graph_model that takes an artist's name and a maximum depth of recursion.
This function will be used to create a Graph Model.
result=musicbrainzngs.search_artists(query=name,strict=True,artist=name)sleep(1)# Sleep for 1 second to avoid rate limitingroot_artist=result.get('artist-list',[])[0]ifresultelseNone
We will define a generator function artists to yield parent and related artists recursively.
We will retrieve the artist data from the cache if it exists, otherwise, we will fetch it from the MusicBrainz API.
Then we will yield the parent and related artists. If the depth is less than the maximum depth, we will recursively
yield artists for each related artist as a starting point.
defartists(parent_artist,artist,depth):artist_id=artist.get('id')ifartist_idnotinartists_cache:artists_cache[artist_id]=musicbrainzngs.get_artist_by_id(id=artist_id,includes=['artist-rels']).get('artist')sleep(0.1)# Sleep for 0.1 second to avoid rate limitingartist=artists_cache.get(artist_id)yieldparent_artist,artistifdepth<max_depth:related_artist_ids=set()foriteminartist.get('artist-relation-list',[]):related_artist=item.get('artist')related_artist_id=related_artist.get('id')ifrelated_artist_idnotinrelated_artist_ids:related_artist_ids.add(related_artist_id)yield fromartists(artist,related_artist,depth+1)
We will define the node model for the graph using the node decorator.
Using the musicbrainz artist type as the node type, the artist ID as the node key, and the artist name as the label.
The Multiplicity.FIRST option ensures that only the first occurrence of an artist is included in the graph.
importgraphinateimportmusicbrainzngsfromtimeimportsleepimportoperatorimportdiskcacheimportpathlibdefcache_dir():current_script_path=pathlib.Path(__file__).resolve()parent_dir=current_script_path.parentreturn(parent_dir/'cache').as_posix()artists_cache=diskcache.Cache(directory=cache_dir(),eviction_policy='none')defmusic_graph_model(name:str,max_depth:int=0):graph_model=graphinate.model(f'{name.capitalize()} Graph')result=musicbrainzngs.search_artists(query=name,strict=True,artist=name)sleep(1)# Sleep for 1 second to avoid rate limitingroot_artist=result.get('artist-list',[])[0]ifresultelseNonedefartists(parent_artist,artist,depth):artist_id=artist.get('id')ifartist_idnotinartists_cache:artists_cache[artist_id]=musicbrainzngs.get_artist_by_id(id=artist_id,includes=['artist-rels']).get('artist')sleep(0.1)# Sleep for 0.1 second to avoid rate limitingartist=artists_cache.get(artist_id)yieldparent_artist,artistifdepth<max_depth:related_artist_ids=set()foriteminartist.get('artist-relation-list',[]):related_artist=item.get('artist')related_artist_id=related_artist.get('id')ifrelated_artist_idnotinrelated_artist_ids:related_artist_ids.add(related_artist_id)yield fromartists(artist,related_artist,depth+1)defartist_type(value):returnvalue.get('type','_UNKNOWN_')@graph_model.node(artist_type,key=operator.itemgetter('id'),label=operator.itemgetter('name'),multiplicity=graphinate.Multiplicity.FIRST,)defnode():yielded=set()fora,binartists(None,root_artist,0):ifaand((a_id:=a.get('id'))notinyielded):yielded.add(a_id)yieldaifband((b_id:=b.get('id'))notinyielded):yielded.add(b_id)yieldb@graph_model.edge()defedge():fora,binartists(None,root_artist,0):ifa:yield{'source':a.get('id'),'target':b.get('id')}returngraph_model
We will generate the GraphModel for the selected artists.
First creating a GraphModel for each artist and then combining them into a single model.
In this case, we will use the reduce function from the functools module to combine the models using the
operator.add function. It leverages the GrapModel support of the + operation.
Using the GraphQLBuilder we generate a GraphQL Schema (i.e. strawberry-graphql schema)
and use the graphql.server function to create and run the GraphQL server.
# Use the GraphQLBuilder Builderbuilder=graphinate.builders.GraphQLBuilder(graph_model)# build the strawberry-graphql schemaschema=builder.build()# plot the graph using matplotlibgraphinate.graphql.server(schema)
if__name__=='__main__':fromguiimportListboxChooserartist_names=['Alice in Chains','Beatles','Caravan','Charles Mingus','Dave Brubeck','Dave Douglas','David Bowie','Deep Purple','Dire Straits','Emerson, Lake & Palmer','Foo Fighters','Frank Zappa','Genesis','Gentle Giant','Herbie Hancock','Jethro Tull','John Coltrane','John Scofield','John Zorn','Ken Vandermark','King Crimson','Led Zeppelin','Mahavishnu Orchestra','Miles Davis','Nirvana','Ornette Coleman','Paul McCartney','Pearl Jam','Pink Floyd','Police','Porcupine Tree','Radiohead','Red Hot Chili Peppers','Return to Forever','Rush','Smashing Pumpkins','Soft Machine','Soundgarden','Stone Temple Pilots','System of a Down','Thelonious Monk','Weather Report','Wings','Yes',]listbox_chooser=ListboxChooser('Choose Artist/s',{name:namefornameinartist_names})models=(music_graph_model(a,2)for_,ainlistbox_chooser.get_choices())model=reduce(operator.add,models)# Use the GraphQLBuilder Builderbuilder=graphinate.builders.GraphQLBuilder(graph_model)# build the strawberry-graphql schemaschema=builder.build()# plot the graph using matplotlibgraphinate.server(schema)