Skip to content

Generate dynamic catalog responses from a pattern

Every mock.data.* field accepts either a dict[str, T] keyed by id or a Callable[[LookupContext], T | None]. The callable form lets you generate a resource on demand instead of enumerating every id up front — useful when the test sweeps over hundreds of ids or when ids carry pattern-encoded metadata.

Per-id factory

from musickit_api_mock import (
    Artwork,
    HlsChunk,
    HlsLayout,
    LookupContext,
    MusicKitApiMock,
    Song,
)

mock = MusicKitApiMock()


def make_song(song_id: str, *, title: str) -> Song:
    media_segment = b"mock media segment"
    return Song(
        title=title,
        artist="Test Artist",
        album="Test Album",
        duration_ms=180_000,
        artwork=Artwork(
            url="https://example.test/artwork/{w}x{h}.{f}",
            width=1200,
            height=1200,
        ),
        bitrate=256,
        sample_rate=44_100,
        file_size=0,
        genres=[],
        has_lyrics=False,
        audio_locale="en-US",
        audio_traits=[],
        has_time_synced_lyrics=False,
        is_apple_digital_master=False,
        is_mastered_for_itunes=False,
        is_vocal_attenuation_allowed=False,
        url=f"https://music.apple.com/us/song/{song_id}",
        hls_layout=HlsLayout(
            target_duration_sec=6,
            init_byte_offset=0,
            init_byte_length=0,
            chunks=(
                HlsChunk(
                    duration_sec=6.0,
                    byte_offset=0,
                    byte_length=len(media_segment),
                ),
            ),
        ),
        hls_segment=media_segment,
        preview_audio=media_segment,
    )


def resolve_song(ctx: LookupContext) -> Song | None:
    if not ctx.id.startswith("test-"):
        return None
    return make_song(ctx.id, title=f"Track {ctx.id}")


mock.data.songs = resolve_song

Returning None from the resolver signals "no such id" — the endpoint reading the source then produces the same not-found response it would for an unknown dict key.

Locale-aware resolution

The lookup context carries the request's ?l= value when one was present in the URL. Use it when your test asserts locale-specific behavior:

def resolve_song(ctx: LookupContext) -> Song | None:
    title = f"Track {ctx.id}"
    if ctx.locale == "ja":
        title = f"トラック {ctx.id}"
    return make_song(ctx.id, title=title)

The mock does not fabricate a locale from the storefront slug; ctx.locale is None when the URL did not include ?l=. Dict sources ignore the locale entirely.

Don't reach across resources

A callable for mock.data.songs should resolve only the song. If the song's album / artist / genre relationships matter, populate the matching mock.data.albums / mock.data.artists / mock.data.genres sources too — each resource has its own source, by design. Cross-resource synthesis inside one resolver is a sign the wrong source is being asked to do the work.

Batch endpoints need a dict

Some endpoints enumerate the full id set (e.g. the /v1/catalog/<storefront>/genres listing). Those require a dict source — callable sources can't be enumerated by design. The mock raises a descriptive ValueError if a batch endpoint hits a callable source; switch that specific resource to a dict.