Skip to content

year_search_coordinator

Year search coordination logic extracted from ExternalApiOrchestrator.

This module handles the coordination of API calls to fetch release year information from multiple providers (MusicBrainz, Discogs, Apple Music).

YearSearchCoordinator

YearSearchCoordinator(
    *,
    console_logger,
    error_logger,
    config,
    preferred_api,
    musicbrainz_client,
    discogs_client,
    applemusic_client,
    release_scorer,
    max_concurrent_api_calls=50
)

Coordinates API calls to fetch release year information.

Handles: - Script-optimized search (Cyrillic, CJK, etc.) - Concurrent API queries across multiple providers - API priority ordering based on configuration

Initialize the year search coordinator.

Parameters:

Name Type Description Default
console_logger Logger

Logger for console output

required
error_logger Logger

Logger for error output

required
config AppConfig

Typed application configuration

required
preferred_api str

Preferred API name for ordering

required
musicbrainz_client MusicBrainzClient

MusicBrainz API client

required
discogs_client DiscogsClient

Discogs API client

required
applemusic_client AppleMusicClient

Apple Music API client

required
release_scorer ReleaseScorer

Release scoring service

required
max_concurrent_api_calls int

Maximum concurrent API requests (default 50). Prevents socket exhaustion on large libraries.

50
Source code in src/services/api/year_search_coordinator.py
def __init__(
    self,
    *,
    console_logger: logging.Logger,
    error_logger: logging.Logger,
    config: AppConfig,
    preferred_api: str,
    musicbrainz_client: MusicBrainzClient,
    discogs_client: DiscogsClient,
    applemusic_client: AppleMusicClient,
    release_scorer: ReleaseScorer,
    max_concurrent_api_calls: int = 50,
) -> None:
    """Initialize the year search coordinator.

    Args:
        console_logger: Logger for console output
        error_logger: Logger for error output
        config: Typed application configuration
        preferred_api: Preferred API name for ordering
        musicbrainz_client: MusicBrainz API client
        discogs_client: Discogs API client
        applemusic_client: Apple Music API client
        release_scorer: Release scoring service
        max_concurrent_api_calls: Maximum concurrent API requests (default 50).
            Prevents socket exhaustion on large libraries.

    """
    self.console_logger = console_logger
    self.error_logger = error_logger
    self.config = config
    self.preferred_api = preferred_api
    self.musicbrainz_client = musicbrainz_client
    self.discogs_client = discogs_client
    self.applemusic_client = applemusic_client
    self.release_scorer = release_scorer
    self._api_semaphore = asyncio.Semaphore(max_concurrent_api_calls)

fetch_all_api_results async

fetch_all_api_results(
    artist_norm,
    album_norm,
    artist_region,
    log_artist,
    log_album,
)

Fetch scored releases from all API providers with script-aware logic.

Source code in src/services/api/year_search_coordinator.py
async def fetch_all_api_results(
    self,
    artist_norm: str,
    album_norm: str,
    artist_region: str | None,
    log_artist: str,
    log_album: str,
) -> list[ScoredRelease]:
    """Fetch scored releases from all API providers with script-aware logic."""
    self._log_api_search_start(artist_norm, album_norm, artist_region, log_artist, log_album)

    # Try script-optimized search first
    artist_script = detect_primary_script(log_artist)
    album_script = detect_primary_script(log_album)
    primary_script = artist_script if artist_script != ScriptType.UNKNOWN else album_script

    if primary_script not in (ScriptType.LATIN, ScriptType.UNKNOWN):
        script_results = await self._try_script_optimized_search(primary_script, artist_norm, album_norm, artist_region)
        if script_results:
            return script_results

    # Standard API search (all providers concurrently)
    results = await self._execute_standard_api_search(artist_norm, album_norm, artist_region, log_artist, log_album)
    if results:
        return results

    # Fallback: try alternative search strategy
    return await self._try_alternative_search(album_norm, artist_region, log_artist, log_album)