"""GraphQL utilities""" from connectors.logging import log from constants.participant_fields import ( PARTICIPANT_NAME, PARTICIPANT_VENDOR_ID, PARTICIPANT_SUBACCOUNT_ID, PARTICIPANT_SPOTIFY_ID, PARTICIPANT_APPLE_MUSIC_ID, PARTICIPANT_DDEX_ROLE, PARTICIPANT_ROLE, PARTICIPANT_CATEGORY, PARTICIPANT_ID, PARTICIPATIONS, PERFORMERS, PUBLISHERS, ) from constants.fields import ( ISRC, VENDOR_ID, SUBACCOUNT_ID, ARTIST_NAME, ARTIST_ROLE, ) from constants.localization_definitions import ( PRODUCT_LOCALIZATIONS_FIELD, TRACK_LOCALIZATIONS_FIELD, ) def format_label_participant( name, vendor_id, subaccount_id, spotify_id, apple_music_id, allow_nulls=False): """Format a ParticipantCreateInput type for mutation. Running this with `allow_nulls=False` prevents updating a participant with a blank Spotify or Apple ID. Args: name: (str) The Proper name of the participant vendor_id: (int) The associated label ID subaccount_id: (int) The associated subaccount ID spotify_id: (str) The participant's Spotify ID apple_music_id (str): The participant's Apple ID allow_nulls (bool): If True, allow null values for Spotify and Apple Id's Returns: dict: A dict of appropriately formatted key, value pairs. """ ret_data = { 'data': { PARTICIPANT_NAME: name, }, PARTICIPANT_VENDOR_ID: int(vendor_id), PARTICIPANT_SUBACCOUNT_ID: int(subaccount_id) if subaccount_id else 0, } # Add ID's if they exist if spotify_id: ret_data['data'][PARTICIPANT_SPOTIFY_ID] = spotify_id elif allow_nulls: ret_data['data'][PARTICIPANT_SPOTIFY_ID] = None if apple_music_id: ret_data['data'][PARTICIPANT_APPLE_MUSIC_ID] = \ str(apple_music_id) elif allow_nulls: ret_data['data'][PARTICIPANT_APPLE_MUSIC_ID] = None return ret_data def format_track_contributor( tuid: str, participations: list, performers: list, publishers: list) -> dict: """Format the track contributor data. Args: tuid (str): The track TUID. participations (list): The participations data. performers (list): The performers data. publishers (list): The publishers data. Returns: dict: The formatted data. """ ret_data = { "input": { "update": { "tracks": [str(tuid)], "body": {} } } } body = { PARTICIPATIONS: participations, PERFORMERS: performers, PUBLISHERS: publishers } if performers and isinstance(performers, list): body['performers'] = performers if publishers and isinstance(publishers, list): body['publishers'] = publishers ret_data["input"]["update"]["body"] = body return ret_data def transform_track_participants_isrc( track_list, participant_list, mapping_dict=None): """Transform a track_list to dict of ISRC's with lists of tuples of participant roles and names. Args: track_list (list): List of dicts of track data participant_list (list): List of tuples of participant data Example output: { 'USAT21803701': { 'vendor_id': 23213, 'subaccount_id': 2342, 'participations: [ { 'name': 'Bazzi', 'ddex_role': 'Recording Engineer', 'role': 'Recording Engineer', 'category': 'Studio Personnel', 'participant_id': 'a19d08be-b07f-4d22-a29b-0797014e3d48' }, { 'name': 'Bazzi', 'ddex_role': 'Programmer', 'role': 'Programmer', 'category': 'Studio Personnel', 'participant_id': '67bf9c19-66a1-449f-8049-9bc726acf365' }, { 'name': 'Kevin White', 'ddex_role': 'Composer', 'role': 'Arranger', 'category': 'Compositional', 'participant_id': '67bf9c19-66a1-449f-8049-9bc726acf365' }, { 'name': 'Mike Woods', 'ddex_role': 'Composer', 'role': 'A & R Coordinator', 'category': 'Label Personnel', 'participant_id': '67bf9c19-66a1-449f-8049-9bc726acf365' }, { 'name': 'Andrew Bazzi', 'ddex_role': 'Artist', 'role': 'String Engineer', 'category': 'Studio Personnel', 'participant_id': '67bf9c19-66a1-449f-8049-9bc726acf365' } ] } }""" if not mapping_dict: raise ValueError( 'Mapping dict is required. Set ROLE_MAPPING in env.') track_dict = {} for track in track_list: isrc = track[ISRC] vendor_id = track[VENDOR_ID] subaccount_id = track[SUBACCOUNT_ID] if not track_dict.get(isrc): track_dict[isrc] = { PARTICIPANT_VENDOR_ID: vendor_id, PARTICIPANT_SUBACCOUNT_ID: subaccount_id, PARTICIPATIONS: [], } # Get participant_id from participant_list for participant in participant_list: if ( track[ARTIST_NAME] == participant[0] and vendor_id == participant[1] and subaccount_id == participant[2] ): participant_id = participant[3] break # <- Guaranteed single match as our iterable is a set # More elegant, but slower, and less readable # participant_id = [ # p[3] for p in participant_list if # p[0] == track[ARTIST_NAME] and # p[1] == vendor_id and # p[2] == subaccount_id # ][0] # <- Single item like above # Map WMG roles to Orchard roles and categories mapped_role = mapping_dict.get(track[ARTIST_ROLE]) if mapped_role: track_dict[isrc][PARTICIPATIONS].append( { PARTICIPANT_NAME: track[ARTIST_NAME], PARTICIPANT_DDEX_ROLE: track[ARTIST_ROLE], PARTICIPANT_ROLE: mapped_role['role'], PARTICIPANT_CATEGORY: mapped_role['category'], PARTICIPANT_ID: participant_id, } ) else: # log.error( # continue raise Exception( f"No Orchard role found for '{track[ARTIST_ROLE]}'") if not track_dict[isrc][PARTICIPATIONS]: log.warning(f"No participants found for ISRC: {isrc}") return track_dict def format_track_label_participations( isrc, vendor_id, subaccount_id, participations): """Format track participations for GraphQL mutation. Args: isrc (str): ISRC vendor_id (int): Vendor ID subaccount_id (int): Subaccount ID participations (list): List of dicts of participations. Example: [ { 'name': 'Ted Franklin', 'ddex_role': 'A&R', 'role': 'A&R Specialist', 'category': 'Label Personnel', 'participant_id': 'a19d08be-b07f-4d22-a29b-0797014e3d48' }, { 'name': 'Bill Johnson', 'ddex_role': 'Drum Mixer', 'role': 'Drum Mixer', 'category': 'Studio Personnel', 'participant_id': '67bf9c19-66a1-449f-8049-9bc726acf365' } ] Returns: dict: A dict of appropriately formatted key, value pairs. Example: { 'isrc': 'USAT21803701', 'vendorId': 23213, 'subaccountId': 2342, 'participations': [ { 'participantId': 'a19d08be-b07f-4d22-a29b-0797014e3d48', 'role': 'A&R Specialist', # 'category': 'Label Personnel', # 'name': 'Ted Franklin', 'sequenceNumber': 1 }, { 'participantId': '67bf9c19-66a1-449f-8049-9bc726acf365', 'role': 'Drum Mixer', # 'category': 'Studio Personnel', # 'name': 'Bill Johnson', 'sequenceNumber': 2 } ] } """ # Create a list of dicts of formatted participations formatted_participations = [] # TODO: participationRoleCategoryName is only available per track, not per # participation. This means that if a track has multiple participants # with different categories, we'll need to make multiple calls to # setLabelSoundRecordingParticipations, one per category. This is # inefficient, but I don't see a way around it. This means we need to pre- # process the data to group by category, then by track, with all # participants in a given category for a given track in a single call. # This is an arbitrary index number that determines some order... view? # I assume this sequence number must be handled if participation updates # are performed during release correction? sequence_num = 1 for participation in participations: formatted_participations.append( { 'labelParticipantId': participation[PARTICIPANT_ID], 'participationRoleName': participation[PARTICIPANT_ROLE], 'sequenceNumber': sequence_num, } ) sequence_num += 1 # Create a dict of formatted track participations formatted_track_participations = { 'isrc': isrc, 'vendorId': int(vendor_id), 'subaccountId': int(subaccount_id), PARTICIPATIONS: formatted_participations } return formatted_track_participations def format_product_id_by_upc(upc: str) -> dict: """Format the payload for get_product_id_by_upc(). Args: upc (str): The UPC. Returns: dict: The formatted data. """ return { 'upc': upc, } def format_product_by_upc(upc: str) -> dict: """Format the payload for get_product_by_upc(). Same payload as get_product_id_by_upc(). Args: upc (str): The UPC. Returns: dict: The formatted data. """ return format_product_id_by_upc(upc) def format_track_localizations_update( tuid: str, localizations: list, participations: list) -> dict: """Format the track contributor data. Args: tuid (str): The track TUID. localizations (list): The localizations data. participations (list): The participations data. Returns: dict: The formatted data. """ ret_data = { "input": { "update": { "tracks": [str(tuid)], "body": {} } } } body = { TRACK_LOCALIZATIONS_FIELD: localizations, PARTICIPATIONS: participations, } ret_data["input"]["update"]["body"] = body return ret_data def format_product_localization_update( product_id: str, product_localizations: list) -> dict: """Format the release localization update data. Args: product_id (str): The release ID. localizations (list): The localizations data as a list of dicts. Returns: dict: The formatted data. """ ret_data = { "input": { "productId": product_id, PRODUCT_LOCALIZATIONS_FIELD: product_localizations, } } return ret_data