Skip to content

Dead simple League of Legend API lookups. Match history, solo/flex queue ranks, champion performance stats, all in a few lines of Python.

License

Notifications You must be signed in to change notification settings

JoshPaulie/nexar

Repository files navigation

Nexar

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.

Why Nexar?

  • Built for Python freaks.
    • Robust, Pythonic models
    • Timestamps are datetime objects
    • 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

Installation

uv pip install "git+https://github.com/joshpaulie/nexar@v1.0.0"

Usage example

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:
        pass

Example Output

Summoner: 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)

Development and contributing

Contributing

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 PR

Running Tests

Tests use real Riot API calls rather than mocks. You'll need a valid Riot API key:

  1. Copy riot-key.sh.example to riot-key.sh
  2. Add your Riot API key to riot-key.sh
  3. Run tests with the provided script:
./run_tests.sh

The script automatically sources your API key and runs the test suite.

Test Requirements

  • Valid Riot API key
  • Active internet connection
  • Tests may be rate-limited by Riot's API

LLMs and the project

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.

LLM 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.

About

Dead simple League of Legend API lookups. Match history, solo/flex queue ranks, champion performance stats, all in a few lines of Python.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •