classicchess-sdk 0.1.1__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.
- classicchess_sdk-0.1.1/LICENSE +21 -0
- classicchess_sdk-0.1.1/PKG-INFO +248 -0
- classicchess_sdk-0.1.1/README.md +235 -0
- classicchess_sdk-0.1.1/classicchess_api/__init__.py +31 -0
- classicchess_sdk-0.1.1/classicchess_api/__main__.py +6 -0
- classicchess_sdk-0.1.1/classicchess_api/application.py +432 -0
- classicchess_sdk-0.1.1/classicchess_api/cli.py +565 -0
- classicchess_sdk-0.1.1/classicchess_api/client.py +671 -0
- classicchess_sdk-0.1.1/classicchess_api/origin_policy.py +81 -0
- classicchess_sdk-0.1.1/classicchess_api/presentation.py +88 -0
- classicchess_sdk-0.1.1/classicchess_api/response.py +123 -0
- classicchess_sdk-0.1.1/classicchess_sdk.egg-info/PKG-INFO +248 -0
- classicchess_sdk-0.1.1/classicchess_sdk.egg-info/SOURCES.txt +16 -0
- classicchess_sdk-0.1.1/classicchess_sdk.egg-info/dependency_links.txt +1 -0
- classicchess_sdk-0.1.1/classicchess_sdk.egg-info/entry_points.txt +2 -0
- classicchess_sdk-0.1.1/classicchess_sdk.egg-info/top_level.txt +1 -0
- classicchess_sdk-0.1.1/pyproject.toml +24 -0
- classicchess_sdk-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christian Walls
|
|
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,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: classicchess-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Classic Chess archive and MasterDB API client and CLI
|
|
5
|
+
Author: Christian Walls
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://classicchess.com/api/
|
|
8
|
+
Project-URL: Repository, https://github.com/Andromeda1957/classicchess-api-python
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Classic Chess Python SDK
|
|
15
|
+
|
|
16
|
+
Use Python to read chess games, player biographies, notable games, tournament
|
|
17
|
+
archives and annotated books from [Classic Chess](https://classicchess.com/).
|
|
18
|
+
The SDK also searches the larger MasterDB game database, retrieves opening
|
|
19
|
+
statistics, exports games, and calls account and application APIs.
|
|
20
|
+
|
|
21
|
+
Public reads need no account or API key. The SDK uses the Python standard
|
|
22
|
+
library and is MIT licensed; see
|
|
23
|
+
[LICENSE](https://github.com/Andromeda1957/classicchess-api-python/blob/main/LICENSE).
|
|
24
|
+
|
|
25
|
+
## Install and run your first example
|
|
26
|
+
|
|
27
|
+
Install [Python 3.10 or newer](https://www.python.org/downloads/). In a terminal,
|
|
28
|
+
create an isolated Python environment and install from
|
|
29
|
+
[PyPI](https://pypi.org/project/classicchess-sdk/):
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
python3 -m venv classicchess-env
|
|
33
|
+
source classicchess-env/bin/activate
|
|
34
|
+
python3 -m pip install classicchess-sdk
|
|
35
|
+
classicchess-api public players --names
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
On Windows PowerShell, use `py` instead of `python3` and activate with
|
|
39
|
+
`classicchess-env\Scripts\Activate.ps1` instead of the `source` command.
|
|
40
|
+
The last command prints the names of all curated players. To install from the
|
|
41
|
+
public source repository instead, install [Git](https://git-scm.com/) and use
|
|
42
|
+
`python3 -m pip install git+https://github.com/Andromeda1957/classicchess-api-python.git`.
|
|
43
|
+
|
|
44
|
+
To use it in a Python program, create `example.py` with this complete example:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from classicchess_api import ClassicChessClient
|
|
48
|
+
|
|
49
|
+
api = ClassicChessClient()
|
|
50
|
+
print(api.player_names()[:10])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Run `python3 example.py` in the same activated environment. You should see up to
|
|
54
|
+
ten player names. The PyPI distribution is `classicchess-sdk`; the import is
|
|
55
|
+
`classicchess_api` and the command-line executable is `classicchess-api`. `python3 -m classicchess_api` runs the same command-line tool.
|
|
56
|
+
|
|
57
|
+
If you installed the previous `classicchess-api` distribution, run
|
|
58
|
+
`python3 -m pip uninstall classicchess-api` before installing `classicchess-sdk`
|
|
59
|
+
in that environment. Both distributions provide the same import and command,
|
|
60
|
+
so install only one. Existing Python programs and CLI commands still work.
|
|
61
|
+
|
|
62
|
+
## Read a player archive
|
|
63
|
+
|
|
64
|
+
Player slugs are identifiers returned by the API. Use them as returned rather
|
|
65
|
+
than guessing a slug from a display name. This complete example discovers a
|
|
66
|
+
player and prints the first ten games in their archive:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from classicchess_api import ClassicChessClient
|
|
70
|
+
|
|
71
|
+
api = ClassicChessClient()
|
|
72
|
+
players = api.public_players()["results"]
|
|
73
|
+
if players:
|
|
74
|
+
player = players[0]
|
|
75
|
+
print(player["name"])
|
|
76
|
+
biography = api.public_player_bio(player["slug"])["bio"]
|
|
77
|
+
print(biography["lede"] if biography else "Biography unavailable")
|
|
78
|
+
for game in api.iterate_public_games(archive_player=player["slug"], limit=10):
|
|
79
|
+
print(game["token"], game["white"], game["black"])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Catalogs return complete lists. Game searches return one page by default.
|
|
83
|
+
The `iterate_*` methods fetch pages as you consume them; breaking the loop or
|
|
84
|
+
setting `limit` stops without fetching an extra page. The `all_pages=True`
|
|
85
|
+
option on game-list methods collects pages into one dictionary instead.
|
|
86
|
+
Missing biographies are `None`, and notable lists can be empty.
|
|
87
|
+
|
|
88
|
+
## Available operations
|
|
89
|
+
|
|
90
|
+
| Capability | Python methods |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| API discovery | `discovery()` |
|
|
93
|
+
| Player, event and book catalogs | `public_players()`, `public_events()`, `annotated_books()` |
|
|
94
|
+
| Names and titles only | `player_names()`, `event_names()`, `book_titles()` |
|
|
95
|
+
| Player profiles, biographies and notable games | `public_player(slug)`, `public_player_bio(slug)`, `public_notable_games(slug)` |
|
|
96
|
+
| Event series | `event_series(query=...)` or `event_series(series=...)` |
|
|
97
|
+
| Public game lists, detail and PGN | `public_games()`, `public_game(token)`, `public_pgn(token)` |
|
|
98
|
+
| MasterDB game lists, detail and PGN | `master_games(query)`, `master_game(token)`, `master_pgn(token)` |
|
|
99
|
+
| Annotated game lists, detail and PGN | `annotated_games(book_slug)`, `annotated_game(book_slug, game_slug)`, `annotated_pgn(book_slug, game_slug=None)` |
|
|
100
|
+
| Streaming games | `iterate_public_games()`, `iterate_master_games(query)`, `iterate_annotated_games(book_slug)` |
|
|
101
|
+
| Combine PGN text for returned games | `pgn_text_for_games(games)` |
|
|
102
|
+
| PGN or NDJSON export | `export_public_games()`, `export_master_games()`; select `format="pgn"` or `format="ndjson"` |
|
|
103
|
+
| Player search and statistics | `players(query)`, `master_stats(query=...)` or `master_stats(player=..., opponent=...)` |
|
|
104
|
+
| Opening explorer | `explorer(play=...)` or `explorer(fen=...)`, `explorer_sources()` |
|
|
105
|
+
| Lichess opening explorer | `lichess_explorer(fen=..., player=..., color=..., result=...)` relays through Classic Chess, so no Lichess token is needed; no player covers the whole Lichess database, a Lichess username covers that player's games |
|
|
106
|
+
| Event detail and About page | `public_event(event_slug)`, `public_event_about(event_slug)` |
|
|
107
|
+
| Photo gallery | `gallery(query=None, page=1, page_size=48)`, `gallery_photo(photo_id)` |
|
|
108
|
+
| Beginner games and Game of the Day | `beginner_games()`, `daily_game()` |
|
|
109
|
+
| Event of the day | `event_of_the_day()` returns the landmark event on today's homepage, its tagline and pictured winner; `event` is `None` when none can be published |
|
|
110
|
+
| Site search | `site_search(query)` for a preview of players, events and games; `site_search(query, kind="games", page=2)` for one paginated kind |
|
|
111
|
+
| Endgame tablebase | `tablebase(fen)` uses local Syzygy tables and falls back to the Lichess tablebase for larger positions, as the site does |
|
|
112
|
+
| Account collections | `ApplicationClient.account_me(token)`, `account_collections(token)`, `account_create_collection(name, token)`, `account_import_public_player_games(archive_player, token, ...)` |
|
|
113
|
+
| Add single games to collections | `ApplicationClient.account_add_collection_game(collection_id, game_slug, token)` |
|
|
114
|
+
| Starred players and games | `account_starred_players(token, page=1, page_size=50)`, `account_star_player(slug, token)`, `account_unstar_player(slug, token)`, `account_starred_games(token, ...)`, `account_star_game(slug, token)`, `account_unstar_game(slug, token)` |
|
|
115
|
+
| Change or delete imported games | `account_set_imported_game_visibility(slug, "public" or "private", token)`, `account_delete_imported_game(slug, token)` |
|
|
116
|
+
| Public imported games | `public_imported_game(username, game_slug)`, `public_imported_pgn(username, game_slug)` read a game another account imported and made public, by its page address |
|
|
117
|
+
| GIF exports | `ApplicationClient.master_game_gif(game_token, token)`, `public_game_gif(slug, token)`, `annotated_game_gif(book_slug, game_slug, token)`, `public_imported_game_gif(username, slug, token)`, `account_imported_game_gif(slug, token)`; add `orientation="black"` to flip the board |
|
|
118
|
+
| Notifications | `account_notifications(token, page=1, page_size=50)`, `account_mark_notification_read(id, token)`, `account_mark_all_notifications_read(token)`, `account_dismiss_notification(id, token)`, `account_dismiss_all_notifications(token)`, `account_notification_preferences(token)`, `account_update_notification_preferences(token, topics=..., sound_enabled=...)` |
|
|
119
|
+
| Notebook exports | `account_notebooks(token)`, `account_notebook(uuid, token)`, `account_notebook_chapter_pgn(uuid, chapter_id, token)`, `account_notebook_file(uuid, token, password=None)` |
|
|
120
|
+
| Play the Gym's bots | `account_gym(token)`, `account_gym_games(token, page=1, page_size=20)`, `account_gym_new_game(bot_key, token, color="white", time="unlimited")`, `account_gym_game(id, token)`, `account_gym_move(id, uci, ply, token)`, `account_gym_bot_move(id, ply, token)`, `account_gym_take_back(id, token)`, `account_gym_resign(id, token)`, `account_gym_clock(id, token)`, `account_gym_abort(id, token)`, `account_gym_delete_game(id, token)`, `account_gym_game_pgn(id, token)` |
|
|
121
|
+
| Notebook, Remote, Cast and other application APIs | `ApplicationClient.request(path, method=..., body=..., token=...)`, `download(path, token=...)` for files |
|
|
122
|
+
| Scanner upload | `ApplicationClient.scan_position(jpeg_bytes, token)` |
|
|
123
|
+
|
|
124
|
+
Public game filters include `query`, `archive_player`, `archive_event`,
|
|
125
|
+
`search_mode`, `since`, `until`, `sort`, `page` and `page_size`. `search_mode`
|
|
126
|
+
chooses which fields `query` searches: `players` (players, event, site and
|
|
127
|
+
date), `openings` (opening name and ECO) or `all` (the default). Each export is capped at 300 games.
|
|
128
|
+
For larger archives, iterate games and export batches of up to 300 returned
|
|
129
|
+
tokens. NDJSON means one JSON object per line; `pgn_in_json=False` omits the
|
|
130
|
+
PGN field from that format. PGN is the standard text format for chess games.
|
|
131
|
+
|
|
132
|
+
## Account and application requests
|
|
133
|
+
|
|
134
|
+
Public archive reads use `ClassicChessClient`. For private account or device
|
|
135
|
+
operations use `ApplicationClient` and pass a token explicitly for each request.
|
|
136
|
+
Create a personal API token in your signed-in Classic Chess profile settings
|
|
137
|
+
with the scopes required by the [account API reference](https://classicchess.com/api/#account-api).
|
|
138
|
+
Set the `CLASSICCHESS_API_TOKEN` environment variable to that token before
|
|
139
|
+
running this read-only account example:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import os
|
|
143
|
+
from classicchess_api import ApplicationClient
|
|
144
|
+
|
|
145
|
+
api = ApplicationClient()
|
|
146
|
+
response = api.account_me(os.environ["CLASSICCHESS_API_TOKEN"])
|
|
147
|
+
print(response.status, response.data)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`response.ok` indicates HTTP success. HTTP JSON errors preserve their status,
|
|
151
|
+
data and `retry_after`; network failures and invalid responses raise `ApiError`.
|
|
152
|
+
Application requests accept JSON text or bytes, including binary notebook uploads.
|
|
153
|
+
Scanner uploads accept JPEG bytes up to 850,000 bytes. Account tokens and mobile
|
|
154
|
+
or Desktop device sessions have different scopes; use the credential required
|
|
155
|
+
by the endpoint. Do not embed tokens in source files.
|
|
156
|
+
|
|
157
|
+
GIF, chapter PGN and Notebook file methods return an `ApplicationDownload` with
|
|
158
|
+
`ok`, `status`, `content` (the file bytes when `ok`), `content_type`, `filename`,
|
|
159
|
+
`retry_after` and `error` (the JSON error reply otherwise). GIF exports need a
|
|
160
|
+
registered account, so any personal token or device session works, and they
|
|
161
|
+
share the site limit on GIF exports: honor `retry_after` after a 429. This saves
|
|
162
|
+
a GIF of a public archive game:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
import os
|
|
166
|
+
from classicchess_api import ApplicationClient
|
|
167
|
+
|
|
168
|
+
gif = ApplicationClient().public_game_gif("tal-vs-larsen-1965", os.environ["CLASSICCHESS_API_TOKEN"])
|
|
169
|
+
if gif.ok:
|
|
170
|
+
with open(gif.filename or "game.gif", "wb") as handle:
|
|
171
|
+
handle.write(gif.content)
|
|
172
|
+
else:
|
|
173
|
+
print(gif.status, gif.error)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Play the Gym's bots
|
|
177
|
+
|
|
178
|
+
Any account can play the Gym's bots; the moves are computed on the server.
|
|
179
|
+
Personal tokens need `gym:read` to read games and `gym:play` to start and play
|
|
180
|
+
them. Send the `ply` from the latest game state with each move: a stale ply
|
|
181
|
+
returns 409 and plays nothing, so a repeated request is safe. The bot engine
|
|
182
|
+
plays one move at a time. While it is busy, `account_gym_bot_move` returns 429
|
|
183
|
+
with `capacity_exhausted`: wait `retry_after` seconds and ask again with the
|
|
184
|
+
same ply. Each account may use 30 minutes of engine time in each hour; once it
|
|
185
|
+
is spent the answer is 429 `engine_budget` with `retry_after` set to the seconds
|
|
186
|
+
until that hour ends. An account starts at most 30 games an hour.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
import os
|
|
190
|
+
import time
|
|
191
|
+
from classicchess_api import ApplicationClient
|
|
192
|
+
|
|
193
|
+
token = os.environ["CLASSICCHESS_API_TOKEN"] # a token with gym:read and gym:play
|
|
194
|
+
api = ApplicationClient()
|
|
195
|
+
created = api.account_gym_new_game("morphy", token, color="white", time="5+0")
|
|
196
|
+
if not created.ok:
|
|
197
|
+
raise SystemExit(f"{created.status} {created.data}")
|
|
198
|
+
game = api.account_gym_move(created.data["id"], "e2e4", created.data["ply"], token).data
|
|
199
|
+
reply = api.account_gym_bot_move(game["id"], game["ply"], token)
|
|
200
|
+
while reply.status == 429 and reply.data["error"]["code"] == "capacity_exhausted":
|
|
201
|
+
time.sleep(int(reply.retry_after or 1))
|
|
202
|
+
reply = api.account_gym_bot_move(game["id"], game["ply"], token)
|
|
203
|
+
print(reply.status, reply.data.get("sans"), reply.data.get("clock"))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The CLI reads `CLASSICCHESS_API_TOKEN` for account commands. Existing installations
|
|
207
|
+
can continue using their previous token environment variable as a fallback.
|
|
208
|
+
The original account helpers on `ClassicChessClient` remain available and raise
|
|
209
|
+
`ApiError` for unsuccessful HTTP responses.
|
|
210
|
+
|
|
211
|
+
## Timeouts, pagination and failures
|
|
212
|
+
|
|
213
|
+
Configure `ClassicChessClient(timeout=30, max_response_bytes=32 * 1024 * 1024)`
|
|
214
|
+
or `ApplicationClient` with the same options. Public reads return dictionaries
|
|
215
|
+
or text; `ApiError` exposes `status_code`, `code` and `retry_after` when available.
|
|
216
|
+
Pagination rejects repeated pages and links outside the original API collection.
|
|
217
|
+
The application transport sends no cookies, follows no redirects, and accepts
|
|
218
|
+
bearers only on private API paths over HTTPS or loopback development.
|
|
219
|
+
There are no automatic retries. Respect `Retry-After` and avoid blindly retrying writes.
|
|
220
|
+
|
|
221
|
+
## Optional standalone command-line script
|
|
222
|
+
|
|
223
|
+
If you only want command-line commands without installing the Python package,
|
|
224
|
+
GitHub also provides [pull_api.py](https://github.com/Andromeda1957/classicchess-api-python/blob/main/pull_api.py). Save that file and run
|
|
225
|
+
`python3 pull_api.py public players --names`. It is the dependency-free CLI
|
|
226
|
+
variant; the importable SDK and application transport are installed by the
|
|
227
|
+
steps above. The Classic Chess website no longer serves a script download URI.
|
|
228
|
+
|
|
229
|
+
## Build and test this repository
|
|
230
|
+
|
|
231
|
+
These contributor commands start from a fresh clone:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
git clone https://github.com/Andromeda1957/classicchess-api-python.git
|
|
235
|
+
cd classicchess-api-python
|
|
236
|
+
python3 -m venv .venv
|
|
237
|
+
source .venv/bin/activate
|
|
238
|
+
python3 -m unittest discover -s classicchess_api/tests -t .
|
|
239
|
+
python3 -m pip install .
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The same API capabilities are available in the
|
|
243
|
+
[TypeScript SDK](https://github.com/Andromeda1957/classicchess-api-typescript) and
|
|
244
|
+
[Kotlin SDK](https://github.com/Andromeda1957/classicchess-api-kotlin).
|
|
245
|
+
See the [API reference](https://classicchess.com/api/) for response fields,
|
|
246
|
+
endpoint permissions, rate limits and data attribution. The MIT license covers
|
|
247
|
+
the SDK code; linked chess resources retain their own stated reuse terms.
|
|
248
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/Andromeda1957/classicchess-api-python/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Classic Chess Python SDK
|
|
2
|
+
|
|
3
|
+
Use Python to read chess games, player biographies, notable games, tournament
|
|
4
|
+
archives and annotated books from [Classic Chess](https://classicchess.com/).
|
|
5
|
+
The SDK also searches the larger MasterDB game database, retrieves opening
|
|
6
|
+
statistics, exports games, and calls account and application APIs.
|
|
7
|
+
|
|
8
|
+
Public reads need no account or API key. The SDK uses the Python standard
|
|
9
|
+
library and is MIT licensed; see
|
|
10
|
+
[LICENSE](https://github.com/Andromeda1957/classicchess-api-python/blob/main/LICENSE).
|
|
11
|
+
|
|
12
|
+
## Install and run your first example
|
|
13
|
+
|
|
14
|
+
Install [Python 3.10 or newer](https://www.python.org/downloads/). In a terminal,
|
|
15
|
+
create an isolated Python environment and install from
|
|
16
|
+
[PyPI](https://pypi.org/project/classicchess-sdk/):
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
python3 -m venv classicchess-env
|
|
20
|
+
source classicchess-env/bin/activate
|
|
21
|
+
python3 -m pip install classicchess-sdk
|
|
22
|
+
classicchess-api public players --names
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On Windows PowerShell, use `py` instead of `python3` and activate with
|
|
26
|
+
`classicchess-env\Scripts\Activate.ps1` instead of the `source` command.
|
|
27
|
+
The last command prints the names of all curated players. To install from the
|
|
28
|
+
public source repository instead, install [Git](https://git-scm.com/) and use
|
|
29
|
+
`python3 -m pip install git+https://github.com/Andromeda1957/classicchess-api-python.git`.
|
|
30
|
+
|
|
31
|
+
To use it in a Python program, create `example.py` with this complete example:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from classicchess_api import ClassicChessClient
|
|
35
|
+
|
|
36
|
+
api = ClassicChessClient()
|
|
37
|
+
print(api.player_names()[:10])
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Run `python3 example.py` in the same activated environment. You should see up to
|
|
41
|
+
ten player names. The PyPI distribution is `classicchess-sdk`; the import is
|
|
42
|
+
`classicchess_api` and the command-line executable is `classicchess-api`. `python3 -m classicchess_api` runs the same command-line tool.
|
|
43
|
+
|
|
44
|
+
If you installed the previous `classicchess-api` distribution, run
|
|
45
|
+
`python3 -m pip uninstall classicchess-api` before installing `classicchess-sdk`
|
|
46
|
+
in that environment. Both distributions provide the same import and command,
|
|
47
|
+
so install only one. Existing Python programs and CLI commands still work.
|
|
48
|
+
|
|
49
|
+
## Read a player archive
|
|
50
|
+
|
|
51
|
+
Player slugs are identifiers returned by the API. Use them as returned rather
|
|
52
|
+
than guessing a slug from a display name. This complete example discovers a
|
|
53
|
+
player and prints the first ten games in their archive:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from classicchess_api import ClassicChessClient
|
|
57
|
+
|
|
58
|
+
api = ClassicChessClient()
|
|
59
|
+
players = api.public_players()["results"]
|
|
60
|
+
if players:
|
|
61
|
+
player = players[0]
|
|
62
|
+
print(player["name"])
|
|
63
|
+
biography = api.public_player_bio(player["slug"])["bio"]
|
|
64
|
+
print(biography["lede"] if biography else "Biography unavailable")
|
|
65
|
+
for game in api.iterate_public_games(archive_player=player["slug"], limit=10):
|
|
66
|
+
print(game["token"], game["white"], game["black"])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Catalogs return complete lists. Game searches return one page by default.
|
|
70
|
+
The `iterate_*` methods fetch pages as you consume them; breaking the loop or
|
|
71
|
+
setting `limit` stops without fetching an extra page. The `all_pages=True`
|
|
72
|
+
option on game-list methods collects pages into one dictionary instead.
|
|
73
|
+
Missing biographies are `None`, and notable lists can be empty.
|
|
74
|
+
|
|
75
|
+
## Available operations
|
|
76
|
+
|
|
77
|
+
| Capability | Python methods |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| API discovery | `discovery()` |
|
|
80
|
+
| Player, event and book catalogs | `public_players()`, `public_events()`, `annotated_books()` |
|
|
81
|
+
| Names and titles only | `player_names()`, `event_names()`, `book_titles()` |
|
|
82
|
+
| Player profiles, biographies and notable games | `public_player(slug)`, `public_player_bio(slug)`, `public_notable_games(slug)` |
|
|
83
|
+
| Event series | `event_series(query=...)` or `event_series(series=...)` |
|
|
84
|
+
| Public game lists, detail and PGN | `public_games()`, `public_game(token)`, `public_pgn(token)` |
|
|
85
|
+
| MasterDB game lists, detail and PGN | `master_games(query)`, `master_game(token)`, `master_pgn(token)` |
|
|
86
|
+
| Annotated game lists, detail and PGN | `annotated_games(book_slug)`, `annotated_game(book_slug, game_slug)`, `annotated_pgn(book_slug, game_slug=None)` |
|
|
87
|
+
| Streaming games | `iterate_public_games()`, `iterate_master_games(query)`, `iterate_annotated_games(book_slug)` |
|
|
88
|
+
| Combine PGN text for returned games | `pgn_text_for_games(games)` |
|
|
89
|
+
| PGN or NDJSON export | `export_public_games()`, `export_master_games()`; select `format="pgn"` or `format="ndjson"` |
|
|
90
|
+
| Player search and statistics | `players(query)`, `master_stats(query=...)` or `master_stats(player=..., opponent=...)` |
|
|
91
|
+
| Opening explorer | `explorer(play=...)` or `explorer(fen=...)`, `explorer_sources()` |
|
|
92
|
+
| Lichess opening explorer | `lichess_explorer(fen=..., player=..., color=..., result=...)` relays through Classic Chess, so no Lichess token is needed; no player covers the whole Lichess database, a Lichess username covers that player's games |
|
|
93
|
+
| Event detail and About page | `public_event(event_slug)`, `public_event_about(event_slug)` |
|
|
94
|
+
| Photo gallery | `gallery(query=None, page=1, page_size=48)`, `gallery_photo(photo_id)` |
|
|
95
|
+
| Beginner games and Game of the Day | `beginner_games()`, `daily_game()` |
|
|
96
|
+
| Event of the day | `event_of_the_day()` returns the landmark event on today's homepage, its tagline and pictured winner; `event` is `None` when none can be published |
|
|
97
|
+
| Site search | `site_search(query)` for a preview of players, events and games; `site_search(query, kind="games", page=2)` for one paginated kind |
|
|
98
|
+
| Endgame tablebase | `tablebase(fen)` uses local Syzygy tables and falls back to the Lichess tablebase for larger positions, as the site does |
|
|
99
|
+
| Account collections | `ApplicationClient.account_me(token)`, `account_collections(token)`, `account_create_collection(name, token)`, `account_import_public_player_games(archive_player, token, ...)` |
|
|
100
|
+
| Add single games to collections | `ApplicationClient.account_add_collection_game(collection_id, game_slug, token)` |
|
|
101
|
+
| Starred players and games | `account_starred_players(token, page=1, page_size=50)`, `account_star_player(slug, token)`, `account_unstar_player(slug, token)`, `account_starred_games(token, ...)`, `account_star_game(slug, token)`, `account_unstar_game(slug, token)` |
|
|
102
|
+
| Change or delete imported games | `account_set_imported_game_visibility(slug, "public" or "private", token)`, `account_delete_imported_game(slug, token)` |
|
|
103
|
+
| Public imported games | `public_imported_game(username, game_slug)`, `public_imported_pgn(username, game_slug)` read a game another account imported and made public, by its page address |
|
|
104
|
+
| GIF exports | `ApplicationClient.master_game_gif(game_token, token)`, `public_game_gif(slug, token)`, `annotated_game_gif(book_slug, game_slug, token)`, `public_imported_game_gif(username, slug, token)`, `account_imported_game_gif(slug, token)`; add `orientation="black"` to flip the board |
|
|
105
|
+
| Notifications | `account_notifications(token, page=1, page_size=50)`, `account_mark_notification_read(id, token)`, `account_mark_all_notifications_read(token)`, `account_dismiss_notification(id, token)`, `account_dismiss_all_notifications(token)`, `account_notification_preferences(token)`, `account_update_notification_preferences(token, topics=..., sound_enabled=...)` |
|
|
106
|
+
| Notebook exports | `account_notebooks(token)`, `account_notebook(uuid, token)`, `account_notebook_chapter_pgn(uuid, chapter_id, token)`, `account_notebook_file(uuid, token, password=None)` |
|
|
107
|
+
| Play the Gym's bots | `account_gym(token)`, `account_gym_games(token, page=1, page_size=20)`, `account_gym_new_game(bot_key, token, color="white", time="unlimited")`, `account_gym_game(id, token)`, `account_gym_move(id, uci, ply, token)`, `account_gym_bot_move(id, ply, token)`, `account_gym_take_back(id, token)`, `account_gym_resign(id, token)`, `account_gym_clock(id, token)`, `account_gym_abort(id, token)`, `account_gym_delete_game(id, token)`, `account_gym_game_pgn(id, token)` |
|
|
108
|
+
| Notebook, Remote, Cast and other application APIs | `ApplicationClient.request(path, method=..., body=..., token=...)`, `download(path, token=...)` for files |
|
|
109
|
+
| Scanner upload | `ApplicationClient.scan_position(jpeg_bytes, token)` |
|
|
110
|
+
|
|
111
|
+
Public game filters include `query`, `archive_player`, `archive_event`,
|
|
112
|
+
`search_mode`, `since`, `until`, `sort`, `page` and `page_size`. `search_mode`
|
|
113
|
+
chooses which fields `query` searches: `players` (players, event, site and
|
|
114
|
+
date), `openings` (opening name and ECO) or `all` (the default). Each export is capped at 300 games.
|
|
115
|
+
For larger archives, iterate games and export batches of up to 300 returned
|
|
116
|
+
tokens. NDJSON means one JSON object per line; `pgn_in_json=False` omits the
|
|
117
|
+
PGN field from that format. PGN is the standard text format for chess games.
|
|
118
|
+
|
|
119
|
+
## Account and application requests
|
|
120
|
+
|
|
121
|
+
Public archive reads use `ClassicChessClient`. For private account or device
|
|
122
|
+
operations use `ApplicationClient` and pass a token explicitly for each request.
|
|
123
|
+
Create a personal API token in your signed-in Classic Chess profile settings
|
|
124
|
+
with the scopes required by the [account API reference](https://classicchess.com/api/#account-api).
|
|
125
|
+
Set the `CLASSICCHESS_API_TOKEN` environment variable to that token before
|
|
126
|
+
running this read-only account example:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import os
|
|
130
|
+
from classicchess_api import ApplicationClient
|
|
131
|
+
|
|
132
|
+
api = ApplicationClient()
|
|
133
|
+
response = api.account_me(os.environ["CLASSICCHESS_API_TOKEN"])
|
|
134
|
+
print(response.status, response.data)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`response.ok` indicates HTTP success. HTTP JSON errors preserve their status,
|
|
138
|
+
data and `retry_after`; network failures and invalid responses raise `ApiError`.
|
|
139
|
+
Application requests accept JSON text or bytes, including binary notebook uploads.
|
|
140
|
+
Scanner uploads accept JPEG bytes up to 850,000 bytes. Account tokens and mobile
|
|
141
|
+
or Desktop device sessions have different scopes; use the credential required
|
|
142
|
+
by the endpoint. Do not embed tokens in source files.
|
|
143
|
+
|
|
144
|
+
GIF, chapter PGN and Notebook file methods return an `ApplicationDownload` with
|
|
145
|
+
`ok`, `status`, `content` (the file bytes when `ok`), `content_type`, `filename`,
|
|
146
|
+
`retry_after` and `error` (the JSON error reply otherwise). GIF exports need a
|
|
147
|
+
registered account, so any personal token or device session works, and they
|
|
148
|
+
share the site limit on GIF exports: honor `retry_after` after a 429. This saves
|
|
149
|
+
a GIF of a public archive game:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import os
|
|
153
|
+
from classicchess_api import ApplicationClient
|
|
154
|
+
|
|
155
|
+
gif = ApplicationClient().public_game_gif("tal-vs-larsen-1965", os.environ["CLASSICCHESS_API_TOKEN"])
|
|
156
|
+
if gif.ok:
|
|
157
|
+
with open(gif.filename or "game.gif", "wb") as handle:
|
|
158
|
+
handle.write(gif.content)
|
|
159
|
+
else:
|
|
160
|
+
print(gif.status, gif.error)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Play the Gym's bots
|
|
164
|
+
|
|
165
|
+
Any account can play the Gym's bots; the moves are computed on the server.
|
|
166
|
+
Personal tokens need `gym:read` to read games and `gym:play` to start and play
|
|
167
|
+
them. Send the `ply` from the latest game state with each move: a stale ply
|
|
168
|
+
returns 409 and plays nothing, so a repeated request is safe. The bot engine
|
|
169
|
+
plays one move at a time. While it is busy, `account_gym_bot_move` returns 429
|
|
170
|
+
with `capacity_exhausted`: wait `retry_after` seconds and ask again with the
|
|
171
|
+
same ply. Each account may use 30 minutes of engine time in each hour; once it
|
|
172
|
+
is spent the answer is 429 `engine_budget` with `retry_after` set to the seconds
|
|
173
|
+
until that hour ends. An account starts at most 30 games an hour.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
import os
|
|
177
|
+
import time
|
|
178
|
+
from classicchess_api import ApplicationClient
|
|
179
|
+
|
|
180
|
+
token = os.environ["CLASSICCHESS_API_TOKEN"] # a token with gym:read and gym:play
|
|
181
|
+
api = ApplicationClient()
|
|
182
|
+
created = api.account_gym_new_game("morphy", token, color="white", time="5+0")
|
|
183
|
+
if not created.ok:
|
|
184
|
+
raise SystemExit(f"{created.status} {created.data}")
|
|
185
|
+
game = api.account_gym_move(created.data["id"], "e2e4", created.data["ply"], token).data
|
|
186
|
+
reply = api.account_gym_bot_move(game["id"], game["ply"], token)
|
|
187
|
+
while reply.status == 429 and reply.data["error"]["code"] == "capacity_exhausted":
|
|
188
|
+
time.sleep(int(reply.retry_after or 1))
|
|
189
|
+
reply = api.account_gym_bot_move(game["id"], game["ply"], token)
|
|
190
|
+
print(reply.status, reply.data.get("sans"), reply.data.get("clock"))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The CLI reads `CLASSICCHESS_API_TOKEN` for account commands. Existing installations
|
|
194
|
+
can continue using their previous token environment variable as a fallback.
|
|
195
|
+
The original account helpers on `ClassicChessClient` remain available and raise
|
|
196
|
+
`ApiError` for unsuccessful HTTP responses.
|
|
197
|
+
|
|
198
|
+
## Timeouts, pagination and failures
|
|
199
|
+
|
|
200
|
+
Configure `ClassicChessClient(timeout=30, max_response_bytes=32 * 1024 * 1024)`
|
|
201
|
+
or `ApplicationClient` with the same options. Public reads return dictionaries
|
|
202
|
+
or text; `ApiError` exposes `status_code`, `code` and `retry_after` when available.
|
|
203
|
+
Pagination rejects repeated pages and links outside the original API collection.
|
|
204
|
+
The application transport sends no cookies, follows no redirects, and accepts
|
|
205
|
+
bearers only on private API paths over HTTPS or loopback development.
|
|
206
|
+
There are no automatic retries. Respect `Retry-After` and avoid blindly retrying writes.
|
|
207
|
+
|
|
208
|
+
## Optional standalone command-line script
|
|
209
|
+
|
|
210
|
+
If you only want command-line commands without installing the Python package,
|
|
211
|
+
GitHub also provides [pull_api.py](https://github.com/Andromeda1957/classicchess-api-python/blob/main/pull_api.py). Save that file and run
|
|
212
|
+
`python3 pull_api.py public players --names`. It is the dependency-free CLI
|
|
213
|
+
variant; the importable SDK and application transport are installed by the
|
|
214
|
+
steps above. The Classic Chess website no longer serves a script download URI.
|
|
215
|
+
|
|
216
|
+
## Build and test this repository
|
|
217
|
+
|
|
218
|
+
These contributor commands start from a fresh clone:
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
git clone https://github.com/Andromeda1957/classicchess-api-python.git
|
|
222
|
+
cd classicchess-api-python
|
|
223
|
+
python3 -m venv .venv
|
|
224
|
+
source .venv/bin/activate
|
|
225
|
+
python3 -m unittest discover -s classicchess_api/tests -t .
|
|
226
|
+
python3 -m pip install .
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The same API capabilities are available in the
|
|
230
|
+
[TypeScript SDK](https://github.com/Andromeda1957/classicchess-api-typescript) and
|
|
231
|
+
[Kotlin SDK](https://github.com/Andromeda1957/classicchess-api-kotlin).
|
|
232
|
+
See the [API reference](https://classicchess.com/api/) for response fields,
|
|
233
|
+
endpoint permissions, rate limits and data attribution. The MIT license covers
|
|
234
|
+
the SDK code; linked chess resources retain their own stated reuse terms.
|
|
235
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/Andromeda1957/classicchess-api-python/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Import surface for the Classic Chess API client package."""
|
|
2
|
+
|
|
3
|
+
from .application import ApplicationClient
|
|
4
|
+
from .application import ApplicationDownload
|
|
5
|
+
from .application import ApplicationResponse
|
|
6
|
+
from .client import DEFAULT_BASE_URL
|
|
7
|
+
from .client import USER_AGENT
|
|
8
|
+
from .client import ApiError
|
|
9
|
+
from .client import ClassicChessClient
|
|
10
|
+
from .client import api_url
|
|
11
|
+
from .client import extract_master_game_token
|
|
12
|
+
from .client import extract_public_game_token
|
|
13
|
+
from .client import fetch_json
|
|
14
|
+
from .client import fetch_url
|
|
15
|
+
from .client import normalized_base_url
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DEFAULT_BASE_URL",
|
|
19
|
+
"USER_AGENT",
|
|
20
|
+
"ClassicChessClient",
|
|
21
|
+
"ApiError",
|
|
22
|
+
"ApplicationClient",
|
|
23
|
+
"ApplicationDownload",
|
|
24
|
+
"ApplicationResponse",
|
|
25
|
+
"api_url",
|
|
26
|
+
"extract_master_game_token",
|
|
27
|
+
"extract_public_game_token",
|
|
28
|
+
"fetch_json",
|
|
29
|
+
"fetch_url",
|
|
30
|
+
"normalized_base_url",
|
|
31
|
+
]
|