from collections import abc from collections import defaultdict from celery.utils.log import get_logger from oto import response from masters_registry.constant import api_const from masters_registry.constant import bulk_tasks_const from masters_registry.constant import error from masters_registry.constant import field_const from masters_registry.constant import opcode_const from masters_registry.logic import bulk_tasks from masters_registry.logic import conflicts from masters_registry.logic import dms_carveout from masters_registry.logic import ownership as ownership_logic from masters_registry.models import ownership from masters_registry.models import ows_carveouts from masters_registry.models import ows_territories from masters_registry.models import yt_ownership from masters_registry.validation import upc_validation celery_log = get_logger(__name__) def get_ownership(isrc): """Get ownership info with added track info for an isrc Args: isrc (str): international standard recording code Returns: response.Response """ if not isrc: return response.Response(message={}) # get ownership info ownership_response = ownership.get_ownership(isrc) if not ownership_response or not ownership_response.message: # TODO it would be 200 response with empty dict if item wasn't found return ownership_response ownership_info = ownership_response.message # get unclaimed territories unclaimed_territories_response = _get_unclaimed_territories(ownership_info) if unclaimed_territories_response.status == 200: unclaimed_territories = unclaimed_territories_response.message else: return unclaimed_territories_response # get track info tuids = _extract_tuids(ownership_info) track_response = ownership.get_tracks(tuids) if not track_response: return track_response track_info = track_response.message # combine ownership and track info tuid_territories = _build_tuid_territories_map(ownership_info) _extend_ownership_with_track(ownership_info, track_info, tuid_territories) _group_locked_territories_by_reason(ownership_info) # Append lock history history_response = ownership.get_lock_history_for_ownership( ownership_info) if not history_response: return history_response conflicts_history_response = ( conflicts.get_conflicts_history_for_ownership(ownership_info)) if not conflicts_history_response: return conflicts_history_response # add unclaimed territories ownership_info[field_const.UNCLAIMED_TERRITORIES] = unclaimed_territories ownership_info.pop(field_const.TERRITORIES, None) return response.Response(message=ownership_info) def refresh_ownership(isrc, tuid, correlation_id): """Refresh territory ownership on YouTube with what is in the registry. Args: isrc (str): international standard recording code tuid (int): track unique identifier correlation_id (str): The correlation id for logging the microservice instance Returns: response.Response """ if not all((isrc, tuid, correlation_id)): return response.create_error_response( code=error.MISSING_ARGUMENTS, message='Update Operation Failed: ISRC or TUID ' 'is empty.') if not _is_valid_isrc_tuid(isrc, tuid): return response.create_error_response( code=error.INVALID_TUID_ISRC, message='TUID and ISRC do not match') get_ownership_response = ownership.get_ownership(isrc) # if the registry does not return any records, we have no ownership if not get_ownership_response.message: existing_territories = {} else: existing_territories = ( get_ownership_response.message[field_const.TERRITORIES]) # send existing claimed territories to YouTube carveout_processing_response = dms_carveout.send_message_to_yt_ownership( isrc, tuid, existing_territories, correlation_id, True) if not carveout_processing_response: return carveout_processing_response return response.Response(message=carveout_processing_response.message) def update_ownership( isrc, tuid, territories_to_update, correlation_id, user): """Update territory ownership. Args: isrc (str): international standard recording code tuid (int): track unique identifier territories_to_update (list): list of 2-code territories to add correlation_id (str): The correlation id for logging the microservice instance user (str): The user id for audit table logging Returns: response.Response """ if not all((isrc, tuid, territories_to_update, correlation_id)): # TODO Add marshmallow schema for this # Add error message about correlation_id return response.create_error_response( code=error.MISSING_ARGUMENTS, message='Update Operation Failed: ISRC, TUID, or territory list ' 'is empty.') get_ownership_response = ownership.get_ownership(isrc) if not get_ownership_response.message: return response.create_error_response( code=error.ISRC_NOT_FOUND, message='ISRC not found in registry') if not _is_valid_isrc_tuid(isrc, tuid): return response.create_error_response( code=error.INVALID_TUID_ISRC, message='TUID and ISRC do not match') claimed_by_the_same_label_response = _check_conflicts_with_the_same_label( tuid, territories_to_update, get_ownership_response.message) if not claimed_by_the_same_label_response: return claimed_by_the_same_label_response territories_response = _is_valid_territories(territories_to_update) if not territories_response: return territories_response if _is_worldwide_flag(territories_to_update): # get unclaimed territories unclaimed_territories_response = _get_unclaimed_territories( get_ownership_response.message) if unclaimed_territories_response.status == 200: territories_to_update = unclaimed_territories_response.message else: return unclaimed_territories_response else: territories_to_update = territories_response.message existing_territories = ( get_ownership_response.message[field_const.TERRITORIES]) # combine existing claimed and new claimed territories to send to # YouTube updated_claimed_territories = list( set(existing_territories).union(territories_to_update)) ownership_response = ownership_logic.update_ownership( isrc, tuid, territories_to_update, existing_territories, correlation_id, user) carveout_processing_response = dms_carveout.send_message_to_yt_ownership( isrc, tuid, updated_claimed_territories, correlation_id) if not carveout_processing_response: return carveout_processing_response return response.Response(message=ownership_response.message) def remove_ownership( isrc, tuid, territories_to_update, correlation_id, user, source=field_const.MANUAL_EDIT_ISRC, opcode=opcode_const.REMOVE): """Remove territory ownership. Args: isrc (str): international standard recording code tuid (int): track unique identifier territories_to_update (list): list of 2-code territories to remove correlation_id (str): The correlation id for logging the microservice instance user (str): The user id for audit table logging source (str): Action code used in audit table for logging opcode (str): The opcode determining the action entered into the audit table. Returns: response.Response """ if not all((isrc, tuid, territories_to_update, correlation_id)): # TODO Add marshmallow schema for this # Add error message about correlation_id return response.create_error_response( code=error.MISSING_ARGUMENTS, message='Update Operation Failed: ISRC, TUID, or territory list ' 'is empty.') get_ownership_response = ownership.get_ownership(isrc) if not get_ownership_response.message: return response.create_error_response( code=error.ISRC_NOT_FOUND, message='ISRC not found in registry') if not _is_valid_isrc_tuid(isrc, tuid): return response.create_error_response( code=error.INVALID_TUID_ISRC, message='TUID and ISRC do not match') territories_response = _is_valid_territories( territories_to_update, correlation_id=correlation_id) if not territories_response: return territories_response if _is_worldwide_flag(territories_to_update): tuid_territories = _build_tuid_territories_map( get_ownership_response.message) territories_to_update = tuid_territories.get(tuid, []) else: territories_to_update = territories_response.message existing_territories = ( get_ownership_response.message[field_const.TERRITORIES]) remove_result = ownership_logic.remove_ownership( isrc, tuid, territories_to_update, existing_territories, correlation_id, user, source=source, opcode=opcode) updated_claimed_territories = ( remove_result[field_const.UPDATED_CLAIMED_TERRITORIES]) carveout_processing_response = ( dms_carveout.send_message_to_yt_ownership( isrc, tuid, updated_claimed_territories, correlation_id)) if not carveout_processing_response: return carveout_processing_response return response.Response( message={ field_const.TUID: tuid, field_const.TERRITORIES: territories_to_update} ) def _check_conflicts_with_the_same_label( tuid, territories_to_update, ownership_info): """Determine if there are internal conflicts between tuids that belongs to the same label. Args: tuid (int): track unique identifier territories (list): list of 2-code territories ownership_info (dict): ownership info for isrc Returns: response.Response: error response if there are any conflicts otherwise empty Response """ claimed_by_the_same_label = conflicts.check_conflicts_with_the_same_label( tuid, territories_to_update, ownership_info) if claimed_by_the_same_label: message = error.TERRITORIES_CLAIMED_BY_THE_SAME_LABEL_MESSAGE return response.create_error_response( code=error.TERRITORIES_CLAIMED_BY_THE_SAME_LABEL, message={ 'error': message, 'territories': claimed_by_the_same_label } ) return response.Response() def _extract_tuids(ownership_info): """Extract set of tuids from ownership_info Args: ownership_info (dict): ownership info for isrc Returns: set of int: tuids found in ownership_info """ tuids = [] for item in ownership_info[field_const.TERRITORIES].values(): # TODO: remove this after Internal Conflicts project go live if isinstance(item, abc.Mapping): tuids_to_add = [int(item[field_const.TUID])] else: tuids_to_add = [int(t[field_const.TUID]) for t in item] tuids.extend(tuids_to_add) return set(tuids) def _build_tuid_territories_map(ownership_info): """Build a map of tuid to list of territories Example of resulting data: 111111: { field_const.VENDOR_ID: 47, field_const.SUBACCOUNT_ID: None, field_const.SUBACCOUNT_NAME: None, field_const.TUID: 111111, field_const.ISRC: 'ABC123456', field_const.RELEASE_NAME: 'Zodiac Symphony', field_const.UPC: 669910758229, field_const.TRACK_NAME: 'Taurus', field_const.ARTIST_NAME: 'Burkhard Schmidl', field_const.VENDOR_NAME: 'Amin Refaat' }, 11: { field_const.VENDOR_ID: 47, field_const.SUBACCOUNT_ID: None, field_const.SUBACCOUNT_NAME: None, field_const.TUID: 11, field_const.ISRC: 'ZW1010600008', field_const.RELEASE_NAME: 'Zodiac Symphony', field_const.UPC: 889910758229, field_const.TRACK_NAME: 'Taurus', field_const.ARTIST_NAME: 'Burkhard Schmidl', field_const.VENDOR_NAME: 'Amin Refaat' } Args: ownership_info (dict): ownership info for isrc Returns: dict: tuid --> [territories] """ if field_const.TERRITORIES not in ownership_info: return {} tuid_territories = defaultdict(list) for terr_code, terr_obj in ownership_info[field_const.TERRITORIES].items(): if isinstance(terr_obj, abc.Mapping): terr_obj = [terr_obj] for item in terr_obj: tuid = item[field_const.TUID] tuid_territories[tuid].append(terr_code) return dict(tuid_territories) def _extend_ownership_with_track(ownership_info, track_info, tuid_territories): """Add track information to the ownership_info Example of resulting data: ownership_info['tracks'] = { 1111111: { field_const.TERRITORIES: (AF, AS, BR, CA) field_const.VENDOR_ID: 47, field_const.SUBACCOUNT_ID: None, field_const.SUBACCOUNT_NAME: None, field_const.TUID: 111111, field_const.ISRC: 'ABC123456', field_const.RELEASE_NAME: 'Zodiac Symphony', field_const.UPC: 669910758229, field_const.TRACK_NAME: 'Taurus', field_const.ARTIST_NAME: 'Burkhard Schmidl', field_const.VENDOR_NAME: 'Amin Refaat' } } Args: ownership_info (dict): ownership info for isrc track_info (dict of dict): track info keyed by tuid tuid_territories (dict): map of tuid to list of territories """ ownership_info[field_const.TRACKS] = [] for tuid, territories in tuid_territories.items(): current_track_information = track_info.get(tuid, {}) current_track_information[field_const.TERRITORIES] = sorted( territories) ownership_info[field_const.TRACKS].append(current_track_information) def _group_locked_territories_by_reason(ownership_info): """Group locked territories by reason Reformat locked_territories to group by the lock reason for display on front end. Example: { "locked_territories": [{ "territories": ["BE", "NL"], "reason": "Owned by Warner" }, { "territories": ["IT", "RU"], "reason": "Conflict with Universal" }] } Args: ownership_info (dict): ownership info for isrc """ reason_map = defaultdict(list) locked_territories = ownership_info[field_const.LOCKED_TERRITORIES] for territory_code, lock_obj in locked_territories.items(): reason = lock_obj[field_const.REASON] reason_map[reason].append(territory_code) ownership_info[field_const.LOCKED_TERRITORIES] = [{ field_const.REASON: reason, field_const.TERRITORIES: sorted(territories)} for reason, territories in reason_map.items()] def _is_valid_isrc_tuid(isrc, tuid): """Validate that tuid belongs to the isrc Query track table with tuid and compare the ISRC from the table and the ISRC given. ISRC is assumed to be properly formatted. Args: isrc (str): isrc tuid (int): track unique id Returns: bool: boolean result of comparing ISRCs """ track_response = ownership.get_tracks([tuid]) track_info = track_response.message if track_info: track_dict = track_info.get(tuid) return isrc.upper() == track_dict.get(field_const.ISRC).upper() return False def _is_worldwide_flag(territories): """Check if provided list of territories contains worldwide flag Args: territories (list): list of territories Returns: bool: list of territories contains WW flag or not """ return len(territories) == 1 and territories[0] == field_const.WW def _is_valid_territories(territories, correlation_id=None): """Validate that the territories are valid Fetch ownership from ows-territories for a master set of ISO-3166-1 and return false if any of the territories in the argument do not belong in the master set. Args: territories (list): ISO-3166-1 territories correlation_id (str): Correlation ID for logging Returns: response.Response """ ows_territories_response = ows_territories.get_territories( field_const.ISO_3166_1_2016, correlation_id=correlation_id) if ows_territories_response.status == 200: valid_territories = ows_territories_response.message.get( api_const.ITEMS) master_set_territories_codes = { item.get( field_const.TERRITORY_CODE_A2) for item in valid_territories} # for WW flag we should return a list of all territories if _is_worldwide_flag(territories): return response.Response(list(master_set_territories_codes)) master_territories_names = {} for item in valid_territories: code = item.get(field_const.TERRITORY_CODE_A2) name = item.get('territory_name').upper() master_territories_names[name] = code territories_codes = [] for territory in territories: if territory in master_territories_names: territories_codes.append(master_territories_names[territory]) elif territory in master_set_territories_codes: territories_codes.append(territory) else: return response.create_error_response( code=error.INVALID_TERRITORIES, message='Invalid territories') return response.Response(territories_codes) else: return ows_territories_response def _get_unclaimed_territories(ownership_info): """Get complement of union of claimed and locked territories Args: ownership_info (dict): ownership info for isrc Returns: response.Response """ claimed = set(ownership_info[field_const.TERRITORIES].keys()) locked = set(ownership_info[field_const.LOCKED_TERRITORIES].keys()) claimed_and_locked = list(claimed.union(locked)) # get complement of territories if claimed_and_locked: # list is not empty ows_territories_response = ows_territories.get_complement_territories( claimed_and_locked, field_const.ISO_3166_1_2016) else: # list is empty ows_territories_response = ows_territories.get_territories( field_const.ISO_3166_1_2016) if ows_territories_response.status == 200: unclaimed_territories = ows_territories_response.message.get( api_const.ITEMS) master_set_unclaimed_territories = {item.get( field_const.TERRITORY_CODE_A2) for item in unclaimed_territories} return response.Response( message=list(sorted(master_set_unclaimed_territories))) else: return ows_territories_response def _get_carved_in_territories(upc, correlation_id): """Get a list of carved-in territories for a given UPC Args: upc (str): UPC correlation_id (str): correlation id for logs Returns: Response: list of carved-in territories """ # 1. Call carve-out service to get the list of carved-out territories carved_out = ows_carveouts.get_territory_carveout_for_upc( upc, correlation_id) if not carved_out: return carved_out carved_out_territories_list = ( carved_out.message.values() if carved_out.message else []) # 2. Call territories service to convert territories to YouTube format if carved_out_territories_list: converted = ows_territories.convert_territories( carved_out_territories_list, correlation_id=correlation_id) if not converted: return converted converted_territories_list = [ territory[field_const.TERRITORY_CODE_A2] for territory in converted.message[field_const.ITEMS]] else: converted_territories_list = [] # 3. Get complement territories carved_in = ows_territories.get_complement_territories( converted_territories_list, correlation_id=correlation_id) if not carved_in: return carved_in carved_in_territories_list = [ territory[field_const.TERRITORY_CODE_A2] for territory in carved_in.message[field_const.ITEMS]] return response.Response(carved_in_territories_list) def _process_upc(upc, isrc_infos, correlation_id, user): """Import process for UPC Args: upc (str): UPC isrc_infos (list(dict)): list of dicts with recording information correlation_id (str): Correlation-Id for logs user (str): Orchard user id Returns: Response: response containing detailed import report """ upc_result = response.Response({ field_const.UPC: upc, field_const.ERROR_COUNT: 0, field_const.SUCCESS_COUNT: 0, field_const.STATUS_REPORT: [] }) # Get a list of carved-in territories territories_response = _get_carved_in_territories(upc, correlation_id) if not territories_response: upc_result.message[field_const.ERROR_COUNT] = 1 upc_result.message[ field_const.ERROR_MESSAGE] = territories_response.errors upc_result.status = territories_response.status return upc_result carved_in_territories = territories_response.message # Process ISRCs for isrc_info in isrc_infos: isrc_result = _process_isrc_info( isrc_info, carved_in_territories, correlation_id, user) if isrc_result: upc_result.message[field_const.SUCCESS_COUNT] += 1 else: upc_result.message[field_const.ERROR_COUNT] += 1 upc_result.message[field_const.STATUS_REPORT].append(isrc_result) return upc_result def _calculate_difference(ownership_data, territories, tuid, user): """Calculate difference between data in registry and imported data Args: ownership_data (dict): ownership information from the registry territories (list): list of territories from imported data tuid (int): Orchard TUID user (str): Orchard user id Returns: dict: Dictionary, containing differences between imported and existing territories. Example: { # Imported territories that exists registry 'existing': ['US', 'CA'], # Imported territories that are missing in registry 'missing': ['RU'], # Imported territories that are locked in registry 'locked': [], # Territories that exists in registry, but was removed 'removed': [] # Territories claimed by another owner 'claimed_by_another_owner': [(territory_code, tuid)] # Territories claimed by the same label 'claimed_by_the_same_label': ['QA'] } """ existing_territories = ownership_data['territories'] locked_territories = ownership_data['locked_territories'] claimed_by_the_same_label = conflicts.check_conflicts_with_the_same_label( tuid, territories, ownership_data) result = { field_const.EXISTING: [], field_const.MISSING: [], field_const.LOCKED: [], field_const.REMOVED: [], field_const.CLAIMED_BY_ANOTHER_OWNER: [], field_const.CLAIMED_BY_THE_SAME_LABEL: claimed_by_the_same_label, } claimed_by_the_same_label = set(claimed_by_the_same_label) for territory in territories: if territory in existing_territories: result[field_const.EXISTING].append(territory) existing_tuids = ownership_logic._get_tuids_from_territory( existing_territories[territory]) if (tuid not in existing_tuids and territory not in claimed_by_the_same_label): result[field_const.CLAIMED_BY_ANOTHER_OWNER].append( (territory, existing_tuids)) result[field_const.MISSING].append(territory) elif (territory not in existing_territories and territory in locked_territories): result[field_const.LOCKED].append(territory) else: result[field_const.MISSING].append(territory) for territory, territory_info in existing_territories.items(): territory_tuids = ownership_logic._get_tuids_from_territory( territory_info) if territory not in territories and tuid in territory_tuids: result[field_const.REMOVED].append(territory) return result def _process_isrc_info(isrc_info, territories, correlation_id, user): """Process data for recording Args: isrc_info (dict): dict with recording information from AR. territories (list): list of carved-in territories from AR. correlation_id (str): Correlation-Id for logs user (str): Orchard user id Returns: dict: result of processing """ # Get registry entry for ISRC ownership_response = ownership.get_ownership(isrc_info[field_const.ISRC]) ownership_data = ownership_response.message # Is it in registry? If no, add record to registry and return. if not ownership_data: processing_response = _process_isrc_info_with_create( isrc_info, territories, correlation_id, orchard_user_id=user) return processing_response # If yes, calculate the difference between territories and add to registry processing_response = _process_isrc_info_with_update( isrc_info, ownership_data, territories, correlation_id, orchard_user_id=user) return processing_response def _process_isrc_info_with_create( isrc_info, territories, correlation_id, orchard_user_id): """Process ISRC data when it doesn't exist in Registry. Args: isrc_info (dict): dict with recording information from AR. territories (list): list of carved-in territories from AR. correlation_id (str): Correlation-Id for logs orchard_user_id (str): Orchard user id Returns: dict: result of processing """ isrc = isrc_info[field_const.ISRC] tuid = isrc_info[field_const.TUID] result = ownership.create_ownership( isrc, territories, tuid, correlation_id, orchard_user_id) carveouts_response = dms_carveout.tuid_check_dms_carveout( tuid, territories, correlation_id) if not carveouts_response: processing_response = _get_processing_response_template(isrc) processing_response[field_const.SUCCESS] = bool(result) return processing_response has_store_carveouts = not carveouts_response.message[ field_const.ALLOWED_TERRITORIES] if has_store_carveouts: processing_response = _get_processing_response_template(isrc) processing_response[field_const.SUCCESS] = bool(result) processing_response[field_const.ERROR_MESSAGE] = ( error.UPC_YOUTUBE_CARVED_OUT) return processing_response # If no DMS carveouts, check for sub-store carveouts substore_carveouts = carveouts_response.message[ field_const.SUBSTORE_CARVEOUTS] # carveouts_result is a tuple(store_id, set(territory)) # If we have a non-empty set of territories, that means # that we have a substore-level carveout. has_substore_carveouts = bool(substore_carveouts[1]) if not has_substore_carveouts: processing_response = _get_processing_response_template(isrc) processing_response[field_const.SUCCESS] = bool(result) processing_response[field_const.SUBSTORE_CARVEOUTS] = [ substore_carveouts] return processing_response processing_response = _get_processing_response_template(isrc) processing_response[field_const.SUCCESS] = False processing_response[field_const.SUBSTORE_CARVEOUTS] = [substore_carveouts] return processing_response def _process_isrc_info_with_update( isrc_info, ownership_data, territories, correlation_id, orchard_user_id): """Process ISRC data when it does exist in Registry. Args: isrc_info (dict): dict with recording information from AR. ownership_data (dict): ISRC entry from DynamoDB territories (list): list of carved-in territories from AR. correlation_id (str): Correlation-Id for logs orchard_user_id (str): Orchard user id Returns: dict: result of processing """ isrc = isrc_info[field_const.ISRC] tuid = isrc_info[field_const.TUID] locked_result = None yt_ownership_result = response.Response() carveouts_result = [] differences = _calculate_difference( ownership_data, territories, tuid, orchard_user_id) # If there aren't any differences in ownership, check for dms carveout if not differences.get(field_const.MISSING)\ and not differences.get(field_const.REMOVED): # get carveout data for tuid yt_ownership_result = _update_youtube_with_carveout( isrc, tuid, ownership_data.get( field_const.TERRITORIES), correlation_id) # If there are differences between the registry and carevout info # then we need to add what is missing from carvein info to # the registry and remove what is not from carvein info # from the registry else: missing_territories = differences[field_const.MISSING] removed_territories = differences[field_const.REMOVED] if missing_territories: # add missing territories in carvein to registry existing_territories = ownership_data[field_const.TERRITORIES] ownership_logic.update_ownership( isrc, tuid, missing_territories, existing_territories, correlation_id, orchard_user_id, daemon=True, source=field_const.BULK_UPDATE_UPC, ) ownership_data = ownership.get_ownership( isrc_info[field_const.ISRC]).message if removed_territories: # remove territories from registry that are not in carvein existing_territories = ownership_data[field_const.TERRITORIES] ownership_logic.remove_ownership( isrc, tuid, removed_territories, existing_territories, correlation_id, orchard_user_id, daemon=True, source=field_const.BULK_UPDATE_UPC) # fetch updated ownership information from registry updated_ownership_response = ownership.get_ownership( isrc_info.get(field_const.ISRC)) update_ownership_data = updated_ownership_response.message # update YouTube with consideration of store/substore carevouts yt_ownership_result = _update_youtube_with_carveout( isrc, tuid, update_ownership_data.get(field_const.TERRITORIES), correlation_id) if differences[field_const.LOCKED]: locked_result = differences[field_const.LOCKED] locked_territories = sorted(locked_result) if locked_result else None carveouts_result = yt_ownership_result.errors or [] processing_response = { field_const.ISRC: isrc, field_const.SUCCESS: True, field_const.ERROR_MESSAGE: ( yt_ownership_result.message if not yt_ownership_result else ''), field_const.LOCKED_TERRITORIES: locked_territories, field_const.RESOLVED_CONFLICT: False, field_const.SUBSTORE_CARVEOUTS: carveouts_result, field_const.WARNING: False, } claimed_by_the_same_label = differences.get( field_const.CLAIMED_BY_THE_SAME_LABEL, []) processing_response[field_const.CLAIMED_BY_THE_SAME_LABEL] = ( claimed_by_the_same_label) claimed_by_another_owner = differences.get( field_const.CLAIMED_BY_ANOTHER_OWNER, []) processing_response[field_const.CLAIMED_BY_ANOTHER_OWNER] = ( claimed_by_another_owner) processing_response[field_const.SUCCESS] = ( processing_response[field_const.SUCCESS] and not claimed_by_the_same_label) processing_response[field_const.WARNING] = bool( claimed_by_another_owner) return processing_response def _get_processing_response_template(isrc): """Return processing response dict for filling with data. Args: isrc (str): ISRC that is being processed Returns: dict: processing template """ template = { field_const.ISRC: isrc, field_const.SUCCESS: False, field_const.ERROR_MESSAGE: '', field_const.LOCKED_TERRITORIES: None, field_const.RESOLVED_CONFLICT: False, field_const.SUBSTORE_CARVEOUTS: [], field_const.WARNING: False, field_const.CLAIMED_BY_THE_SAME_LABEL: [], field_const.CLAIMED_BY_ANOTHER_OWNER: [] } return template def _generate_processing_result(results): """Generate composite error result with concatenated error messages Args: results (list): list of Response objects Returns: Composite result """ calculated_result = response.Response('') for result in results: if not result: calculated_result.status = 500 # FIXME potential bug, we don't look into result.errors + # some functions don't event return Response(e.g. remove_ownership) if result.message: calculated_result.message += '{};'.format(result.message) return calculated_result def _update_youtube_with_carveout(isrc, tuid, territories, correlation_id): """Updates youtube with carveouts by the specific tuid Args: isrc (str): isrc tuid (str): track unique identifier territories (dict): dictionary of ownership data in dynamodb correlation_id (str): correlation_id Returns: response.Response: result of sending to yt_ownership """ result = response.Response() if not territories: return result unqiue_tuids = _get_unique_tuid_from_ownership_data(territories) combined_territories = set() carveout_terrotories = list() for isrc_tuid in unqiue_tuids: picked = _pick_territories_by_tuid( territories, isrc_tuid) tuid_carveout = dms_carveout.tuid_check_dms_carveout( isrc_tuid, picked, correlation_id) if tuid_carveout: message = tuid_carveout.message combined_territories = combined_territories.union( message[field_const.ALLOWED_TERRITORIES]) # if carveouts are for the tuid that is imported if tuid == isrc_tuid: # if empty list in response - store carveout if not message[field_const.ALLOWED_TERRITORIES]: result.message = error.UPC_YOUTUBE_CARVED_OUT result.status = 400 substores = message[field_const.SUBSTORE_CARVEOUTS] if substores[1]: carveout_terrotories.append( message[field_const.SUBSTORE_CARVEOUTS] ) else: celery_log.error( '{0}. Message: TUID {1}' ' Status: {2} Errors: {3}'.format( error.FAILED_TO_GET_DMS_CARVEOUTS, str(tuid), tuid_carveout.status, str(tuid_carveout.errors))) return response.Response( message=error.FAILED_TO_GET_DMS_CARVEOUTS, status=400) yt_ownership.send_message( isrc, sorted(combined_territories), correlation_id) if (carveout_terrotories and result.message != error.UPC_YOUTUBE_CARVED_OUT): result.message = '' result.errors = list(carveout_terrotories) result.status = 400 return result def bulk_import_upcs(upcs, correlation_id, user): """Bulk import ownership data into the registry for all given UPCs Args: upcs (list(str)): list of UPCs to import correlation_id (str): Correlation-Id for logs user (str): Orchard user id Returns: response.Response: detailed report for imported UPCs/ISRCs. Format differs a little bit if a request failed validation """ # Local import to avoid circular references in celery tasks from masters_registry.tasks import bulk upcs_isrcs_response = ownership.get_isrcs(upcs) if not upcs_isrcs_response: return upcs_isrcs_response upcs_isrcs_data = upcs_isrcs_response.message validation_response = upc_validation.compare(upcs, upcs_isrcs_data) if not validation_response: return validation_response count = len(upcs) task_context = upcs task_id = bulk_tasks.create_task( correlation_id, user, bulk_tasks_const.BULK_IMPORT, count, task_context) async_result = bulk.bulk_import_upcs.delay( upcs, task_id, correlation_id, user) return response.Response({ field_const.CORRELATION_ID_FIELD: correlation_id, field_const.CELERY_TASK_ID: async_result.id }) def add_tuid_for_territory(tuid, isrc, territories, correlation_id, user): """Add individual tuid for a territory. Args: tuid (int): new tuid isrc (str): ISRC territories (list): list of ISO-3166-1 territories correlation_id (str): Correlation-ID for logs user (str): Orchard user id """ territories_response = _is_valid_territories(territories) if not territories_response: return territories_response territories = set(territories_response.message) track_response = _validate_track(tuid, isrc) if not track_response: return track_response track_info = track_response.message ownership_response = ownership.get_ownership(isrc) if not ownership_response or not ownership_response.message: return ownership_response ownership_info = ownership_response.message tuid_response = _validate_tuid_not_claimed(tuid, ownership_info) if not tuid_response: return tuid_response territories_response = _validate_territories_not_claimed( ownership_info, territories) if not territories_response: return territories_response claimed_by_the_same_label_response = _check_conflicts_with_the_same_label( tuid, territories, ownership_info) if not claimed_by_the_same_label_response: return claimed_by_the_same_label_response claimed = set(ownership_info.get(field_const.TERRITORIES, {}).keys()) claimed.update(territories) claimed = list(claimed) territories = list(territories) existing_territories = ownership_info[field_const.TERRITORIES] ownership_logic.update_ownership( isrc, tuid, territories, existing_territories, correlation_id, user) dms_carveout_response = dms_carveout.tuid_check_dms_carveout( tuid, claimed, correlation_id) if not dms_carveout_response: return dms_carveout_response message = dms_carveout_response.message allowed_territories = message[field_const.ALLOWED_TERRITORIES] if allowed_territories: yt_ownership.send_message( isrc, allowed_territories, correlation_id) track = { field_const.TUID: tuid, field_const.VENDOR_NAME: track_info[field_const.VENDOR_NAME], field_const.VENDOR_ID: track_info[field_const.VENDOR_ID], field_const.ARTIST_NAME: track_info[field_const.ARTIST_NAME], field_const.TRACK_NAME: track_info[field_const.TRACK_NAME], field_const.UPC: track_info[field_const.UPC], field_const.RELEASE_NAME: track_info[field_const.RELEASE_NAME], field_const.TERRITORIES: territories } return response.Response(message=track) def _validate_track(tuid, isrc): """Gets track info from AR database. And checks if tuid and ISRC match Args: tuid (int): track unique identifier isrc (str): international standard recording code Returns: response.Response: track info or error response """ track_response = ownership.get_track(tuid) if not track_response: return track_response track = track_response.message if not track: return response.create_error_response( code=error.INVALID_TUID, message=error.TUID_DOES_NOT_EXIST_MESSAGE) if track[field_const.ISRC] != isrc: return response.create_error_response( code=error.INVALID_TUID_ISRC, message=error.INVALID_TUID_ISRC_MESSAGE) return response.Response(message=track) def _validate_tuid_not_claimed(tuid, ownership_info): """Checks if given tuid in not already claimed Args tuid (int): track unique identifier ownership_info: ownership record from active table Returns: response.Response """ for territory, claimed in ownership_info.get( field_const.TERRITORIES, {}).items(): if isinstance(claimed, abc.Mapping): is_claimed = tuid == claimed[field_const.TUID] else: is_claimed = tuid in [c[field_const.TUID] for c in claimed] if is_claimed: return response.create_error_response( code=error.TUID_CLAIMED, message=error.TUID_CLAIMED_MESSAGE.format(tuid)) return response.Response() def _validate_territories_not_claimed(ownership_info, territories): """Checks if given territories are not already claimed Args: ownership_info: territories (list(str)): list of 2-code territories Returns: response.Response """ locked = set(ownership_info.get(field_const.LOCKED_TERRITORIES, {}).keys()) locked = locked.intersection(territories) if locked: message = _create_invalid_territories_message(locked, 'locked') return response.create_error_response( code=error.INVALID_TERRITORIES, message=message) return response.Response() def _create_invalid_territories_message(territories, reason): """Creates error message Args: territories (list(str)): list of 2-code territories reason (str): error reason: 'locked' or 'claimed' Returns: str: error message Example: Territories: AF, AF are already locked. Territory: AF is already claimed. """ if not territories: return '' be = 'is' if len(territories) == 1 else 'are' subject = 'Territory' if len(territories) == 1 else 'Territories:' territories = list(territories) territories.sort() return '{} {} {} already {}.'.format( subject, ', '.join(territories), be, reason ) def _pick_territories_by_tuid(territories, tuid): """Picks territories by tuid Args: territories (list(str)): list of 2-code territories tuid (str): track unique id Returns: list: territories of given tuid """ picked = [] for territory, tuid_info in territories.items(): tuids = ownership_logic._get_tuids_from_territory(tuid_info) if tuid in tuids: picked.append(territory) return picked def _get_unique_tuid_from_ownership_data(ownership_data): """Returns unique tuids Args: ownership_data (dict): ownership_data from dynamo Returns: list: tuid """ tuids = set() for territory, tuid_info in ownership_data.items(): tuids.update(ownership_logic._get_tuids_from_territory(tuid_info)) return tuids