Files
musicseerr/backend/services/artist_service.py
T
Harvey a69a26852e Cut down unnecessary logging (#48)
* Cut down unnecessary logging

* fix format etc

* fix checks

* fix tests
2026-04-14 00:02:38 +01:00

679 lines
31 KiB
Python

import asyncio
import copy
import logging
import msgspec
from typing import Any, Optional, TYPE_CHECKING
from api.v1.schemas.artist import ArtistInfo, ArtistExtendedInfo, ArtistReleases, ExternalLink, ReleaseItem
from repositories.protocols import MusicBrainzRepositoryProtocol, LidarrRepositoryProtocol, WikidataRepositoryProtocol
from services.preferences_service import PreferencesService
from services.artist_utils import (
detect_platform,
extract_tags,
extract_aliases,
extract_life_span,
extract_external_links,
categorize_release_groups,
categorize_lidarr_albums,
extract_wiki_info,
build_base_artist_info,
)
from infrastructure.cache.cache_keys import ARTIST_INFO_PREFIX
from infrastructure.cache.memory_cache import CacheInterface
from infrastructure.cache.disk_cache import DiskMetadataCache
from infrastructure.validators import validate_mbid
from core.exceptions import ExternalServiceError, ResourceNotFoundError
from services.audiodb_image_service import AudioDBImageService
from repositories.audiodb_models import AudioDBArtistImages
if TYPE_CHECKING:
from infrastructure.persistence import LibraryDB
logger = logging.getLogger(__name__)
class ArtistService:
def __init__(
self,
mb_repo: MusicBrainzRepositoryProtocol,
lidarr_repo: LidarrRepositoryProtocol,
wikidata_repo: WikidataRepositoryProtocol,
preferences_service: PreferencesService,
memory_cache: CacheInterface,
disk_cache: DiskMetadataCache,
audiodb_image_service: AudioDBImageService | None = None,
audiodb_browse_queue: Any = None,
library_db: 'LibraryDB | None' = None,
):
self._mb_repo = mb_repo
self._lidarr_repo = lidarr_repo
self._wikidata_repo = wikidata_repo
self._preferences_service = preferences_service
self._cache = memory_cache
self._disk_cache = disk_cache
self._audiodb_image_service = audiodb_image_service
self._audiodb_browse_queue = audiodb_browse_queue
self._library_db = library_db
self._artist_in_flight: dict[str, asyncio.Future[ArtistInfo]] = {}
self._artist_basic_in_flight: dict[str, asyncio.Future[ArtistInfo]] = {}
async def _get_library_cache_mbids(self) -> set[str]:
if self._library_db is None:
return set()
try:
raw = await self._library_db.get_all_album_mbids()
return {m.lower() for m in raw if m}
except Exception as e: # noqa: BLE001
logger.warning("Failed to read library cache MBIDs: %s", e)
return set()
async def _revalidate_library_status(self, artist_info: ArtistInfo) -> ArtistInfo:
"""Re-evaluate in_library flags on a cached artist response using fresh LibraryDB data."""
cache_mbids = await self._get_library_cache_mbids()
try:
library_mbids = await self._lidarr_repo.get_library_mbids(include_release_ids=True)
except Exception: # noqa: BLE001
library_mbids = set()
all_mbids = library_mbids | cache_mbids
if not all_mbids:
return artist_info
result = copy.deepcopy(artist_info)
changed = False
for release_list in (result.albums, result.singles, result.eps):
if not release_list:
continue
for release in release_list:
if isinstance(release, dict):
rid = (release.get("id") or "").lower()
else:
rid = (release.id or "").lower()
if not rid:
continue
new_in_library = rid in all_mbids
old_in_library = release.get("in_library", False) if isinstance(release, dict) else release.in_library
if new_in_library != old_in_library:
if isinstance(release, dict):
release["in_library"] = new_in_library
if new_in_library and release.get("requested"):
release["requested"] = False
else:
release.in_library = new_in_library
if new_in_library and release.requested:
release.requested = False
changed = True
artist_mbids = await self._get_library_artist_mbids()
new_artist_in_library = result.musicbrainz_id and result.musicbrainz_id.lower() in artist_mbids
if new_artist_in_library != result.in_library:
result.in_library = new_artist_in_library
return result
async def _get_library_artist_mbids(self) -> set[str]:
if self._library_db is None:
return set()
try:
raw = await self._library_db.get_all_artist_mbids()
return {m.lower() for m in raw if m}
except Exception as e: # noqa: BLE001
logger.warning("Failed to read library artist cache MBIDs: %s", e)
return set()
async def _apply_audiodb_artist_images(
self,
artist_info: ArtistInfo,
mbid: str,
name: str | None,
*,
allow_fetch: bool = False,
is_monitored: bool = False,
) -> ArtistInfo:
if self._audiodb_image_service is None:
return artist_info
try:
images: AudioDBArtistImages | None
if allow_fetch:
images = await self._audiodb_image_service.fetch_and_cache_artist_images(
mbid, name, is_monitored=is_monitored,
)
else:
images = await self._audiodb_image_service.get_cached_artist_images(mbid)
if images is None or images.is_negative:
if not allow_fetch and images is None and self._audiodb_browse_queue:
settings = self._preferences_service.get_advanced_settings()
if settings.audiodb_enabled:
await self._audiodb_browse_queue.enqueue("artist", mbid, name=name)
return artist_info
if not artist_info.fanart_url and images.fanart_url:
artist_info.fanart_url = images.fanart_url
if not artist_info.banner_url and images.banner_url:
artist_info.banner_url = images.banner_url
if images.thumb_url:
artist_info.thumb_url = images.thumb_url
if images.fanart_url_2:
artist_info.fanart_url_2 = images.fanart_url_2
if images.fanart_url_3:
artist_info.fanart_url_3 = images.fanart_url_3
if images.fanart_url_4:
artist_info.fanart_url_4 = images.fanart_url_4
if images.wide_thumb_url:
artist_info.wide_thumb_url = images.wide_thumb_url
if images.logo_url:
artist_info.logo_url = images.logo_url
if images.clearart_url:
artist_info.clearart_url = images.clearart_url
if images.cutout_url:
artist_info.cutout_url = images.cutout_url
except Exception as e: # noqa: BLE001
logger.warning("Failed to apply AudioDB images for artist %s: %s", mbid[:8], e)
return artist_info
async def get_artist_info(
self,
artist_id: str,
library_artist_mbids: set[str] = None,
library_album_mbids: dict[str, Any] = None
) -> ArtistInfo:
try:
artist_id = validate_mbid(artist_id, "artist")
except ValueError as e:
logger.error(f"Invalid artist MBID: {e}")
raise
try:
cached = await self._get_cached_artist(artist_id)
if cached:
cached = await self._revalidate_library_status(cached)
cached = await self._apply_audiodb_artist_images(
cached, artist_id, cached.name,
allow_fetch=False, is_monitored=cached.in_library,
)
return cached
if artist_id in self._artist_in_flight:
return await asyncio.shield(self._artist_in_flight[artist_id])
loop = asyncio.get_running_loop()
future: asyncio.Future[ArtistInfo] = loop.create_future()
self._artist_in_flight[artist_id] = future
try:
artist_info = await self._do_get_artist_info(artist_id, library_artist_mbids, library_album_mbids)
if not future.done():
future.set_result(artist_info)
return artist_info
except BaseException as exc:
if not future.done():
future.set_exception(exc)
raise
finally:
self._artist_in_flight.pop(artist_id, None)
except ValueError:
raise
except Exception as e: # noqa: BLE001
logger.error(f"API call failed for artist {artist_id}: {e}")
raise ResourceNotFoundError(f"Failed to get artist info: {e}")
async def _do_get_artist_info(
self, artist_id: str,
library_artist_mbids: set[str] | None,
library_album_mbids: dict[str, Any] | None,
) -> ArtistInfo:
lidarr_artist = await self._lidarr_repo.get_artist_details(artist_id) if self._lidarr_repo.is_configured() else None
in_library = lidarr_artist is not None and lidarr_artist.get("monitored", False)
if in_library and lidarr_artist:
artist_info = await self._build_artist_from_lidarr(artist_id, lidarr_artist, library_album_mbids)
else:
artist_info = await self._build_artist_from_musicbrainz(artist_id, library_artist_mbids, library_album_mbids)
if lidarr_artist is not None:
artist_info.in_lidarr = True
artist_info.monitored = lidarr_artist.get("monitored", False)
artist_info.auto_download = lidarr_artist.get("monitor_new_items", "none") == "all"
artist_info = await self._apply_audiodb_artist_images(
artist_info, artist_id, artist_info.name,
allow_fetch=False, is_monitored=artist_info.in_library,
)
await self._save_artist_to_cache(artist_id, artist_info)
return artist_info
async def set_artist_monitoring(
self, artist_mbid: str, *, monitored: bool, auto_download: bool = False,
) -> dict[str, Any]:
if not self._lidarr_repo.is_configured():
raise ExternalServiceError("Lidarr is not configured")
monitor_new_items = "all" if (monitored and auto_download) else "none"
result = await self._lidarr_repo.update_artist_monitoring(
artist_mbid, monitored=monitored, monitor_new_items=monitor_new_items,
)
await self._cache.delete(f"{ARTIST_INFO_PREFIX}{artist_mbid}")
return result
async def get_artist_monitoring_status(self, artist_mbid: str) -> dict[str, Any]:
if not self._lidarr_repo.is_configured():
return {"in_lidarr": False, "monitored": False, "auto_download": False}
details = await self._lidarr_repo.get_artist_details(artist_mbid)
if details is None:
return {"in_lidarr": False, "monitored": False, "auto_download": False}
return {
"in_lidarr": True,
"monitored": details.get("monitored", False),
"auto_download": details.get("monitor_new_items", "none") == "all",
}
async def _build_artist_from_lidarr(
self,
artist_id: str,
lidarr_artist: dict[str, Any],
library_album_mbids: dict[str, Any] = None
) -> ArtistInfo:
description = lidarr_artist.get("overview")
image = lidarr_artist.get("poster_url")
fanart_url = lidarr_artist.get("fanart_url")
banner_url = lidarr_artist.get("banner_url")
genres = lidarr_artist.get("genres", [])
external_links = []
for link in lidarr_artist.get("links", []):
link_name = link.get("name", "")
link_url = link.get("url", "")
if link_url:
label, category = detect_platform(link_url, link_name.lower())
external_links.append(ExternalLink(type=link_name.lower(), url=link_url, label=label, category=category))
need_musicbrainz = not description or not genres or not external_links
parallel_tasks: list[Any] = []
task_names: list[str] = []
if library_album_mbids is None:
parallel_tasks.append(self._lidarr_repo.get_library_mbids(include_release_ids=True))
task_names.append("library_mbids")
parallel_tasks.append(self._get_library_cache_mbids())
task_names.append("cache_mbids")
parallel_tasks.append(self._lidarr_repo.get_artist_albums(artist_id))
task_names.append("lidarr_albums")
parallel_tasks.append(self._lidarr_repo.get_requested_mbids())
task_names.append("requested_mbids")
if need_musicbrainz:
parallel_tasks.append(self._mb_repo.get_artist_by_id(artist_id))
task_names.append("mb_artist")
results = await asyncio.gather(*parallel_tasks, return_exceptions=True)
result_map = dict(zip(task_names, results))
if library_album_mbids is None:
lib_result = result_map.get("library_mbids")
library_album_mbids = lib_result if not isinstance(lib_result, Exception) and lib_result else {}
cache_result = result_map.get("cache_mbids")
cache_mbids = cache_result if not isinstance(cache_result, Exception) and cache_result else {}
library_album_mbids = library_album_mbids | cache_mbids
req_result = result_map.get("requested_mbids")
requested_mbids = req_result if not isinstance(req_result, Exception) and req_result else set()
albums_result = result_map.get("lidarr_albums")
lidarr_albums = albums_result if not isinstance(albums_result, Exception) and albums_result else []
albums, singles, eps = self._categorize_lidarr_albums(lidarr_albums, library_album_mbids, requested_mbids=requested_mbids)
aliases = []
life_span = None
artist_type = lidarr_artist.get("artist_type")
disambiguation = lidarr_artist.get("disambiguation")
country = None
release_group_count = len(lidarr_albums)
if need_musicbrainz:
try:
mb_artist = result_map.get("mb_artist")
if isinstance(mb_artist, Exception):
raise mb_artist
if mb_artist:
if not description:
mb_description, _ = await self._fetch_wikidata_info(mb_artist)
description = mb_description
if not genres:
genres = extract_tags(mb_artist)
if not external_links:
external_links = self._build_external_links(mb_artist)
aliases = extract_aliases(mb_artist)
life_span = extract_life_span(mb_artist)
country = mb_artist.get("country")
if not artist_type:
artist_type = mb_artist.get("type")
if not disambiguation:
disambiguation = mb_artist.get("disambiguation")
release_group_count = mb_artist.get("release-group-count", release_group_count)
except Exception as e: # noqa: BLE001
logger.warning(f"MusicBrainz fallback failed for artist {artist_id[:8]}: {e}")
return ArtistInfo(
name=lidarr_artist.get("name", "Unknown Artist"),
musicbrainz_id=artist_id,
disambiguation=disambiguation,
type=artist_type,
country=country,
life_span=life_span,
description=description,
image=image,
fanart_url=fanart_url,
banner_url=banner_url,
tags=genres,
aliases=aliases,
external_links=external_links,
in_library=True,
albums=albums,
singles=singles,
eps=eps,
release_group_count=release_group_count,
)
def _categorize_lidarr_albums(
self,
lidarr_albums: list[dict[str, Any]],
library_album_mbids: set[str],
requested_mbids: set[str] | None = None,
) -> tuple[list[ReleaseItem], list[ReleaseItem], list[ReleaseItem]]:
prefs = self._preferences_service.get_preferences()
included_primary_types = set(t.lower() for t in prefs.primary_types)
included_secondary_types = set(t.lower() for t in prefs.secondary_types)
return categorize_lidarr_albums(lidarr_albums, included_primary_types, included_secondary_types, library_album_mbids, requested_mbids=requested_mbids)
async def _build_artist_from_musicbrainz(
self,
artist_id: str,
library_artist_mbids: set[str] = None,
library_album_mbids: dict[str, Any] = None,
include_extended: bool = True,
include_releases: bool = True,
) -> ArtistInfo:
mb_artist, library_mbids, album_mbids, requested_mbids = await self._fetch_artist_data(
artist_id, library_artist_mbids, library_album_mbids
)
in_library = artist_id.lower() in library_mbids
albums, singles, eps = (await self._get_categorized_releases(mb_artist, album_mbids, requested_mbids)) if include_releases else ([], [], [])
description, image = (await self._fetch_wikidata_info(mb_artist)) if include_extended else (None, None)
info = build_base_artist_info(
mb_artist, artist_id, in_library,
extract_tags(mb_artist), extract_aliases(mb_artist), extract_life_span(mb_artist),
self._build_external_links(mb_artist), albums, singles, eps, description, image
)
return ArtistInfo(**info)
async def get_artist_info_basic(self, artist_id: str) -> ArtistInfo:
artist_id = validate_mbid(artist_id, "artist")
cached = await self._get_cached_artist(artist_id)
if cached:
cached = await self._apply_audiodb_artist_images(
cached, artist_id, cached.name, allow_fetch=False,
)
await self._refresh_library_flags(cached)
return cached
if artist_id in self._artist_basic_in_flight:
return await asyncio.shield(self._artist_basic_in_flight[artist_id])
loop = asyncio.get_running_loop()
future: asyncio.Future[ArtistInfo] = loop.create_future()
self._artist_basic_in_flight[artist_id] = future
try:
artist_info = await self._build_artist_from_musicbrainz(artist_id, include_extended=False, include_releases=False)
artist_info = await self._apply_audiodb_artist_images(
artist_info, artist_id, artist_info.name, allow_fetch=False,
)
await self._refresh_library_flags(artist_info)
await self._save_artist_to_cache(artist_id, artist_info)
if not future.done():
future.set_result(artist_info)
return artist_info
except BaseException as exc:
if not future.done():
future.set_exception(exc)
raise
finally:
self._artist_basic_in_flight.pop(artist_id, None)
async def _refresh_library_flags(self, artist_info: ArtistInfo) -> None:
if not self._lidarr_repo.is_configured():
return
try:
library_mbids, requested_mbids, artist_mbids = await asyncio.gather(
self._lidarr_repo.get_library_mbids(include_release_ids=False),
self._lidarr_repo.get_requested_mbids(),
self._lidarr_repo.get_artist_mbids(),
)
for release_list in (artist_info.albums, artist_info.singles, artist_info.eps):
for rg in release_list:
rg_id = (rg.id or "").lower()
if not rg_id:
continue
rg.in_library = rg_id in library_mbids
rg.requested = rg_id in requested_mbids and not rg.in_library
mbid_lower = artist_info.musicbrainz_id.lower()
is_in_artist_mbids = mbid_lower in artist_mbids
artist_info.in_library = is_in_artist_mbids
if is_in_artist_mbids:
try:
lidarr_artist = await self._lidarr_repo.get_artist_details(artist_info.musicbrainz_id)
if lidarr_artist is not None:
artist_info.in_lidarr = True
artist_info.monitored = lidarr_artist.get("monitored", False)
artist_info.auto_download = lidarr_artist.get("monitor_new_items", "none") == "all"
elif not artist_info.in_lidarr:
artist_info.in_lidarr = True
except Exception: # noqa: BLE001
if not artist_info.in_lidarr:
artist_info.in_lidarr = True
except Exception as e: # noqa: BLE001
logger.warning(f"Failed to refresh library flags: {e}")
async def _get_cached_artist(self, artist_id: str) -> Optional[ArtistInfo]:
cache_key = f"{ARTIST_INFO_PREFIX}{artist_id}"
cached_info = await self._cache.get(cache_key)
if cached_info:
return cached_info
disk_data = await self._disk_cache.get_artist(artist_id)
if disk_data:
try:
artist_info = msgspec.convert(disk_data, ArtistInfo, strict=False)
except (msgspec.ValidationError, TypeError, ValueError) as e:
logger.warning(f"Corrupt disk cache for artist {artist_id[:8]}, clearing: {e}")
await self._disk_cache.delete_artist(artist_id)
return None
ttl = self._get_artist_ttl(artist_info.in_library)
await self._cache.set(cache_key, artist_info, ttl_seconds=ttl)
return artist_info
return None
async def _save_artist_to_cache(self, artist_id: str, artist_info: ArtistInfo) -> None:
cache_key = f"{ARTIST_INFO_PREFIX}{artist_id}"
ttl = self._get_artist_ttl(artist_info.in_library)
await self._cache.set(cache_key, artist_info, ttl_seconds=ttl)
await self._disk_cache.set_artist(
artist_id, artist_info,
is_monitored=artist_info.in_library,
ttl_seconds=ttl if not artist_info.in_library else None
)
def _get_artist_ttl(self, in_library: bool) -> int:
advanced_settings = self._preferences_service.get_advanced_settings()
return advanced_settings.cache_ttl_artist_library if in_library else advanced_settings.cache_ttl_artist_non_library
async def get_artist_extended_info(self, artist_id: str) -> ArtistExtendedInfo:
try:
artist_id = validate_mbid(artist_id, "artist")
cache_key = f"{ARTIST_INFO_PREFIX}{artist_id}"
cached_info = await self._cache.get(cache_key)
if cached_info and cached_info.description is not None:
return ArtistExtendedInfo(description=cached_info.description, image=cached_info.image)
mb_artist = await self._mb_repo.get_artist_by_id(artist_id)
if not mb_artist:
raise ResourceNotFoundError("Artist not found")
description, image = await self._fetch_wikidata_info(mb_artist)
if cached_info:
cached_info.description = description
cached_info.image = image
await self._save_artist_to_cache(artist_id, cached_info)
return ArtistExtendedInfo(description=description, image=image)
except Exception as e: # noqa: BLE001
logger.error(f"Error fetching extended artist info for {artist_id}: {e}")
return ArtistExtendedInfo(description=None, image=None)
async def get_artist_releases(
self,
artist_id: str,
offset: int = 0,
limit: int = 50
) -> ArtistReleases:
try:
lidarr_artist = await self._lidarr_repo.get_artist_details(artist_id)
in_library = lidarr_artist is not None and lidarr_artist.get("monitored", False)
album_mbids, requested_mbids, cache_mbids = await asyncio.gather(
self._lidarr_repo.get_library_mbids(include_release_ids=True),
self._lidarr_repo.get_requested_mbids(),
self._get_library_cache_mbids(),
)
album_mbids = album_mbids | cache_mbids
prefs = self._preferences_service.get_preferences()
included_primary_types = set(t.lower() for t in prefs.primary_types)
included_secondary_types = set(t.lower() for t in prefs.secondary_types)
if in_library and offset == 0:
lidarr_albums = await self._lidarr_repo.get_artist_albums(artist_id)
albums, singles, eps = self._categorize_lidarr_albums(lidarr_albums, album_mbids, requested_mbids=requested_mbids)
total_count = len(albums) + len(singles) + len(eps)
return ArtistReleases(
albums=albums,
singles=singles,
eps=eps,
total_count=total_count,
has_more=False
)
release_groups, total_count = await self._mb_repo.get_artist_release_groups(
artist_id, offset, limit
)
temp_artist = {"release-group-list": release_groups}
albums, singles, eps = categorize_release_groups(
temp_artist,
album_mbids,
included_primary_types,
included_secondary_types,
requested_mbids
)
has_more = (offset + len(release_groups)) < total_count
return ArtistReleases(
albums=albums,
singles=singles,
eps=eps,
total_count=total_count,
has_more=has_more
)
except Exception as e: # noqa: BLE001
logger.error(f"Error fetching releases for artist {artist_id} at offset {offset}: {e}")
return ArtistReleases(albums=[], singles=[], eps=[], total_count=0, has_more=False)
async def _fetch_artist_data(
self,
artist_id: str,
library_artist_mbids: set[str] = None,
library_album_mbids: dict[str, Any] = None
) -> tuple[dict, set[str], set[str], set[str]]:
if library_artist_mbids is not None and library_album_mbids is not None:
mb_artist = await self._mb_repo.get_artist_by_id(artist_id)
library_mbids = library_artist_mbids
album_mbids = library_album_mbids
requested_result = await asyncio.gather(
self._lidarr_repo.get_requested_mbids(),
return_exceptions=True,
)
requested_mbids = requested_result[0] if not isinstance(requested_result[0], BaseException) else set()
if isinstance(requested_result[0], BaseException):
logger.warning(f"Lidarr unavailable, proceeding without requested data: {requested_result[0]}")
else:
mb_artist, *lidarr_results = await asyncio.gather(
self._mb_repo.get_artist_by_id(artist_id),
self._lidarr_repo.get_artist_mbids(),
self._lidarr_repo.get_library_mbids(include_release_ids=True),
self._lidarr_repo.get_requested_mbids(),
return_exceptions=True,
)
if isinstance(mb_artist, BaseException):
logger.error(f"Error fetching artist data for {artist_id}: {mb_artist}")
raise ResourceNotFoundError(f"Failed to fetch artist: {mb_artist}")
lidarr_failed = any(isinstance(r, BaseException) for r in lidarr_results)
if lidarr_failed:
logger.warning(f"Lidarr unavailable for artist {artist_id}, proceeding with MusicBrainz data only")
library_mbids = lidarr_results[0] if not isinstance(lidarr_results[0], BaseException) else set()
album_mbids = lidarr_results[1] if not isinstance(lidarr_results[1], BaseException) else set()
requested_mbids = lidarr_results[2] if not isinstance(lidarr_results[2], BaseException) else set()
# Supplement with LibraryDB so monitored albums (even with trackFileCount=0)
# are recognised as "in library", consistent with the Library page.
cache_mbids = await self._get_library_cache_mbids()
album_mbids = album_mbids | cache_mbids
if not mb_artist:
raise ResourceNotFoundError("Artist not found")
return mb_artist, library_mbids, album_mbids, requested_mbids
def _build_external_links(self, mb_artist: dict[str, Any]) -> list[ExternalLink]:
external_links_data = extract_external_links(mb_artist)
return [
ExternalLink(type=link["type"], url=link["url"], label=link["label"])
for link in external_links_data
]
async def _get_categorized_releases(
self,
mb_artist: dict[str, Any],
album_mbids: set[str],
requested_mbids: set[str] = None
) -> tuple[list[ReleaseItem], list[ReleaseItem], list[ReleaseItem]]:
prefs = self._preferences_service.get_preferences()
included_primary_types = set(t.lower() for t in prefs.primary_types)
included_secondary_types = set(t.lower() for t in prefs.secondary_types)
return categorize_release_groups(
mb_artist,
album_mbids,
included_primary_types,
included_secondary_types,
requested_mbids or set()
)
async def _fetch_wikidata_info(self, mb_artist: dict[str, Any]) -> tuple[Optional[str], Optional[str]]:
wikidata_id, wiki_urls = self._extract_wiki_info(mb_artist)
tasks = []
if wiki_urls:
tasks.append(self._wikidata_repo.get_wikipedia_extract(wiki_urls[0]))
else:
tasks.append(asyncio.create_task(asyncio.sleep(0)))
if wikidata_id:
tasks.append(self._wikidata_repo.get_artist_image_from_wikidata(wikidata_id))
else:
tasks.append(asyncio.create_task(asyncio.sleep(0)))
results = await asyncio.gather(*tasks, return_exceptions=True)
description = results[0] if len(results) > 0 and not isinstance(results[0], Exception) and results[0] else None
image = results[1] if len(results) > 1 and not isinstance(results[1], Exception) and results[1] else None
return description, image
def _extract_wiki_info(self, mb_artist: dict[str, Any]) -> tuple[Optional[str], list[str]]:
return extract_wiki_info(mb_artist, self._wikidata_repo.get_wikidata_id_from_url)