Skip to content

Media: Sprites & Cries⚓︎

Pokémon models come back from PokéAPI full of URLs to sprites and cries, not the raw bytes. PokeLance gives you two async helpers to fetch those, each backed by their own async LRU cache so re-downloading the same sprite is instant. This page also shows both rendered inline, live: the bytes are downloaded at doc-build time, base64-encoded, and dropped straight into an <img>/<audio> tag.

Fetching an image⚓︎

get_image_async downloads and validates any sprite URL, returning raw bytes:

Python
import asyncio
from pokelance import PokeLance

client = PokeLance()


async def main() -> str:
    pokemon = await client.pokemon.fetch_pokemon("pikachu")
    img = await client.get_image_async(pokemon.sprites.front_default)
    await client.close()
    return f"downloaded {len(img)} bytes from {pokemon.sprites.front_default}"


print(asyncio.run(main()))
Text Only
downloaded 597 bytes from https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png

Accepted content types are png, jpg, jpeg, gif, webp, and svg, anything else (or a non-2xx response) raises ImageNotFound.

Embedding it as <img>⚓︎

Python
import asyncio
import base64
from pokelance import PokeLance

client = PokeLance()


async def main() -> str:
    pokemon = await client.pokemon.fetch_pokemon("pikachu")
    img = await client.get_image_async(pokemon.sprites.front_default)
    await client.close()
    encoded = base64.b64encode(img).decode("ascii")
    return f'<img src="data:image/png;base64,{encoded}" alt="pikachu sprite" width="96" height="96"/>'


print(asyncio.run(main()))
pikachu sprite

Fetching a cry⚓︎

get_audio_async works identically, for the newer cries field on Pokemon models:

Python
import asyncio
from pokelance import PokeLance

client = PokeLance()


async def main() -> str:
    pokemon = await client.pokemon.fetch_pokemon("pikachu")
    audio = await client.get_audio_async(pokemon.cries.latest)
    await client.close()
    return f"downloaded {len(audio)} bytes from {pokemon.cries.latest}"


print(asyncio.run(main()))
Text Only
downloaded 7080 bytes from https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/25.ogg

Accepted content types here are ogg, wav, and mp3; anything else raises AudioNotFound.

Embedding it as <audio>⚓︎

Python
import asyncio
import base64
from pokelance import PokeLance

client = PokeLance()


async def main() -> str:
    pokemon = await client.pokemon.fetch_pokemon("pikachu")
    audio = await client.get_audio_async(pokemon.cries.latest)
    await client.close()
    encoded = base64.b64encode(audio).decode("ascii")
    return (
        f'<audio controls preload="none">'
        f'<source src="data:audio/ogg;base64,{encoded}" type="audio/ogg"></audio>'
    )


print(asyncio.run(main()))

Why these are cached separately⚓︎

Unlike model caches (bounded by cache_size), image and audio helpers are decorated with PokeLance's own alru_cache, an async-aware LRU cache that also de-duplicates concurrent in-flight requests for the same URL (two coroutines awaiting the same sprite at the same time share one download instead of firing two).

Sizes are configured independently from the model cache, via the client constructor or properties (see Configuration):

Python
client = PokeLance(image_cache_size=256, audio_cache_size=64)
client.image_cache_size = 512  # can also be changed after construction

A cached hit is measurably faster since it skips the network entirely:

Python
import asyncio
import time

from pokelance import PokeLance


async def main() -> None:
    client = PokeLance()
    url = (await client.pokemon.fetch_pokemon("pikachu")).sprites.front_default

    t0 = time.perf_counter()
    await client.get_image_async(url)
    first = time.perf_counter() - t0

    t1 = time.perf_counter()
    await client.get_image_async(url)  # same URL: served from the LRU cache
    second = time.perf_counter() - t1

    print(f"first fetch: {first:.4f}s, second fetch: {second:.4f}s")
    assert first > second
    await client.close()


asyncio.run(main())
Text Only
first fetch: 0.0209s, second fetch: 0.0000s

Error handling⚓︎

Both helpers raise before returning any bytes if the response isn't actually image/audio content, or the request failed outright:

Python
import asyncio
from pokelance import PokeLance
from pokelance.exceptions import ImageNotFound

client = PokeLance()


async def main() -> None:
    try:
        await client.get_image_async("https://pokeapi.co/api/v2/pokemon/invalid")
    except ImageNotFound as exc:
        print(exc)  # e.g. "... was unsuccessful or the URL is not an image. (...) | ... | 404"
    finally:
        await client.close()

asyncio.run(main())
Text Only
Request to https://pokeapi.co/api/v2/pokemon/invalid was unsuccessful or the URL is not an image. (text/plain) | <Route endpoint= method=GET> | 404

See Error Handling for the full exception hierarchy.

Comments