"""GraphQL logic.""" from time import sleep from types import SimpleNamespace from connectors.logging import logger as log from connectors.graphql import GraphQLBackoffConnector, \ GraphQLError from constants import fields from constants import graphql_queries from constants.graphql_headers import GRASS_ACCOUNT_TYPE from utils.graphql_utils import ( format_label_participant, format_track_label_participations ) from config import ( GRAPHQL_GATEWAY_URL, APPLICATION_NAME, OA_USER, ORCH_HEADER_IDENTITY_ID, ORCH_HEADER_PROFILE_ID, ORCH_HEADER_PROFILE_TYPE, ORCH_HEADER_ROLE, PRINT_STACK_TRACES, ALLOW_NULL_ARTIST_IDS ) if PRINT_STACK_TRACES: from traceback_with_variables import activate_by_import # noqa # create GraphQL connector graphql_conn = GraphQLBackoffConnector(GRAPHQL_GATEWAY_URL, APPLICATION_NAME) graphql_conn.set_headers({ 'Orchard-Profile-Type': ORCH_HEADER_PROFILE_TYPE, 'Orchard-Profile-Id': ORCH_HEADER_PROFILE_ID, 'Orchard-Identity-Id': ORCH_HEADER_IDENTITY_ID, 'Orchard-Roles': ORCH_HEADER_ROLE, 'Orchard-User-Id': OA_USER, 'GRASS-ACCOUNT-TYPE': GRASS_ACCOUNT_TYPE, }) # Debug # headers = graphql_conn.get_headers() # log.info('Headers:') # for key in headers: # log.info(f'{key}: {headers[key]}') def get_or_create_all_label_participants(participant_list, sleep_time=None): """Create all label participants from a list, and return the same list with uuids. Args: participant_list (list): A list of tuples containing the following values: (name, vendor_id, subaccount_id). Returns: list: A list of tuples containing the following values: (name, vendor_id, subaccount_id, uuid). """ # Init return list return_list = [] error_list = [] if not sleep_time: sleep_time = 0.1 target = len(participant_list) count = 0 # Loop and get / create participant uuids for participant in participant_list: count += 1 log.info(f'Processing participant {count} of {target}') try: name, vendor_id, subaccount_id, spotify_id, apple_music_id = \ participant log.info(f'Retrieving participant object for {name}') participant_id, uuid, error = get_or_create_label_participant( name=name, vendor_id=vendor_id, subaccount_id=subaccount_id, spotify_id=spotify_id, apple_music_id=apple_music_id ) if error: log.error('Error cementing participant.') # Append error to error list error_list.append(error) else: log.info(f'Participant {name} cemented.') # Append uuid to participant tuple return_list.append( (name, vendor_id, subaccount_id, participant_id, spotify_id, apple_music_id )) except Exception as e: # Fashion default error fields status = 'python_error' err_code = 0 status_text = 'Python Error' err_msg = str(e) # Push error metadata to method result object error = { 'name': name, 'vendor_id': vendor_id, 'subaccount_id': subaccount_id, 'status': status, 'error_code': err_code, 'status_text': status_text, 'message': err_msg } # Notify user of Python logic error msg = f'Python Error - Name {name} ' \ 'get_or_create_all_label_participants() call ' \ 'failed - Error Code: ' \ f'{status} - {err_code} : {status_text} : {err_msg}' log.error(msg) sleep(sleep_time) return return_list, error_list def get_or_create_label_participant( name, vendor_id, subaccount_id, spotify_id=None, apple_music_id=None): """Create a single label participant. Args: name (str): The name of the participant. vendor_id (int): The vendor ID. subaccount_id (int): The subaccount ID. spotify_id (str): The Spotify ID. apple_music_id (str|int): The Apple Music ID. Returns: str: The participant ID. str: The participant UUID. dict: The error dictionary. """ uuid = None error = None if not name or not vendor_id or subaccount_id is None: log.error( 'Name, vendor_id, and subaccount_id (or 0) are required.' f'name: {name}, vendor_id: {vendor_id}, ' f'subaccount_id: {subaccount_id}' ) raise ValueError('Name, vendor_id, and subaccount_id are required.') log.info( f'Cementing participant ID for {name} ' f'on vendor: {vendor_id}, ' f'subaccount: {subaccount_id} ' f'with spotify_id: {spotify_id} ' f'and apple_music_id: {apple_music_id}' ) create_label_participant_data = format_label_participant( name=name, vendor_id=vendor_id, subaccount_id=subaccount_id, spotify_id=spotify_id, apple_music_id=str(apple_music_id) if apple_music_id else None, allow_nulls=ALLOW_NULL_ARTIST_IDS ) try: # Adjust header for each label graphql_conn.set_headers({'GRASS-ACCOUNT-ID': str(vendor_id)}) # Get or create participant create_participant_response = graphql_conn.execute( graphql_queries.GET_OR_CREATE_LABEL_PARTICIPANT, create_label_participant_data ) # ['data']['product'] except GraphQLError as ge: # Push error metadata to method result object error = { 'name': name, 'vendor_id': vendor_id, 'subaccount_id': subaccount_id, 'status': 'graphql_error', 'error_code': ge.code, 'status_text': 'GraphQL Error', 'message': ge.message } # Notify user of GraphQL error msg = f'GraphQL Call Error - \'{name}\' on vendor {vendor_id}, ' \ f'subaccount {subaccount_id} ' \ 'label participant creation call failed - Error Code: ' \ f'graphql_error - {ge.code} : GraphQL ' \ f'Error : {ge.message}' log.error(msg) else: # Split due to length data = create_participant_response['data']['createLabelParticipant'] uuid = data.get('uuid') participant_id = data.get('id') if uuid: log.info(f'Participant UUID: {uuid}') if participant_id: log.info(f'Participant ID: {participant_id}') if not uuid and not participant_id: log.error(f'No UUID or participant ID returned for {name}.') # return create_partipant_result['data'] return participant_id, uuid, error def set_all_label_participants_isrc(participant_by_isrc_dict, sleep_time=None): """Set all label sound recording participations for a list of ISRCs. Args: participant_by_isrc_dict (dict): A dict of ISRCs and their associated participation data. sleep_time (float): The time to sleep between GraphQL calls. """ # Default sleep if not sleep_time: sleep_time = 0.1 # Init method result object results = [] # Sanity check if not len(participant_by_isrc_dict): log.error('No eligible ISRC\'s passed.') return # Notify user graph_call_count = len(participant_by_isrc_dict) log.info(f'{graph_call_count} GraphQL calls will be made.') target = len(participant_by_isrc_dict.keys()) count = 0 for isrc, participation_data in participant_by_isrc_dict.items(): count += 1 log.info(f'Processing ISRC {count} of {target}') response = None error = None try: response, error = set_label_participants_isrc( isrc=isrc, vendor_id=participation_data[fields.PARTICIPATION_VENDOR_ID], participations=participation_data[fields.PARTICIPATIONS], subaccount_id=participation_data[fields.PARTICIPATION_SUBACCOUNT_ID] # noqa ) except Exception as e: # Fashion default error fields status = 'python_error' err_code = 0 status_text = 'Python Error' err_msg = str(e) # Push error metadata to method result object error = { 'isrc': isrc, 'status': status, 'error_code': err_code, 'status_text': status_text, 'message': err_msg } # Notify user of Python logic error msg = f'Python Error - ISRC \'{isrc}\' product approval call ' \ 'failed - Error Code: ' \ f'{status} - {err_code} : {status_text} : {err_msg}' log.error(msg) # Handles both GraphQL and Python errors if not error: log.info('Participants set.') results.append(SimpleNamespace(response=response, error=error)) sleep(sleep_time) return results def set_label_participants_isrc( isrc, vendor_id, participations, subaccount_id=None): """Set all label sound recording participations for a single ISRC.""" create_participant_response = None error = None if not subaccount_id: subaccount_id = 0 log.info(f'Setting participants on {isrc}.') # Last minute dedupe handled = set() # What we've seen as determined by key fields deduped = [] # The final list of all items with all fields for p in participations: dedupe_key = (p['name'], p['role'], p['category']) if not len(handled): # First item handled.add(dedupe_key) deduped.append(p) continue elif dedupe_key not in handled: # Not a duplicate handled.add(dedupe_key) deduped.append(p) else: # Duplicate msg = 'Duplicate participant: ' \ f"{p['name']}, {p['role']}, {p['category']}" log.info(msg) participation_payload = format_track_label_participations( isrc=isrc, vendor_id=vendor_id, subaccount_id=subaccount_id, participations=deduped ) try: # Adjust header for each label graphql_conn.set_headers({'GRASS-ACCOUNT-ID': str(vendor_id)}) create_participant_response = graphql_conn.execute( graphql_queries.SET_LABEL_PARTICIPANTS_FOR_ISRC, participation_payload ) # ['data']['product'] except GraphQLError as ge: # Push error metadata to method result object error = { 'isrc': isrc, 'vendor_id': vendor_id, 'subaccount_id': subaccount_id, 'status': 'graphql_error', 'error_code': ge.code, 'status_text': 'GraphQL Error', 'message': ge.message } # Notify user of GraphQL error msg = f'GraphQL Call Error - \'{isrc}-{vendor_id}-{subaccount_id}\' ' \ 'label participant creation call failed - Error Code: ' \ f'graphql_error - {ge.code} : GraphQL ' \ f'Error : {ge.message}' log.error(msg) return create_participant_response, error