"""Base class for Audio and VideoSingle handlers.""" from switchboard_consumer.constants import product_status from switchboard_consumer.constants.exceptions import \ ParticipantHandlerError, ParticipantHandlerGraphQLError from switchboard_consumer.constants.not_for_distribution import ( SME_ANALYTICS_DUMMY, SWITCHBOARD_DUMMY) from switchboard_consumer.constants.product_metadata_update import ( DUMMY_PRODUCT_DIFFERENT_TARGET, PRODUCT_ALREADY_EXISTS_IN_ORCHARD, PRODUCT_DOES_NOT_EXIST_IN_ORCHARD, PRODUCT_HAS_DIFFERENT_LOCAL_IDS ) from switchboard_consumer.constants.system import ORCHARD from switchboard_consumer.formatters.errors import format_exception, \ format_generic_error, format_graphql_errors from switchboard_consumer.formatters.product import (get_company_code) from switchboard_consumer.logic.email import send_unsubmit_email from switchboard_consumer.logic.release_correction.release_correction import ( needs_release_correcting ) from switchboard_consumer.utils import switchboard_graphql_json from switchboard_consumer.utils.message import create_processing_result, \ get_system_local_id class ProductHandler(): """Base class for Audio and VideoSingle handlers.""" def __init__(self, data, message, logger, orchard_client, swb_client=None): """Init handler.""" self.swb_product_data = data self.message = message self.logger = logger self.orchard_client = orchard_client self.swb_client = swb_client self.processing_results = [] self.artists = [] self.product_id = None self.success = False self.validation_errors = [] self.label_name = switchboard_graphql_json.get_label_name( self.swb_product_data) self.artist_name = switchboard_graphql_json.get_product_artist_name( self.swb_product_data) self.product_title = switchboard_graphql_json.get_product_title( self.swb_product_data) self.upc = self.message.sending_system_local_id.get('businessKey') self.vendor_id = get_company_code( self.swb_product_data['labelAccount']) self.subaccount_id = get_company_code( self.swb_product_data['subAccount']) self.dummy_product_id = None def validate_local_ids(self): """Validate local ids before ingest.""" # Get Orchard localId. swb_orchard_product_local_id = None if self.swb_product_data: swb_identifier = get_system_local_id(self.swb_product_data) try: swb_orchard_product_local_id = swb_identifier.get('localId') except AttributeError: pass # Query for the Orchard product with the Sony UPC. # This will cause SWB to lookup the product from Orchard. orchard_product = self.swb_client.get_product_by_upc( { 'businessKey': self.upc, 'system': ORCHARD, }, self.message.correlation_id ) if orchard_product and orchard_product.get('errors'): errors = orchard_product.get('errors') self.logger.error( 'Error getting product by UPC: {} - {}'.format( self.upc, errors)) self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=format_graphql_errors(errors) # TODO different error )) return False # Get Orchard localId. orchard_product_local_id = None if orchard_product: orchard_identifier = get_system_local_id(orchard_product) try: orchard_product_local_id = orchard_identifier.get('localId') except AttributeError: pass # SWB has a mapping for the product in Orchard... if swb_orchard_product_local_id: if orchard_product_local_id: if swb_orchard_product_local_id != orchard_product_local_id: # We want to reject this because it could be that multiple # products with the same UPC exist across multiple labels # and if there is confusion between SWB and Orchard about # which product to update we should take the safest # approach and just reject. self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=[format_generic_error( PRODUCT_HAS_DIFFERENT_LOCAL_IDS.format( self.upc, swb_orchard_product_local_id, orchard_product_local_id) )] )) return False # SWB's mapping must be wrong as Orchard # does not have this product. else: self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=[format_generic_error( PRODUCT_DOES_NOT_EXIST_IN_ORCHARD.format( self.upc, swb_orchard_product_local_id) )] )) return False # SWB does NOT have a mapping for the product in Orchard: # - the SWB database could have been reset (probably QA). # - the product could have been added manually in Orchard. else: if orchard_product_local_id: orchard_product = self.orchard_client.get_product_by_id( orchard_product_local_id, self.message.correlation_id ) is_dummy = orchard_product.get('notForDistribution') in [ SME_ANALYTICS_DUMMY, SWITCHBOARD_DUMMY ] if not is_dummy: # It is not a dummy Product. self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=[format_generic_error( PRODUCT_ALREADY_EXISTS_IN_ORCHARD.format(self.upc) )] )) return False is_matching_vendor_id = orchard_product.get('vendorId') == self.vendor_id # noqa is_matching_subaccount_id = orchard_product.get('subaccountId') == self.subaccount_id # noqa update_conditions = [ is_matching_vendor_id, is_matching_subaccount_id ] if not all(update_conditions): # Target account is different than what is in the Orchard self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=[format_generic_error( DUMMY_PRODUCT_DIFFERENT_TARGET.format( self.upc, orchard_product_local_id, orchard_product.get('vendorId'), orchard_product.get('subaccountId') ) )] )) return False self.dummy_product_id = orchard_product_local_id return True def create_or_update(self): """Check if product needs to be created or updated.""" orchard_product_id = get_system_local_id(self.swb_product_data) try: if self.dummy_product_id: self.update_product(self.dummy_product_id) elif orchard_product_id: product_id = orchard_product_id['localId'] return self.update_product(product_id) else: return self.create_product() except ParticipantHandlerGraphQLError as e: self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=e.raw_errors)) return False except ParticipantHandlerError as e: self.processing_results.append(create_processing_result( self.message.ids, self.message, exception=format_exception(e))) return False def unsubmit_product(self, product_id): """Unsubmit product.""" self.logger.info('Going to unsubmit product.') result = self.orchard_client.unsubmit_product( product_id, self.message.correlation_id) if result.get('errors'): errors = result.get('errors') self.logger.error('Problem unsubmitting product {}'.format( errors)) self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=format_graphql_errors(errors))) return False self.logger.info( 'Product unsubmitted in Orchard with id: {}'.format(product_id)) return True def update_in_progress_product(self, *args, **kwargs): """Update in-progress state product.""" raise NotImplementedError('update_in_progress_product not implemented') def update_via_release_correction(self, product_id): """Handle product update via release correction""" raise NotImplementedError( 'update_via_release_correction not implemented') def update_product(self, product_id): """Check product status and do appropriate update.""" orchard_product = self.orchard_client.get_product_by_id( product_id, correlation_id=self.message.correlation_id) if orchard_product.get('errors'): errors = orchard_product.get('errors') self.processing_results.append(create_processing_result( self.message.ids, self.message, errors=format_graphql_errors(errors))) return False display_status = orchard_product['displayStatus'] self.logger.info('Product status: {}'.format(display_status)) if needs_release_correcting(display_status, orchard_product.get('releaseCorrectionId') ): return self.update_via_release_correction(product_id) else: if display_status == product_status.SUBMITTED: if not self.unsubmit_product(product_id): return False send_unsubmit_email(orchard_product, self.swb_product_data) return self.update_in_progress_product(product_id) def process(self): """Process metadata update.""" if not self.validate_local_ids(): return self.processing_results if not self.validate(): return self.processing_results if not self.process_participants(): return self.processing_results if not self.create_or_update(): return self.processing_results self.success = True return self.processing_results