"""Logic for Artists. Accesses and modifies artist metadata. """ from typing import Any from flask import g from artist import response from artist.constants import account as account_constants from artist.constants import errors from artist.models import account from artist.models import artist as artist_model from artist.models import releases as releases_model SUBACCOUNT_LOOKUP_FAILED_MESSAGE = 'subaccount vendor_id lookup failed' SUBACCOUNT_NOT_FOUND_MESSAGE = 'subaccount not found' NO_VENDOR_MESSAGE = 'no vendor returned by permissions check for {profile_type}: {profile_id}' def create_artist(*, artist_data: dict[str, Any], vendor_id: int): """Create a new artist. Args: artist_data (dict): properties to assign to the new artist. Returns: response.Response: wrapper containing the created artist or errors. """ return artist_model.upsert_artist( artist_type=artist_data.get('artist_type'), name=artist_data.get('name'), vendor_id=vendor_id, unique_artist_id=artist_data.get('unique_artist_id') ) def update_artist(artist_id, artist_data): """Update an artist by artist_id. Args: artist_id (int): unique identifier of an artist. artist_data (dict): properties to assign to the existing artist. Returns: response.Response: wrapper containing the updated artist or errors. """ return artist_model.update_artist(artist_id, artist_data) def fetch_artists( account_type, account_id, page_offset=None, page_limit=None, artist_type=None): """Fetch artists. Fetch all the artists for a specific account and account id. Args: account_type (str): the account's type (either vendor or subaccount). account_id (str): the account's id page_offset (int): offset used for pagination page_limit (int): max number of records to return artist_type (str): whether the artist is for music, film or TV Returns: Response: paginated list of artists. """ vendor_id = account_id if account_type == account_constants.SUBACCOUNT_TYPE: # For a subaccount, look up the parent vendor id and get artists that # belong to the parent vendor account account_response = account.get_vendor_id_for_subaccount_id(account_id) if account_response.status == 404: # Return a 400 status, since a non-existent subaccount id is # invalid input. The message arg that's passed in here does not # affect the message property of the Response object (which will # be None). return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=400) elif not account_response: return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) vendor_id = account_response.message return artist_model.list_by_vendor_id( vendor_id=vendor_id, page_offset=page_offset, page_limit=page_limit, artist_type=artist_type) def fetch_full_artists( account_type, account_id, page_offset=None, page_limit=None, updated_since=None): """Fetch a paginated list of artists with additional metadata. Args: account_type (str): the account's type (either vendor or subaccount). account_id (str): the account's id page_offset (int): offset used for pagination page_limit (int): max number of records to return updated_since (int): timestamp to restrict query Returns: Response: containing the paginated list of artists. """ if account_type == account_constants.SUBACCOUNT_TYPE: account_response = account.get_vendor_id_for_subaccount_id(account_id) if account_response.status == 404: return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=400) elif not account_response: return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) vendor_id = account_response.message else: vendor_id = account_id return artist_model.fetch_full_artists( vendor_id, page_offset, page_limit, updated_since) def fetch_full_artists_bulk(account_type, account_id, artist_ids): """Fetch a list of artists with additional metadata. Args: account_type (str): the account's type (either vendor or subaccount). account_id (str): the account's id. artist_ids ([int]): the list of artist ids to fetch. Returns: Response: containing the list of artists. """ if account_type == account_constants.SUBACCOUNT_TYPE: account_response = account.get_vendor_id_for_subaccount_id(account_id) if account_response.status == 404: return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=400) elif not account_response: return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) vendor_id = account_response.message else: vendor_id = account_id return artist_model.fetch_full_artists_bulk(vendor_id, artist_ids) def fetch_full_artist_by_id(artist_id): """The artist with additional metadata. Args: artist_id (int): The artist id """ artist_response = fetch_artist_by_id(artist_id) if not artist_response: return artist_response genres_response = artist_model.get_artist_genres(artist_id) artist_response.message['genres'] = genres_response.message.get('items') if 'country_id' in artist_response.message: del artist_response.message['country_id'] return artist_response def filter_artists(**kwargs): """Filter artists function.""" return artist_model.filter_artists(**kwargs) def _account_status_error_response(status): """Create an error response for a failed call to ows-account. Args: status (int): status code from the account model. Returns: Response: an error response based on the status from account. """ if status == 404: return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=400) return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) def fetch_artist_by_id(artist_id, account_id=None, account_type=None): """Retrieve an artist's information for a given artist id. Args: artist_id (int): id of the artist to retrieve. account_id (int): the account's id. Returns: Response: a dict with the retrieved artist's information. """ vendor_id = account_id if account_type == account_constants.SUBACCOUNT_TYPE: account_response = account.get_vendor_id_for_subaccount_id(account_id) if account_response.status == 404: return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=400) if not account_response: return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) vendor_id = account_response.message artist_data = artist_model.fetch_artist_by_id( artist_id, vendor_id ) if artist_data: # Each entry in artist_data share a lot of artist data with # each other. artist_row = artist_data[0] if vendor_id and vendor_id != artist_row['vendor_id']: error_message = \ 'The vendor ID provided does not' \ + " match the artist's vendor ID." return response.create_error_response( code=errors.OWNERSHIP_ERROR, status=400, message=error_message) artist_record = { 'id': artist_row['id'], 'artist_type': artist_row['artist_type'], 'name': artist_row['name'], 'isni_id': artist_row['isni_id'], 'vendor_id': artist_row['vendor_id'] } return response.Response(status=200, message=artist_record) return artist_data def fetch_artist_identifiers(product_id): """Retrieve store identifiers for all artists in a release. Args: product_id (int): id of a release Returns: Response: a dict with release and track artists and their identifiers. """ return releases_model.get_artists(product_id) def bulk_ensure_artists(*, source_artist_ids: list[int], destination_vendor_id: int): """Ensure each source artist exists under destination_vendor_id. Fetches name/type for each source artist, then upserts at the destination vendor. Returns a mapping of str(source_artist_id) -> destination_artist_id. """ mapping = {} for source_artist_id in source_artist_ids: source_response = fetch_artist_by_id(source_artist_id) if not source_response: return source_response dest_response = artist_model.upsert_artist( name=source_response.message['name'], artist_type=source_response.message['artist_type'], vendor_id=destination_vendor_id, ) mapping[str(source_artist_id)] = dest_response.message['id'] return response.Response(message={'mapping': mapping}) def get_artist_document(artist_id, account_id=None, account_type=None): """Get artist details in cloudsearch friendly format. Args: artist_id (int): primary key of artist_info account_id (int): vendor_id or subaccount_id account_type (string): vendor or subaccount Returns: dict with artist details """ vendor_id = account_id if account_type == account_constants.SUBACCOUNT_TYPE: account_response = account.get_vendor_id_for_subaccount_id(account_id) if account_response.status == 404: return response.create_error_response( code=errors.VALIDATION_ERROR, message=SUBACCOUNT_NOT_FOUND_MESSAGE, status=404) if not account_response: return response.create_fatal_response( message=SUBACCOUNT_LOOKUP_FAILED_MESSAGE) vendor_id = account_response.message artist_data = artist_model.get_artist_document(artist_id, vendor_id) return artist_data