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.
@@ -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
+ ]
@@ -0,0 +1,6 @@
1
+ """Run the Classic Chess API CLI with `python -m classicchess_api`."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())