soulsync/core/metadata/contracts.py
Antti Kettunen 896b1b1012
Add typed metadata engine baseline
- introduce the in-process metadata engine and fresh provider adapters
- normalize external identity through source_id while keeping id as a compatibility alias
- route metadata callers through the new seam and add regression coverage
2026-05-09 11:44:47 +03:00

71 lines
1.9 KiB
Python

"""Typed metadata engine contracts."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Generic, Literal, Optional, Sequence, TypeVar
T = TypeVar("T")
MetadataEntityKind = Literal["artist", "album", "track"]
@dataclass(frozen=True)
class MetadataSearchRequest:
entity_kind: MetadataEntityKind
query: str
limit: int = 20
source_override: Optional[str] = None
enabled_sources: Optional[Sequence[str]] = None
allow_fallback: bool = True
skip_cache: bool = False
max_pages: int = 0
dedup_variants: bool = True
artist_source_ids: Optional[dict[str, str]] = None
@dataclass(frozen=True)
class MetadataLookupRequest:
entity_kind: MetadataEntityKind
entity_id: str
source_override: Optional[str] = None
enabled_sources: Optional[Sequence[str]] = None
allow_fallback: bool = True
skip_cache: bool = False
include_tracks: bool = True
limit: int = 50
max_pages: int = 0
@dataclass(frozen=True)
class MetadataSearchOutcome(Generic[T]):
items: list[T] = field(default_factory=list)
source: Optional[str] = None
attempted_sources: tuple[str, ...] = ()
cache_hit: bool = False
status: str = "miss"
errors: tuple[str, ...] = ()
raw_payload: Any = None
@dataclass(frozen=True)
class MetadataLookupOutcome(Generic[T]):
value: Optional[T] = None
source: Optional[str] = None
attempted_sources: tuple[str, ...] = ()
cache_hit: bool = False
status: str = "miss"
errors: tuple[str, ...] = ()
raw_payload: Any = None
@dataclass(frozen=True)
class MetadataProviderStatus:
provider: str
configured: bool
available: bool
authenticated: bool = False
rate_limited: bool = False
retry_after: Optional[int] = None
last_error: Optional[str] = None
details: dict[str, Any] = field(default_factory=dict)