universal-game-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_game_api-0.1.0/.github/workflows/python-package.yml +40 -0
- universal_game_api-0.1.0/.gitignore +15 -0
- universal_game_api-0.1.0/CHANGELOG.md +38 -0
- universal_game_api-0.1.0/CONTRIBUTING.md +85 -0
- universal_game_api-0.1.0/LICENSE +21 -0
- universal_game_api-0.1.0/PKG-INFO +291 -0
- universal_game_api-0.1.0/README.md +239 -0
- universal_game_api-0.1.0/demo-project/pyproject.toml +24 -0
- universal_game_api-0.1.0/demo-project/src/demo_project/__init__.py +1 -0
- universal_game_api-0.1.0/demo-project/src/demo_project/__main__.py +6 -0
- universal_game_api-0.1.0/demo-project/src/demo_project/dashboard.py +46 -0
- universal_game_api-0.1.0/demo-project/src/demo_project/main.py +60 -0
- universal_game_api-0.1.0/demo-project/tests/test_demo.py +29 -0
- universal_game_api-0.1.0/examples/async_usage.py +18 -0
- universal_game_api-0.1.0/examples/basic_usage.py +18 -0
- universal_game_api-0.1.0/examples/compare_players.py +8 -0
- universal_game_api-0.1.0/pyproject.toml +64 -0
- universal_game_api-0.1.0/src/gameapi/__init__.py +48 -0
- universal_game_api-0.1.0/src/gameapi/_base.py +59 -0
- universal_game_api-0.1.0/src/gameapi/async_client.py +40 -0
- universal_game_api-0.1.0/src/gameapi/cache/__init__.py +5 -0
- universal_game_api-0.1.0/src/gameapi/cache/memory.py +40 -0
- universal_game_api-0.1.0/src/gameapi/client.py +43 -0
- universal_game_api-0.1.0/src/gameapi/exceptions.py +95 -0
- universal_game_api-0.1.0/src/gameapi/games/__init__.py +6 -0
- universal_game_api-0.1.0/src/gameapi/games/base.py +60 -0
- universal_game_api-0.1.0/src/gameapi/games/chess_com/__init__.py +5 -0
- universal_game_api-0.1.0/src/gameapi/games/chess_com/client.py +271 -0
- universal_game_api-0.1.0/src/gameapi/games/chess_com/endpoints.py +21 -0
- universal_game_api-0.1.0/src/gameapi/games/chess_com/models.py +35 -0
- universal_game_api-0.1.0/src/gameapi/games/lichess/__init__.py +5 -0
- universal_game_api-0.1.0/src/gameapi/games/lichess/client.py +156 -0
- universal_game_api-0.1.0/src/gameapi/games/lichess/endpoints.py +21 -0
- universal_game_api-0.1.0/src/gameapi/games/lichess/models.py +41 -0
- universal_game_api-0.1.0/src/gameapi/games/registry.py +25 -0
- universal_game_api-0.1.0/src/gameapi/http/__init__.py +5 -0
- universal_game_api-0.1.0/src/gameapi/http/client.py +203 -0
- universal_game_api-0.1.0/src/gameapi/models/__init__.py +15 -0
- universal_game_api-0.1.0/src/gameapi/models/leaderboard.py +34 -0
- universal_game_api-0.1.0/src/gameapi/models/match.py +31 -0
- universal_game_api-0.1.0/src/gameapi/models/player.py +27 -0
- universal_game_api-0.1.0/src/gameapi/models/stats.py +27 -0
- universal_game_api-0.1.0/tests/__init__.py +0 -0
- universal_game_api-0.1.0/tests/conftest.py +119 -0
- universal_game_api-0.1.0/tests/test_cache.py +53 -0
- universal_game_api-0.1.0/tests/test_chess_com.py +155 -0
- universal_game_api-0.1.0/tests/test_client.py +72 -0
- universal_game_api-0.1.0/tests/test_exceptions.py +47 -0
- universal_game_api-0.1.0/tests/test_http_client.py +118 -0
- universal_game_api-0.1.0/tests/test_integration.py +46 -0
- universal_game_api-0.1.0/tests/test_lichess.py +61 -0
- universal_game_api-0.1.0/tests/test_live.py +52 -0
- universal_game_api-0.1.0/tests/test_models.py +56 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: Python package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "main" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v3
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install flake8 pytest
|
|
31
|
+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
32
|
+
- name: Lint with flake8
|
|
33
|
+
run: |
|
|
34
|
+
# stop the build if there are Python syntax errors or undefined names
|
|
35
|
+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
36
|
+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
37
|
+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
38
|
+
- name: Test with pytest
|
|
39
|
+
run: |
|
|
40
|
+
pytest
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.2.0] — 2026-08-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Lichess integration** (`lichess` slug) — player profiles and leaderboards via the public Lichess API.
|
|
9
|
+
- **`compare_players(game, identifiers)`** — batch-fetch multiple players in one call (sync + async).
|
|
10
|
+
- **`game_info(game)`** — introspect integration metadata (slug, auth requirements, source URL).
|
|
11
|
+
- **Model helpers**:
|
|
12
|
+
- `Match.is_win`, `Match.is_loss`, `Match.is_draw`
|
|
13
|
+
- `Player.win_rate_pct()` — returns win rate as a 0–100 percentage
|
|
14
|
+
- `Leaderboard.top(n)` — slice the top N entries
|
|
15
|
+
- **Live test suite** (`tests/test_live.py`) — run with `pytest -m live` to test against real APIs.
|
|
16
|
+
- **Demo project** (`demo-project/`) — CLI tool and Rich dashboard built on top of `gameapi`.
|
|
17
|
+
- **Examples** (`examples/`) — `basic_usage.py`, `async_usage.py`, `compare_players.py`.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **HTTP base headers** — `HTTPClient` now correctly forwards `base_headers` to `httpx.Client` and `httpx.AsyncClient` (fixes missing `User-Agent` on requests).
|
|
21
|
+
- **Retry-After handling** — 429 responses now respect the `Retry-After` header instead of always using exponential backoff.
|
|
22
|
+
- **Missing import** — `GameAPI.__repr__` now properly imports `supported_games`.
|
|
23
|
+
- **Pylance warnings** — guarded `game_data` access in tests to satisfy `reportOptionalMemberAccess`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Bumped version to `0.2.0`.
|
|
27
|
+
- `pyproject.toml` now includes `lichess` in keywords and classifiers.
|
|
28
|
+
- `pytest.ini_options` adds a `live` marker for optional live API tests.
|
|
29
|
+
|
|
30
|
+
## [0.1.0] — 2026-08-20
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- Initial release with Chess.com integration.
|
|
34
|
+
- Unified models: `Player`, `PlayerStats`, `Rank`, `Match`, `Leaderboard`, `LeaderboardEntry`.
|
|
35
|
+
- Sync (`GameAPI`) and async (`AsyncGameAPI`) clients.
|
|
36
|
+
- In-memory TTL cache (`MemoryCache`).
|
|
37
|
+
- Retry logic with exponential backoff for 429/500/502/503/504.
|
|
38
|
+
- Exception hierarchy: `GameAPIError`, `PlayerNotFoundError`, `RateLimitError`, etc.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Contributing to gameapi
|
|
2
|
+
|
|
3
|
+
Thanks for considering a contribution! The most common contribution is a
|
|
4
|
+
new game integration, so that's covered in detail below.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/F0xyN0xy/universal-game-api
|
|
10
|
+
cd gameapi
|
|
11
|
+
pip install -e ".[dev]"
|
|
12
|
+
pytest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Adding a new game integration
|
|
16
|
+
|
|
17
|
+
1. **Confirm there's a legitimate public data source.** Only integrate APIs
|
|
18
|
+
that developers are permitted to access under their terms of service —
|
|
19
|
+
don't scrape sites in ways that violate their terms, and don't bypass
|
|
20
|
+
auth, rate limits, or anti-bot systems. If a game doesn't have a
|
|
21
|
+
suitable public API, don't add a fake/partial integration for it.
|
|
22
|
+
|
|
23
|
+
2. **Create a new module** under `src/gameapi/games/<your_game>/`:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
games/
|
|
27
|
+
└── your_game/
|
|
28
|
+
├── __init__.py
|
|
29
|
+
├── client.py # subclasses GameIntegration
|
|
30
|
+
├── models.py # game-specific dataclasses (exposed via game_data)
|
|
31
|
+
└── endpoints.py # URL builders
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `games/chess_com/` as the reference implementation.
|
|
35
|
+
|
|
36
|
+
3. **Subclass `GameIntegration`** (`gameapi.games.base.GameIntegration`) and
|
|
37
|
+
implement at minimum `get_player` and `get_player_async`. Implement
|
|
38
|
+
`get_matches`/`get_matches_async` and `get_leaderboard`/
|
|
39
|
+
`get_leaderboard_async` only if the underlying API actually supports
|
|
40
|
+
them — the base class already raises a clear `NotImplementedError`
|
|
41
|
+
otherwise.
|
|
42
|
+
|
|
43
|
+
All HTTP calls should go through `self.http` (a shared `HTTPClient`) —
|
|
44
|
+
never call `httpx`/`requests` directly from a game integration. This
|
|
45
|
+
keeps retries, timeouts, and error translation consistent everywhere.
|
|
46
|
+
|
|
47
|
+
4. **Map the game's data onto the unified models** (`Player`, `PlayerStats`,
|
|
48
|
+
`Rank`, `Match`, `Leaderboard`) wherever a field genuinely corresponds.
|
|
49
|
+
Don't force a field that doesn't generalize — put it on `game_data`
|
|
50
|
+
instead (a dataclass you define in your integration's `models.py`).
|
|
51
|
+
|
|
52
|
+
5. **Register your integration** in `src/gameapi/games/registry.py`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from .your_game.client import YourGameIntegration
|
|
56
|
+
|
|
57
|
+
GAME_REGISTRY["your_game"] = YourGameIntegration
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
6. **Translate errors.** Catch the upstream API's "not found" signal (often
|
|
61
|
+
an HTTP 404) and re-raise it as `PlayerNotFoundError(self.slug, identifier)`
|
|
62
|
+
with useful context. Let other `GameAPIError` subclasses raised by the
|
|
63
|
+
shared `HTTPClient` propagate as-is.
|
|
64
|
+
|
|
65
|
+
7. **Write tests** in `tests/test_your_game.py` using `respx` to mock HTTP
|
|
66
|
+
responses — don't depend on the live API in the standard test suite.
|
|
67
|
+
See `tests/test_chess_com.py` for the pattern. If you want, add a
|
|
68
|
+
separate integration test (clearly marked) that hits the real API.
|
|
69
|
+
|
|
70
|
+
8. **Document it** — add a row to the "Supported games" table in
|
|
71
|
+
`README.md`, including the data source, whether auth is required, and
|
|
72
|
+
a link to the upstream API's documentation.
|
|
73
|
+
|
|
74
|
+
## Code style
|
|
75
|
+
|
|
76
|
+
- Type hints on all public functions/methods.
|
|
77
|
+
- Docstrings on all public classes and methods (Google style, as used
|
|
78
|
+
throughout the codebase).
|
|
79
|
+
- Run `ruff check .` and `mypy src/` before submitting.
|
|
80
|
+
|
|
81
|
+
## Pull requests
|
|
82
|
+
|
|
83
|
+
- Keep PRs focused — one game integration or one fix per PR.
|
|
84
|
+
- Include tests for anything you add or change.
|
|
85
|
+
- Update `CHANGELOG.md` under an "Unreleased" heading.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 F0xyN0xy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: universal-game-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A unified, developer-friendly Python interface for public game data and statistics.
|
|
5
|
+
Project-URL: Homepage, https://github.com/F0xyN0xy/universal-game-api
|
|
6
|
+
Project-URL: Documentation, https://github.com/F0xyN0xy/universal-game-api#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/F0xyN0xy/universal-game-api/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/F0xyN0xy/universal-game-api/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: F0xyN0xy <foxynoxy07@proton.me>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 F0xyN0xy
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: api,chess,games,gaming,lichess,python,statistics
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Classifier: Typing :: Typed
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Requires-Dist: httpx<1.0,>=0.25
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: mypy>=1.5; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
49
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
50
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# Universal Game API (`gameapi`)
|
|
54
|
+
|
|
55
|
+
> One interface. Every game. No API archaeology.
|
|
56
|
+
|
|
57
|
+
`gameapi` is a Python library that unifies public game APIs behind a single, consistent interface. Instead of learning a new client for every game, you write the same code for Chess.com, Lichess, and whatever comes next.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from gameapi import GameAPI
|
|
61
|
+
|
|
62
|
+
with GameAPI() as api:
|
|
63
|
+
player = api.player(game="chess_com", identifier="hikaru")
|
|
64
|
+
print(player.name, player.rank.rating) # hikaru 2800
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Why
|
|
70
|
+
|
|
71
|
+
Every game API looks different:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# Without gameapi
|
|
75
|
+
chess_client.get_profile(...)
|
|
76
|
+
rl_client.fetch_stats(...)
|
|
77
|
+
mc_client.player_lookup(...)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`gameapi` gives you one shape instead:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# With gameapi
|
|
84
|
+
api.player(game="chess_com", identifier="...")
|
|
85
|
+
api.player(game="lichess", identifier="...")
|
|
86
|
+
api.player(game="rocket_league", identifier="...") # once implemented
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Common fields (`name`, `stats`, `rank`) are normalized. Anything that doesn't generalize lives on `player.game_data`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Supported Games
|
|
94
|
+
|
|
95
|
+
| Game | Slug | Auth Required | Data Source |
|
|
96
|
+
|-----------|-------------|---------------|--------------------------------------|
|
|
97
|
+
| Chess.com | `chess_com` | No | Chess.com Published-Data API |
|
|
98
|
+
| Lichess | `lichess` | No | Lichess Public API |
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from gameapi import supported_games
|
|
102
|
+
print(supported_games()) # ['chess_com', 'lichess']
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Installation
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install gameapi
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Requires Python 3.9+.
|
|
114
|
+
|
|
115
|
+
### Development
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/F0xyN0xy/universal-game-api.git
|
|
119
|
+
cd universal-game-api
|
|
120
|
+
pip install -e ".[dev]"
|
|
121
|
+
pytest
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Quick Start
|
|
127
|
+
|
|
128
|
+
### Player Profile
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from gameapi import GameAPI
|
|
132
|
+
|
|
133
|
+
with GameAPI() as api:
|
|
134
|
+
player = api.player(game="chess_com", identifier="hikaru")
|
|
135
|
+
print(player.name) # hikaru
|
|
136
|
+
print(player.rank.tier) # GM
|
|
137
|
+
print(player.rank.rating) # 2800
|
|
138
|
+
print(player.stats) # PlayerStats(games_played=..., wins=...)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Recent Matches
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
for match in api.matches(game="chess_com", identifier="hikaru", limit=10):
|
|
145
|
+
print(match.result, match.opponent, match.played_at)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Leaderboard
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
board = api.leaderboard(game="chess_com")
|
|
152
|
+
for entry in board.top(5):
|
|
153
|
+
print(entry.position, entry.name, entry.rating)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Batch Lookups
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
players = api.compare_players("chess_com", ["hikaru", "magnuscarlsen", "nihalsarin"])
|
|
160
|
+
for p in players:
|
|
161
|
+
print(p.name, p.rank.rating)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Async
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import asyncio
|
|
168
|
+
from gameapi import AsyncGameAPI
|
|
169
|
+
|
|
170
|
+
async def main():
|
|
171
|
+
async with AsyncGameAPI() as api:
|
|
172
|
+
player = await api.player(game="lichess", identifier="drnykterstein")
|
|
173
|
+
print(player.name)
|
|
174
|
+
|
|
175
|
+
asyncio.run(main())
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Caching
|
|
181
|
+
|
|
182
|
+
Optional in-process caching reduces redundant requests:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
api = GameAPI(cache=True, cache_ttl=60) # seconds
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Nothing sensitive is ever cached — only parsed response data.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Rate Limits & Retries
|
|
193
|
+
|
|
194
|
+
`gameapi` retries transient failures (HTTP 429/500/502/503/504) with exponential backoff, then raises typed exceptions:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from gameapi import RateLimitError
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
player = api.player(game="chess_com", identifier="hikaru")
|
|
201
|
+
except RateLimitError as e:
|
|
202
|
+
print(f"Rate limited, retry after {e.retry_after}s")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
429 responses respect the upstream `Retry-After` header when provided.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Error Handling
|
|
210
|
+
|
|
211
|
+
All exceptions inherit from `GameAPIError`:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from gameapi import (
|
|
215
|
+
GameAPIError,
|
|
216
|
+
GameNotSupportedError,
|
|
217
|
+
PlayerNotFoundError,
|
|
218
|
+
AuthenticationError,
|
|
219
|
+
RateLimitError,
|
|
220
|
+
APIUnavailableError,
|
|
221
|
+
InvalidResponseError,
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## API Reference
|
|
228
|
+
|
|
229
|
+
### `GameAPI(api_key=None, cache=False, cache_ttl=60.0, timeout=10.0, max_retries=2)`
|
|
230
|
+
|
|
231
|
+
| Method | Returns |
|
|
232
|
+
|--------|---------|
|
|
233
|
+
| `player(game, identifier)` | `Player` |
|
|
234
|
+
| `matches(game, identifier, limit=20)` | `list[Match]` |
|
|
235
|
+
| `leaderboard(game, region=None)` | `Leaderboard` |
|
|
236
|
+
| `compare_players(game, identifiers)` | `list[Player]` |
|
|
237
|
+
| `game_info(game)` | `dict` |
|
|
238
|
+
| `close()` | — |
|
|
239
|
+
|
|
240
|
+
Context-manager compatible: `with GameAPI() as api:`
|
|
241
|
+
|
|
242
|
+
`AsyncGameAPI` has identical signatures, `await`-ed.
|
|
243
|
+
|
|
244
|
+
### Models
|
|
245
|
+
|
|
246
|
+
- `Player` — `name`, `game`, `identifier`, `stats`, `rank`, `game_data`, `avatar_url`
|
|
247
|
+
- `PlayerStats` — `games_played`, `wins`, `losses`, `draws`, `win_rate`
|
|
248
|
+
- `Rank` — `tier`, `rating`, `position`, `raw`
|
|
249
|
+
- `Match` — `id`, `game`, `played_at`, `result`, `opponent`, `game_data`
|
|
250
|
+
- `Leaderboard` / `LeaderboardEntry` — `position`, `name`, `rating`
|
|
251
|
+
|
|
252
|
+
Every model is a `@dataclass`, so `dataclasses.asdict(player)` works out of the box.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Demo Project
|
|
257
|
+
|
|
258
|
+
A small CLI and dashboard built on `gameapi` lives in `demo-project/`:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
cd demo-project
|
|
262
|
+
pip install -e "."
|
|
263
|
+
|
|
264
|
+
# Look up a player
|
|
265
|
+
python -m demo_project chess_com hikaru -m 5 -l
|
|
266
|
+
|
|
267
|
+
# Compare two players side-by-side
|
|
268
|
+
python src/demo_project/dashboard.py chess_com hikaru magnuscarlsen
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Contributing
|
|
274
|
+
|
|
275
|
+
See `CONTRIBUTING.md` for how to add a new game integration. The pattern is:
|
|
276
|
+
|
|
277
|
+
1. Create `src/gameapi/games/<game>/`
|
|
278
|
+
2. Subclass `GameIntegration`
|
|
279
|
+
3. Register it in `games/registry.py`
|
|
280
|
+
|
|
281
|
+
No changes to `client.py` or `async_client.py` are needed.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
288
|
+
|
|
289
|
+
## Legal
|
|
290
|
+
|
|
291
|
+
`gameapi` only integrates with public APIs that permit this kind of access under their terms of service. It does not scrape websites in ways that violate their terms, and does not attempt to bypass authentication, rate limits, or anti-bot protections.
|