A simple League of Legends SDK with built-in rate limiting & disk caching.
Trying to restore the glory days, pre Riot IDs.
Note
v1.0.0: This project is currently in early release. The API is stable enough for usage, but features are still being actively added.
- Built for Python freaks.
- Robust, Pythonic models
- Timestamps are
datetimeobjects - So many enums you'll hate them (but autocomplete makes it worth)
- Clean high-level API wraps the messy Riot API underneath.
- Pull player ranks, match history, and champion stats with just a few lines of code.
Hate Riot IDs? Who doesn't. Just NexarClient.get_player("username", "tag") and explore with your IDE.
Packed with helpful doc strings and tips.
Check out Why not Nexar? for more
uv pip install "git+https://github.com/joshpaulie/nexar@v1.0.0"Below is a real, working example from README_example.py:
"""Example from README showing async player information retrieval."""
import asyncio
import os
import sys
from datetime import UTC, datetime
from nexar.cache import SMART_CACHE_CONFIG
from nexar.client import NexarClient
from nexar.enums import Region
async def main() -> None:
"""Demonstrate player information retrieval using the async API."""
# Get API key from environment
api_key = os.getenv("RIOT_API_KEY")
if not api_key:
sys.exit("Please set RIOT_API_KEY environment variable")
# Create async client
# Note: Using sqlite cache (default) persists rate limits (to ~/.nexar/rate_limits.db) and responses to disk
client = NexarClient(
riot_api_key=api_key,
default_region=Region.NA1,
cache_config=SMART_CACHE_CONFIG,
)
async with client:
print("Fetching player info...")
# Get player information
player = await client.get_player("bexli", "bex")
print()
riot_account = player.riot_account # Immediately available!
summoner = await player.get_summoner()
rank = await player.get_solo_rank()
print(f"Summoner: {riot_account.game_name}#{riot_account.tag_line}")
print(f"Level: {summoner.summoner_level}")
if rank:
print(f"Solo Queue rank: {rank.tier} {rank.division}\n")
print("Fetching recent matches...")
# Get and display recent matches
recent_matches = await player.get_matches(count=5)
print(f"Recent Match History ({len(recent_matches)} matches):\n")
for match in recent_matches:
# Get participant stats of particular summoner
participant = match.participants.by_puuid(player.puuid)
if not participant:
continue
result = "Victory!" if participant.win else "Defeat."
kda = participant.kda(as_str=True)
kda_ratio = "N/A"
if participant.challenges:
kda_ratio = f"{participant.challenges.kda:.2f}"
# Calculate time ago
game_start = match.info.game_start_timestamp
# Ensure game_start is timezone-aware
if game_start.tzinfo is None:
game_start = game_start.replace(tzinfo=UTC)
days_ago = (datetime.now(tz=UTC) - game_start).days
if days_ago == 0:
time_str = "Today"
elif days_ago == 1:
time_str = "Yesterday"
else:
time_str = f"{days_ago} days ago"
print(
f"{time_str:<12} "
f"{result:<9} "
f"{participant.champion_name:<10} "
f"{participant.team_position.value.title():<8} "
f"{kda} ({kda_ratio})",
)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
passSummoner: bexli#bex
Level: 512
Solo Queue rank: Silver III
Recent Match History (5 matches):
1 day ago Victory! Jhin Bottom 11/6/8 (3.17)
1 day ago Victory! Jhin Bottom 10/6/10 (3.33)
1 day ago Victory! Jinx Bottom 11/6/7 (3.00)
2 days ago Defeat. Jinx Bottom 11/16/13 (1.50)
2 days ago Victory! Jinx Bottom 9/1/12 (21.00)
Features requests should start as issues. Bugs may start as PRs.
# Fork the repo
# Clone fork locally
git clone https://github.com/username/nexar
# Make changes on branch
# Ensure functionality and new tests if needed
# Ensure tests are still passing
# Make PRTests use real Riot API calls rather than mocks. You'll need a valid Riot API key:
- Copy
riot-key.sh.exampletoriot-key.sh - Add your Riot API key to
riot-key.sh - Run tests with the provided script:
./run_tests.shThe script automatically sources your API key and runs the test suite.
- Valid Riot API key
- Active internet connection
- Tests may be rate-limited by Riot's API
Tests, transforming the API response schemas to models, and other large scale chores were contributed by Github Copilot with Anthropic's Claude Sonnet 4 model.
Cheers to the Anthropic team. This was the only model that didn't make me want to tear my hair out. Easily saved me hours of boring contributions.
Used sparingly, and in the same fashion as above, it would hypocritical to not allow LLM contributions. But vibe coded, unchecked, slop may result in ban.