Quickstart¶
This tutorial will walk you through the basics of using PokeLance, from setting up a client and understanding its lifecycle to fetching resources from the PokéAPI.
PokeLance supports both asynchronous (PokeLanceAsyncClient) and synchronous (PokeLanceSyncClient) paradigms out of the box.
Creating a client¶
The simplest possible client needs no arguments at all:
PokeLance lazily creates its own niquests.AsyncSession or niquests.Session on the first request. That's convenient for quick scripts, but for anything long-lived (bots, web servers) prefer context managers so the session is guaranteed to close cleanly.
With a context manager¶
import asyncio
from pokelance import PokeLanceAsyncClient
async def main() -> None:
async with PokeLanceAsyncClient() as client:
print(await client.ping())
berry = await client.berry.fetch_berry("cheri")
print(berry.name)
# client and HTTP session are both closed here automatically
asyncio.run(main())
import asyncio
import niquests
from pokelance import PokeLanceAsyncClient
async def main() -> None:
async with niquests.AsyncSession() as session, PokeLanceAsyncClient(session=session) as client:
print(await client.ping())
berry = await client.berry.fetch_berry("cheri")
print(berry.name)
asyncio.run(main())
Bring your own session
Passing your own niquests.AsyncSession or niquests.Session is useful when PokeLance shares a connection pool with other HTTP services in your app.
More on Async Context Managers
If you're new to async context managers in Python, check out the Python Documentation on Context Managers and contextlib.asynccontextmanager.
Fetching a few resources¶
This example hits the PokéAPI endpoint to fetch a berry, its flavor, and its firmness:
import asyncio
from pokelance import PokeLanceAsyncClient
async def main() -> None:
async with PokeLanceAsyncClient() as client:
latency = await client.ping()
berry = await client.berry.fetch_berry("cheri")
flavor = await client.berry.fetch_berry_flavor(berry.flavors[0].flavor.name)
firmness = await client.berry.fetch_berry_firmness(berry.firmness.name)
print(f"ping: {latency:.4f}s")
print(f"berry: {berry.name} (id={berry.id}, growth_time={berry.growth_time})")
print(f"flavor: {flavor.name}")
print(f"firmness: {firmness.name}")
asyncio.run(main())
from pokelance import PokeLanceSyncClient
with PokeLanceSyncClient() as client:
latency = client.ping()
berry = client.berry.fetch_berry("cheri")
flavor = client.berry.fetch_berry_flavor(berry.flavors[0].flavor.name)
firmness = client.berry.fetch_berry_firmness(berry.firmness.name)
print(f"ping: {latency:.4f}s")
print(f"berry: {berry.name} (id={berry.id}, growth_time={berry.growth_time})")
print(f"flavor: {flavor.name}")
print(f"firmness: {firmness.name}")
Fully Typed Models
All models and each of their fields are fully typed with attrs and modern type annotations, providing accurate autocomplete and validation in IDEs, Pyright, and Ty.
Fetching media resources¶
The client includes convenience methods (get_image and get_audio) for fetching media resources with built-in LRU caching:
import asyncio
import base64
from pokelance import PokeLanceAsyncClient
async def main() -> str:
async with PokeLanceAsyncClient() as client:
pokemon = await client.pokemon.fetch_pokemon("pikachu")
assert pokemon.sprites.front_default is not None
sprite = await client.get_image(pokemon.sprites.front_default)
encoded = base64.b64encode(sprite).decode("ascii")
return f'<img src="data:image/png;base64,{encoded}" alt="{pokemon.name} sprite" width="96" height="96"/>'
print(asyncio.run(main()))
See Media for the full breakdown of get_image and get_audio.
Reading the response as a dict¶
Every model inherits to_dict() (see BaseModel), which recursively serializes attrs models and enums back into plain Python data:
import asyncio
import json
from pokelance import PokeLanceAsyncClient
async def main() -> str:
async with PokeLanceAsyncClient() as client:
berry = await client.berry.fetch_berry("cheri")
return json.dumps(berry.to_dict(), indent=2, default=str)[:600] + "\n..."
print(asyncio.run(main()))
{
"id": 1,
"name": "cheri",
"growth_time": 3,
"max_harvest": 5,
"natural_gift_power": 60,
"size": 20,
"smoothness": 25,
"soil_dryness": 15,
"firmness": {
"name": "soft",
"url": "https://pokeapi.co/api/v2/berry-firmness/2/"
},
"flavors": [
{
"potency": 10,
"flavor": {
"name": "spicy",
"url": "https://pokeapi.co/api/v2/berry-flavor/1/"
}
},
{
"potency": 0,
"flavor": {
"name": "dry",
"url": "https://pokeapi.co/api/v2/berry-flavor/2/"
}
},
{
"potency": 0,
"flavor": {
...
berry.raw is also always available if you need the exact untouched JSON PokéAPI payload.
Cache-then-fetch, by hand¶
Every resource category provides a cache lookup (get_*) and a network fetch (fetch_*) counterpart:
# Synchronous cache check:
print(client.berry.get_berry("cheri")) # None on cold cache
# Network fetch & cache population:
print(await client.berry.fetch_berry("cheri")) # hits network, populates cache
# Subsequent cache check:
print(client.berry.get_berry("cheri")) # cached, instant lookup
See Fetching Data for details, and getch_data for a single call that performs cache-then-fetch across any extension.
Configuring the API Base URL¶
By default, PokeLance connects to https://pokeapi.co/api/v2/. You can override this by setting the POKEAPI_BASE_URL environment variable:
Next steps¶
- Configuration: cache sizes, logging, endpoint pre-loading
- Extensions Reference: the full map of what you can fetch
- Caching In Depth: in-memory LRU and disk cache persistence