"""Product metadata update.""" from switchboard_consumer.constants import ( entity_type, message_fields, release_type, system) from switchboard_consumer.constants.exceptions import ( ParticipantHandlerError, ParticipantHandlerGraphQLError ) from switchboard_consumer.constants.message_fields import ISRC from switchboard_consumer.formatters.errors import ( format_exception, format_graphql_errors ) from switchboard_consumer.formatters.product import ( format_create_product_input, format_update_product_input, get_company_code) from switchboard_consumer.formatters.track import ( format_rearrange_tracks, format_result_id as format_track_result_id, format_tracks_unmapped_publishers_batch) from switchboard_consumer.formatters.track import ( format_switchboard_product_tracks_to_orchard_tracks_batch, get_all_product_tracks, get_product_track_by_isrc) from switchboard_consumer.logic.\ label_participant.label_participant_handler import ( LabelParticipantHandler ) from switchboard_consumer.logic.participant.product_participant_handler \ import ProductParticipantHandler from switchboard_consumer.logic.participant.track_participant_handler \ import TrackParticipantHandler from switchboard_consumer.logic.preprocessing import product, \ sme_analytics_ingestion from switchboard_consumer.logic.product import create_product, \ release_correct_product, update_product from switchboard_consumer.logic.product_handler import ProductHandler from switchboard_consumer.logic.tracks import create_tracks, \ get_publisher_processing_results from switchboard_consumer.logic.validation.product import validate from switchboard_consumer.logic.video_single_handler import \ VideoSingleHandler from switchboard_consumer.utils.graphql import is_error from switchboard_consumer.utils.mapping import create_track_mapping from switchboard_consumer.utils.message import (create_processing_result, get_system_local_id) from switchboard_consumer.utils.product import get_isrcs, get_track_arrangement class AudioHandler(ProductHandler): """Handler for Audio products.""" def validate(self): """Validate product before processing.""" validation_errors = validate.validate_product( self.swb_product_data, self.message) if validation_errors: self.processing_results.extend(validation_errors) return False return True def update_via_release_correction(self, product_id): """Handle product update via release correction""" self.logger.info('Updating a product using release correction') update_messages = release_correct_product( product_id, self.swb_product_data, self.message, self.swb_client, self.orchard_client, self.logger) self.processing_results.extend(update_messages) return True def update_in_progress_product(self, product_id): """Update in-progress state product.""" payload = format_update_product_input( product_id, self.swb_product_data, self.artists) update_product_result = update_product( self.logger, self.message, payload, self.orchard_client) self.processing_results.append(update_product_result) if update_product_result.get('errors'): return False if self.dummy_product_id: # Add the Orchard Dummy product id into swb data for # track handler self.logger.info(( 'Dummy product update, addings ids from successful ' 'update into swb product data.' )) self.swb_product_data['ids'] = update_product_result['ids'] # Check for productTracks update. success, tracks_processing_result = process_product_tracks_update( self.logger, self.message, self.orchard_client, self.swb_product_data) self.processing_results.extend(tracks_processing_result) return success def create_product(self): """Create new product.""" vendor_id = get_company_code(self.swb_product_data['labelAccount']) subaccount_id = get_company_code(self.swb_product_data['subAccount']) track_participant_handler = TrackParticipantHandler( self.swb_product_data, self.logger, self.orchard_client, self.message, vendor_id, subaccount_id) create_product_payload = format_create_product_input( self.message.sending_system_local_id['businessKey'], self.swb_product_data, self.artists) processing_results, create_tracks_payload = ( format_switchboard_product_tracks_to_orchard_tracks_batch( self.swb_product_data, self.logger, track_participant_handler)) # Add participant processing results self.processing_results += processing_results track_mappings = create_track_mapping(self.swb_product_data) product_id, processing_results = create_product( self.logger, self.message, self.orchard_client, create_product_payload) self.processing_results += processing_results if not product_id: return False if create_tracks_payload: create_tracks_success, track_processing_results = create_tracks( self.logger, self.message, self.orchard_client, product_id, create_tracks_payload, track_mappings, format_tracks_unmapped_publishers_batch(self.swb_product_data) ) label_participant_handler = LabelParticipantHandler( self.orchard_client, vendor_id, subaccount_id, self.message.correlation_id, self.logger ) # We need to get the track results from the track bulk # creation results. Because if we have an error with # adding participants then we need to ensure we add # our new error onto that processing result. for track_result in track_processing_results: is_track = track_result['entityType'] == entity_type.TRACK not_errored = not track_result['errors'] if is_track and not_errored: try: track = get_product_track_by_isrc( self.swb_product_data, track_result['ids'][0]['businessKey'] ) label_participants = label_participant_handler.\ create_label_participants( track['track']['contributors'] ) label_participant_handler.\ set_label_participants_for_track( track['track']['isrc'], label_participants ) except ParticipantHandlerGraphQLError as e: track_result['errors'].extend( e.raw_errors ) create_tracks_success = False except ParticipantHandlerError as e: track_result['errors'].append(format_exception(e)) create_tracks_success = False self.processing_results += track_processing_results return create_tracks_success return True def process_participants(self): """Handle product participants.""" try: participant_handler = ProductParticipantHandler( self.logger, self.orchard_client, self.message, self.vendor_id, self.subaccount_id) processing_results, artists = participant_handler.process( self.swb_product_data) self.processing_results.extend(processing_results) self.artists = artists except ParticipantHandlerGraphQLError as e: # We encountered a GraphQL error setting up participants self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=e.raw_errors)) return False except ParticipantHandlerError as e: # We raised a handler error self.processing_results.append(create_processing_result( self.message.ids, self.message, exception=format_exception(e))) return False return True def handle_product_metadata_update(logger, message, swb_client, orchard_client): """Handle PRODUCT messages.""" results = [] logger.info('Handling PRODUCT metadata update.') identifier = message.sending_system_local_id if not identifier['businessKeyType'] == message_fields.UPC: message_text = 'businessKeyType: {} is not supported'.format( identifier.get('businessKeyType') ) logger.error(message) results.append(create_processing_result( message.ids, message_text, # TODO: Finalize error codes that the daemon may throw errors={'code': 'invalid_business_key_type', 'message': message } )) return results upc = identifier['businessKey'] # Query SWB for product with Sony UPC. swb_product = swb_client.get_product_by_upc(identifier, message.correlation_id) if swb_product.get('errors'): errors = swb_product.get('errors') logger.error('Error getting product by UPC: {} - {}'.format(upc, errors)) results.append(create_processing_result( message.ids, message, errors=format_graphql_errors(errors) )) return results product.preprocess(swb_product) sme_analytics_ingestion.update_label_id(message, swb_product) if swb_product['releaseType'] == release_type.VIDEO_SINGLE: metadata_handler = VideoSingleHandler( swb_product, message, logger, orchard_client, swb_client) return metadata_handler.process() else: metadata_handler = AudioHandler( swb_product, message, logger, orchard_client, swb_client) return metadata_handler.process() def process_product_tracks_update( logger, message, orchard_client, switchboard_product_data): """Process product track update on product update.""" results = [] orchard_product_id = get_system_local_id(switchboard_product_data) orchard_product_data = orchard_client.get_product_by_id( orchard_product_id['localId'], correlation_id=message.correlation_id) if is_error(orchard_product_data): errors = orchard_product_data.get('errors') logger.error('Problem getting orchard product. - {}'.format(errors)) return False, [create_processing_result( message.ids, message, errors=format_graphql_errors(errors))] orchard_isrcs = {track['isrc'] for track in orchard_product_data['tracks']} swb_isrcs = get_isrcs(switchboard_product_data) results += construct_mappings_for_existing_tracks( orchard_product_data, switchboard_product_data, message, logger ) isrcs_to_add = swb_isrcs - orchard_isrcs isrcs_to_delete = orchard_isrcs - swb_isrcs if isrcs_to_add: success, missing_tracks_results = _insert_missing_product_tracks( logger, message, orchard_client, orchard_product_id, switchboard_product_data, isrcs_to_add) if not success: logger.info('Failed to add tracks to product ' 'with id {}'.format(orchard_product_id)) return success, missing_tracks_results logger.info('Added tracks to product with ' 'id {}.'.format(orchard_product_id)) results += missing_tracks_results if isrcs_to_delete: success, deleted_tracks_results = _delete_extra_tracks( logger, message, orchard_client, orchard_product_id, isrcs_to_delete, orchard_product_data) if not success: logger.info('Failed to delete tracks from ' 'product with id {}'.format(orchard_product_id)) return success, results + deleted_tracks_results logger.info('Deleted tracks from product with id ' '{}'.format(orchard_product_id)) tracks_updated = any([isrcs_to_add, isrcs_to_delete]) success, arrange_tracks_results = _fix_tracks_arrangement( logger, message, orchard_client, orchard_product_id, orchard_product_data, switchboard_product_data, tracks_updated) if not success: return False, results + arrange_tracks_results return True, results def _insert_missing_product_tracks( logger, message, orchard_client, orchard_product_id, switchboard_product_data, isrcs_to_add): tracks_to_add = [] track_result_ids = [] for component in switchboard_product_data.get('components', []): for side in component.get('sides', []): for product_track in side.get('productTracks', []): swb_track = product_track['track'] swb_track_isrc = swb_track['isrc'] if swb_track_isrc in isrcs_to_add: tracks_to_add.append(product_track) swb_local_id = get_system_local_id( swb_track, system=system.SONY ) swb_local_id.update({ 'businessKey': swb_track_isrc, 'businessKeyType': ISRC, }) track_result_ids.append([swb_local_id]) logger.info( 'New track with isrc {} ' 'detected on product update'.format( swb_track_isrc)) vendor_id = get_company_code(switchboard_product_data['labelAccount']) subaccount_id = get_company_code(switchboard_product_data['subAccount']) track_participant_handler = TrackParticipantHandler( switchboard_product_data, logger, orchard_client, message, vendor_id, subaccount_id) processing_results = [] format_payload = { 'components': [{ 'sides': [{ 'productTracks': tracks_to_add }], }] } participant_processing_results, add_tracks_payload = ( format_switchboard_product_tracks_to_orchard_tracks_batch( format_payload, logger, track_participant_handler) ) # Add participant processing results processing_results += participant_processing_results create_tracks_results = orchard_client.create_tracks( add_tracks_payload, product_id=int(orchard_product_id['localId']), correlation_id=message.correlation_id) if is_error(create_tracks_results): errors = create_tracks_results['errors'] logger.error('Problem inserting tracks. - {}'.format(errors)) return False, [create_processing_result( message.ids, message, errors=format_graphql_errors(errors))] label_participant_handler = LabelParticipantHandler(orchard_client, vendor_id, subaccount_id, message. correlation_id, logger) for track in tracks_to_add: label_participants = label_participant_handler.\ create_label_participants( track['track']['contributors'] ) label_participant_handler.set_label_participants_for_track( track['track']['isrc'], label_participants ) # Build new tracks processing result id for i in range(len(create_tracks_results)): create_tracks_result = create_tracks_results[i] tuid = create_tracks_result['tuid'] isrc = create_tracks_result['isrc'] track_result_ids[i] += format_track_result_id(tuid, isrc) processing_results.append(create_processing_result( track_result_ids[i], message, entity_type=entity_type.TRACK)) format_unmapped_publisher_payload = { 'components': [{ 'sides': [{ 'productTracks': tracks_to_add }], }] } unmapped_tracks_publishers = format_tracks_unmapped_publishers_batch( format_unmapped_publisher_payload) processing_results.extend(get_publisher_processing_results( logger, message, create_tracks_results, unmapped_tracks_publishers)) return True, processing_results def _delete_extra_tracks( logger, message, orchard_client, orchard_product_id, isrcs_to_delete, orchard_product_data): if not isrcs_to_delete: return True, [] tracks_to_delete = [ track['tuid'] for track in orchard_product_data['tracks'] if track['isrc'] in isrcs_to_delete] delete_tracks_results = orchard_client.delete_tracks( tracks_to_delete, product_id=int(orchard_product_id['localId']), correlation_id=message.correlation_id) if is_error(delete_tracks_results): errors = delete_tracks_results['errors'] logger.error('Problem inserting tracks. - {}'.format(errors)) return False, [create_processing_result( message.ids, message, errors=format_graphql_errors(errors))] return True, [] def _fix_tracks_arrangement( logger, message, orchard_client, orchard_product_id, orchard_product_data, switchboard_product_data, tracks_updated): if tracks_updated: orchard_product_data = orchard_client.get_product_by_id( str(orchard_product_id['localId']), message.correlation_id) if orchard_product_data.get('errors'): errors = orchard_product_data.get('errors') logger.error( 'Problem getting orchard product. - {}'.format(errors)) return False, [create_processing_result( message.ids, message, errors=format_graphql_errors(errors))] orchard_isrc_to_tuid_mapping = { track['isrc']: track['tuid'] for track in orchard_product_data['tracks']} orchard_arrangement_mapping = { track['isrc']: (track['volumeNumber'], track['trackNumber']) for track in orchard_product_data['tracks']} switchboard_arrangement_mapping = get_track_arrangement( switchboard_product_data ) rearrange_required = False for isrc in orchard_arrangement_mapping: if (orchard_arrangement_mapping[isrc] != # noqa: W504 switchboard_arrangement_mapping[isrc]): rearrange_required = True if rearrange_required: arrangement = format_rearrange_tracks(switchboard_product_data, orchard_isrc_to_tuid_mapping) rearrange_result = orchard_client.rearrange_tracks( arrangement, int(orchard_product_id['localId']), message.correlation_id) if is_error(rearrange_result): errors = rearrange_result.get('errors') logger.error( 'Problem rearranging product tracks. - {}'.format(errors)) return False, [create_processing_result( message.ids, message, errors=format_graphql_errors(errors))] return True, [] def construct_mappings_for_existing_tracks(orchard_product_data, switchboard_product_data, message, logger): """ Contruct mapping for Tracks that have been created in the Orchard but failed to map back to SWB when first created. (SWITCH-1781) """ results = [] orchard_isrcs = {track['isrc'] for track in orchard_product_data['tracks']} orchard_tuids = [track['tuid'] for track in orchard_product_data['tracks']] for swb_track in get_all_product_tracks(switchboard_product_data): mapped_tuids = [identifier['localId'] for identifier in swb_track['track']['ids'] if identifier['system'] == system.ORCHARD] # Does the Track ISRC exist on this Orchard Product isrc_exists_on_orchard_product = \ swb_track['track']['isrc'] in orchard_isrcs # Are any of the mapped tuids for this Track on this Orchard Product matching_mapped_tuids = any( tuid in orchard_tuids for tuid in mapped_tuids ) # If we do have the ISRC and there is no matching mapping on Product if isrc_exists_on_orchard_product and not matching_mapped_tuids: # Create the mapping since we somehow failed to create it before logger.info(('Constructing Track mapping for Track' ' that is already attached to Orchard Product' ' but is somehow missing a Track mapping in SWB.'), custom_fields={ 'track': swb_track }) for orchard_track in orchard_product_data['tracks']: if orchard_track['isrc'] == swb_track['track']['isrc']: isrc = swb_track['track']['isrc'] # Add businessKey onto all SWB track identifers [identifier.update({ 'businessKeyType': ISRC, 'businessKey': isrc }) for identifier in swb_track['track']['ids']] # Construct processing result for this track identifiers = [ *format_track_result_id( orchard_track['tuid'], isrc ), *swb_track['track']['ids'] ] results.append(create_processing_result( identifiers, message, entity_type=entity_type.TRACK )) return results