rust-pgn-reader-python-binding 4.2.0__cp315-cp315-musllinux_1_2_i686.whl

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,5 @@
1
+ from .rust_pgn_reader_python_binding import *
2
+
3
+ __doc__ = rust_pgn_reader_python_binding.__doc__
4
+ if hasattr(rust_pgn_reader_python_binding, "__all__"):
5
+ __all__ = rust_pgn_reader_python_binding.__all__
@@ -0,0 +1,393 @@
1
+ from typing import List, Optional, Tuple, Dict, Iterator, Union, overload
2
+ import pyarrow
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+ from numpy.typing import NDArray
6
+
7
+ class PyGameView:
8
+ """Zero-copy view into a single game's data within a ParsedGames result.
9
+
10
+ Board indexing note: Boards use square indexing (a1=0, h8=63).
11
+ To convert to rank/file:
12
+ rank = square // 8
13
+ file = square % 8
14
+ """
15
+
16
+ def __len__(self) -> int:
17
+ """Number of moves in this game."""
18
+ ...
19
+
20
+ @property
21
+ def num_positions(self) -> int:
22
+ """Number of positions recorded for this game."""
23
+ ...
24
+
25
+ # === Board state views ===
26
+
27
+ @property
28
+ def boards(self) -> NDArray[np.uint8]:
29
+ """Board positions, shape (num_positions, 8, 8)."""
30
+ ...
31
+
32
+ @property
33
+ def initial_board(self) -> NDArray[np.uint8]:
34
+ """Initial board position, shape (8, 8)."""
35
+ ...
36
+
37
+ @property
38
+ def final_board(self) -> NDArray[np.uint8]:
39
+ """Final board position, shape (8, 8)."""
40
+ ...
41
+
42
+ @property
43
+ def castling(self) -> NDArray[np.bool_]:
44
+ """Castling rights [K,Q,k,q], shape (num_positions, 4)."""
45
+ ...
46
+
47
+ @property
48
+ def en_passant(self) -> NDArray[np.int8]:
49
+ """En passant file (-1 if none), shape (num_positions,)."""
50
+ ...
51
+
52
+ @property
53
+ def halfmove_clock(self) -> NDArray[np.uint8]:
54
+ """Halfmove clock, shape (num_positions,)."""
55
+ ...
56
+
57
+ @property
58
+ def turn(self) -> NDArray[np.bool_]:
59
+ """Side to move (True=white), shape (num_positions,)."""
60
+ ...
61
+
62
+ # === Move views ===
63
+
64
+ @property
65
+ def from_squares(self) -> NDArray[np.uint8]:
66
+ """From squares, shape (num_moves,)."""
67
+ ...
68
+
69
+ @property
70
+ def to_squares(self) -> NDArray[np.uint8]:
71
+ """To squares, shape (num_moves,)."""
72
+ ...
73
+
74
+ @property
75
+ def promotions(self) -> NDArray[np.int8]:
76
+ """Promotions (-1=none, 2=N, 3=B, 4=R, 5=Q), shape (num_moves,)."""
77
+ ...
78
+
79
+ @property
80
+ def clocks(self) -> NDArray[np.float32]:
81
+ """Clock times in seconds (NaN if missing), shape (num_moves,)."""
82
+ ...
83
+
84
+ @property
85
+ def evals(self) -> NDArray[np.float32]:
86
+ """Engine evals (NaN if missing), shape (num_moves,)."""
87
+ ...
88
+
89
+ # === Per-game metadata ===
90
+
91
+ @property
92
+ def headers(self) -> Dict[str, str]:
93
+ """Raw PGN headers as dict."""
94
+ ...
95
+
96
+ @property
97
+ def outcome(self) -> Optional[str]:
98
+ """Game outcome from movetext: 'White', 'Black', 'Draw', 'Unknown', or None."""
99
+ ...
100
+
101
+ @property
102
+ def parse_error(self) -> Optional[str]:
103
+ """Parse error message if the game failed to parse, or None if valid."""
104
+ ...
105
+
106
+ @property
107
+ def is_checkmate(self) -> bool:
108
+ """Final position is checkmate."""
109
+ ...
110
+
111
+ @property
112
+ def is_stalemate(self) -> bool:
113
+ """Final position is stalemate."""
114
+ ...
115
+
116
+ @property
117
+ def is_insufficient(self) -> Tuple[bool, bool]:
118
+ """Insufficient material (white, black)."""
119
+ ...
120
+
121
+ @property
122
+ def legal_move_count(self) -> int:
123
+ """Legal move count in final position."""
124
+ ...
125
+
126
+ @property
127
+ def is_valid(self) -> bool:
128
+ """Whether game parsed successfully."""
129
+ ...
130
+
131
+ @property
132
+ def is_game_over(self) -> bool:
133
+ """Whether the game is over (checkmate, stalemate, or both sides insufficient)."""
134
+ ...
135
+
136
+ @property
137
+ def comments(self) -> List[Optional[str]]:
138
+ """Raw text comments per move (only populated when store_comments=True)."""
139
+ ...
140
+
141
+ @property
142
+ def legal_moves(self) -> List[List[Tuple[int, int, int]]]:
143
+ """Legal moves at each position (only populated when store_legal_moves=True).
144
+ Each entry is a list of (from_square, to_square, promotion) tuples."""
145
+ ...
146
+
147
+ # === Convenience methods ===
148
+
149
+ def move_uci(self, move_idx: int) -> str:
150
+ """Get UCI string for move at index."""
151
+ ...
152
+
153
+ def moves_uci(self) -> List[str]:
154
+ """Get all moves as UCI strings."""
155
+ ...
156
+
157
+ def __repr__(self) -> str: ...
158
+
159
+ class ParsedGamesIter:
160
+ """Iterator over games in a ParsedGames result."""
161
+
162
+ def __iter__(self) -> "ParsedGamesIter": ...
163
+ def __next__(self) -> PyGameView: ...
164
+
165
+ class PyChunkView:
166
+ """View into a single chunk's raw numpy arrays.
167
+
168
+ Access via ``parsed_games.chunks[i]``. Each chunk corresponds to one
169
+ parsing thread's output. Use this for advanced access patterns like
170
+ manual concatenation or custom batching.
171
+ """
172
+
173
+ @property
174
+ def num_games(self) -> int: ...
175
+ @property
176
+ def num_moves(self) -> int: ...
177
+ @property
178
+ def num_positions(self) -> int: ...
179
+ @property
180
+ def boards(self) -> NDArray[np.uint8]:
181
+ """Board positions, shape (N_positions, 8, 8), dtype uint8."""
182
+ ...
183
+ @property
184
+ def castling(self) -> NDArray[np.bool_]:
185
+ """Castling rights [K,Q,k,q], shape (N_positions, 4), dtype bool."""
186
+ ...
187
+ @property
188
+ def en_passant(self) -> NDArray[np.int8]: ...
189
+ @property
190
+ def halfmove_clock(self) -> NDArray[np.uint8]: ...
191
+ @property
192
+ def turn(self) -> NDArray[np.bool_]: ...
193
+ @property
194
+ def from_squares(self) -> NDArray[np.uint8]: ...
195
+ @property
196
+ def to_squares(self) -> NDArray[np.uint8]: ...
197
+ @property
198
+ def promotions(self) -> NDArray[np.int8]: ...
199
+ @property
200
+ def clocks(self) -> NDArray[np.float32]: ...
201
+ @property
202
+ def evals(self) -> NDArray[np.float32]: ...
203
+ @property
204
+ def move_offsets(self) -> NDArray[np.uint32]: ...
205
+ @property
206
+ def position_offsets(self) -> NDArray[np.uint32]: ...
207
+ @property
208
+ def is_checkmate(self) -> NDArray[np.bool_]: ...
209
+ @property
210
+ def is_stalemate(self) -> NDArray[np.bool_]: ...
211
+ @property
212
+ def is_insufficient(self) -> NDArray[np.bool_]: ...
213
+ @property
214
+ def legal_move_count(self) -> NDArray[np.uint16]: ...
215
+ @property
216
+ def valid(self) -> NDArray[np.bool_]: ...
217
+ @property
218
+ def headers(self) -> List[Dict[str, str]]: ...
219
+ @property
220
+ def outcome(self) -> List[Optional[str]]: ...
221
+ @property
222
+ def parse_errors(self) -> List[Optional[str]]: ...
223
+ @property
224
+ def comments(self) -> List[Optional[str]]: ...
225
+ @property
226
+ def legal_move_from_squares(self) -> NDArray[np.uint8]: ...
227
+ @property
228
+ def legal_move_to_squares(self) -> NDArray[np.uint8]: ...
229
+ @property
230
+ def legal_move_promotions(self) -> NDArray[np.int8]: ...
231
+ @property
232
+ def legal_move_offsets(self) -> NDArray[np.uint32]: ...
233
+ def __repr__(self) -> str: ...
234
+
235
+ class ParsedGames:
236
+ """Chunked container for parsed chess games, optimized for ML training.
237
+
238
+ Internally stores data in multiple chunks (one per parsing thread) to
239
+ avoid the cost of merging. Per-game access is O(log(num_chunks)) via
240
+ binary search on precomputed boundaries.
241
+
242
+ Board layout:
243
+ Boards use square indexing: a1=0, b1=1, ..., h8=63
244
+ Piece encoding: 0=empty, 1-6=white PNBRQK, 7-12=black pnbrqk
245
+ """
246
+
247
+ # === Computed properties ===
248
+
249
+ @property
250
+ def num_games(self) -> int:
251
+ """Number of games in the result."""
252
+ ...
253
+
254
+ @property
255
+ def num_moves(self) -> int:
256
+ """Total number of moves across all games."""
257
+ ...
258
+
259
+ @property
260
+ def num_positions(self) -> int:
261
+ """Total number of board positions recorded."""
262
+ ...
263
+
264
+ @property
265
+ def num_chunks(self) -> int:
266
+ """Number of internal chunks."""
267
+ ...
268
+
269
+ # === Escape hatch: raw chunk access ===
270
+
271
+ @property
272
+ def chunks(self) -> List[PyChunkView]:
273
+ """Access raw per-chunk data.
274
+
275
+ Each chunk corresponds to one parsing thread's output. Use this
276
+ for advanced access patterns like manual concatenation.
277
+ """
278
+ ...
279
+
280
+ # === Sequence protocol ===
281
+
282
+ def __len__(self) -> int:
283
+ """Number of games in the result."""
284
+ ...
285
+
286
+ @overload
287
+ def __getitem__(self, idx: int) -> PyGameView: ...
288
+ @overload
289
+ def __getitem__(self, idx: slice) -> List[PyGameView]: ...
290
+ def __getitem__(
291
+ self, idx: Union[int, slice]
292
+ ) -> Union[PyGameView, List[PyGameView]]:
293
+ """Access game(s) by index or slice."""
294
+ ...
295
+
296
+ def __iter__(self) -> ParsedGamesIter:
297
+ """Iterate over all games."""
298
+ ...
299
+
300
+ def __repr__(self) -> str: ...
301
+
302
+ # === Mapping utilities ===
303
+
304
+ def position_to_game(self, position_indices: npt.ArrayLike) -> NDArray[np.int64]:
305
+ """Map global position indices to game indices.
306
+
307
+ Useful after shuffling/sampling positions to look up game metadata.
308
+
309
+ Args:
310
+ position_indices: Array of indices into the global position space.
311
+ Accepts any integer dtype; int64 is optimal (avoids conversion).
312
+
313
+ Returns:
314
+ Array of game indices (same shape as input)
315
+ """
316
+ ...
317
+
318
+ def move_to_game(self, move_indices: npt.ArrayLike) -> NDArray[np.int64]:
319
+ """Map global move indices to game indices.
320
+
321
+ Args:
322
+ move_indices: Array of indices into the global move space.
323
+ Accepts any integer dtype; int64 is optimal (avoids conversion).
324
+
325
+ Returns:
326
+ Array of game indices (same shape as input)
327
+ """
328
+ ...
329
+
330
+ def parse_game(
331
+ pgn: str,
332
+ store_comments: bool = False,
333
+ store_legal_moves: bool = False,
334
+ ) -> ParsedGames:
335
+ """Parse a single PGN game string.
336
+
337
+ Convenience wrapper for parsing a single game. Returns a ParsedGames
338
+ container with one game.
339
+
340
+ Args:
341
+ pgn: PGN game string
342
+ store_comments: Whether to store raw text comments (default: False)
343
+ store_legal_moves: Whether to store legal moves at each position (default: False)
344
+
345
+ Returns:
346
+ ParsedGames object containing the parsed game
347
+ """
348
+ ...
349
+
350
+ def parse_games(
351
+ pgn_chunked_array: pyarrow.ChunkedArray,
352
+ num_threads: Optional[int] = None,
353
+ chunk_multiplier: Optional[int] = None,
354
+ store_comments: bool = False,
355
+ store_legal_moves: bool = False,
356
+ ) -> ParsedGames:
357
+ """Parse chess games from a PyArrow ChunkedArray into flat NumPy arrays.
358
+
359
+ This API is optimized for ML training pipelines, returning flat NumPy arrays
360
+ that can be efficiently batched and processed.
361
+
362
+ Args:
363
+ pgn_chunked_array: PyArrow ChunkedArray containing PGN strings
364
+ num_threads: Number of threads for parallel parsing (default: all CPUs)
365
+ chunk_multiplier: Multiplier for number of chunks (default: 1)
366
+ store_comments: Whether to store raw text comments (default: False)
367
+ store_legal_moves: Whether to store legal moves at each position (default: False)
368
+
369
+ Returns:
370
+ ParsedGames object containing flat arrays and iteration support
371
+ """
372
+ ...
373
+
374
+ def parse_games_from_strings(
375
+ pgns: List[str],
376
+ num_threads: Optional[int] = None,
377
+ store_comments: bool = False,
378
+ store_legal_moves: bool = False,
379
+ ) -> ParsedGames:
380
+ """Parse multiple PGN game strings in parallel.
381
+
382
+ Convenience wrapper for when you have a list of strings rather than an Arrow array.
383
+
384
+ Args:
385
+ pgns: List of PGN game strings
386
+ num_threads: Number of threads for parallel parsing (default: all CPUs)
387
+ store_comments: Whether to store raw text comments (default: False)
388
+ store_legal_moves: Whether to store legal moves at each position (default: False)
389
+
390
+ Returns:
391
+ ParsedGames object containing flat arrays and iteration support
392
+ """
393
+ ...
File without changes
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: rust_pgn_reader_python_binding
3
+ Version: 4.2.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Dist: numpy
8
+ Requires-Dist: pyarrow
9
+ Summary: A fast chess PGN & parquet parser
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
+ Project-URL: Homepage, https://github.com/vladkvit/rust_pgn_reader_python_binding
13
+ Project-URL: Repository, https://github.com/vladkvit/rust_pgn_reader_python_binding.git
14
+
15
+ # rust_pgn_reader_python_binding
16
+ ## Fast PGN parsing bindings for Python
17
+ This project adds Python bindings to [rust-pgn-reader](https://github.com/niklasf/rust-pgn-reader). In addition, it also parses and extracts [%clk ..] and [%eval ..] tags from comments.
18
+
19
+ ## Installing
20
+ `pip install rust_pgn_reader_python_binding`
21
+
22
+ ## API
23
+
24
+ Three entry points are available:
25
+
26
+ - `parse_game(pgn)` - Parse a single PGN string
27
+ - `parse_games(chunked_array)` - Parse games from a PyArrow ChunkedArray (multithreaded)
28
+ - `parse_games_from_strings(pgns)` - Parse a list of PGN strings (multithreaded)
29
+
30
+ All return a `ParsedGames` container with flat NumPy arrays, supporting:
31
+ - Indexing (`result[i]`), slicing (`result[1:3]`), and iteration (`for game in result`)
32
+ - Per-game views (`PyGameView`) with zero-copy array slices
33
+ - Position-to-game and move-to-game mapping for ML workflows
34
+ - Optional comment storage (`store_comments=True`)
35
+ - Optional legal move storage (`store_legal_moves=True`)
36
+
37
+ ## Benchmarks
38
+ Below are some benchmarks on Lichess's 2013-07 chess games (293,459 games) on a 7800X3D.
39
+
40
+ | Parser | File format | Time |
41
+ |----------------------------------------------------------------------------|-------------|--------|
42
+ | **rust_pgn_reader_python_binding, parse_games (multithreaded)** | parquet | 0.14s |
43
+ | rust_pgn_reader_python_binding, parse_games (singlethreaded) | parquet | 1.3s |
44
+ | rust_pgn_reader_python_binding, parse_games_from_strings (multithreaded) | PGN | 0.41s |
45
+ | [chess-library](https://github.com/Disservin/chess-library) | PGN | 2s |
46
+ | [rust-pgn-reader](https://github.com/niklasf/rust-pgn-reader/tree/master) | PGN | 1s |
47
+ | [python-chess](https://github.com/niklasf/python-chess) | PGN | 3+ min |
48
+
49
+ To replicate, download `2013-07-train-00000-of-00001.parquet` and then run:
50
+
51
+ `python src/bench_parse_games.py` (recommended — multithreaded parse_games via Arrow)
52
+
53
+ `python src/bench_parse_games_singlethreaded.py` (singlethreaded parse_games via Arrow)
54
+
55
+ `python src/bench_parse_pgn.py` (multithreaded .pgn parsing)
56
+
57
+ `python src/bench_data_access.py 2013-07-train-00000-of-00001.parquet` (parsing + data access + memory)
58
+
59
+ ## Building
60
+ `maturin develop`
61
+
62
+ `maturin develop --release`
63
+
64
+ For a more thorough tutorial, follow https://lukesalamone.github.io/posts/how-to-create-rust-python-bindings/
65
+
66
+ ## Profiling
67
+ `samply record --rate 10000 python src\bench_parse_games.py`
68
+ `py-spy record -s -F -f speedscope --output profile.speedscope -- python ./src/bench_parse_games.py`
69
+
70
+ Linux/WSL-only:
71
+ `py-spy record -s -F -n -f speedscope --output profile.speedscope -- python ./src/bench_parse_games.py`
72
+
73
+ ## Testing
74
+ `cargo test`
75
+
76
+ `python -m unittest src/test.py`
77
+
78
+ ## Type stubs
79
+ We have a manual `rust_pgn_reader_python_binding.pyi` python type stub.
80
+ To keep it in sync with the Rust bindings, run:
81
+
82
+ `python tools/check_stubs.py`
83
+
84
+ This regenerates the stub via `maturin generate-stubs` (PyO3
85
+ `experimental-inspect`, behind the `stubgen` Cargo feature) and diffs the
86
+ symbol surface (module functions, classes, methods/properties, but not
87
+ annotations) against the hand-written file. CI runs the same check in the
88
+ `stubs` job.
89
+
90
+
91
+ ## Further performance squeezing:
92
+ - Interleaved parquet read + parse
93
+ - Opening cache. For positions that occur plenty of times, a `(zobrist(pos), san_token)` → `Move`
94
+ cache could skip `san_resolver` + legality on cache hits.
95
+ - crude move-count estimator from movetext byte length for buffer preallocation (bytes-per-ply ≈ 6-6.5, over-estimate
96
+ is relatively cheap, under-estimate forces realloc copies). We currently assume 70 moves per game. Since we have long chunks (not per-game),
97
+ long game moves eat up the slack from short games. Wont' help on Lichess games (we're already doing quite well). But if going with a
98
+ different / unknown corpus, 70 will be off (different game lengths, comments). But comments will also throw off our byte-based move count estimator.
99
+
@@ -0,0 +1,9 @@
1
+ rust_pgn_reader_python_binding/__init__.py,sha256=lhSdJaKZEMrZVdzPOP73MTNFcg6hOIbqlkQ070gWYAQ,203
2
+ rust_pgn_reader_python_binding/__init__.pyi,sha256=reLZezmDzhgf918Nz3BQAgiv8PI1brq0UI6uzVdlsJk,11542
3
+ rust_pgn_reader_python_binding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ rust_pgn_reader_python_binding/rust_pgn_reader_python_binding.cpython-315-i386-linux-musl.so,sha256=7CA6IGR0dP0yZabu3WgRpCa7mCjUxDL7Edrih72alp4,17314837
5
+ rust_pgn_reader_python_binding-4.2.0.dist-info/METADATA,sha256=Ba9f6gB_q4GCpn9AUNAmvNDEC1F3hRFHFa9zWyfBEao,4689
6
+ rust_pgn_reader_python_binding-4.2.0.dist-info/WHEEL,sha256=AVOvPisvUAI6lf0hLvbRNTY5HpjHu_JcDKr2CcWb4i8,106
7
+ rust_pgn_reader_python_binding-4.2.0.dist-info/sboms/rust_pgn_reader_python_binding.cyclonedx.json,sha256=O4BiYfU52WHl_o_kaob3KcoaeaXAvlwdQ4IVUlUvPDA,102729
8
+ rust_pgn_reader_python_binding.libs/libgcc_s-8c2f5de4.so.1,sha256=skwA-Q5Jp32g8zGCUPVjrovZM8kIgh32NyKochtFx7U,553865
9
+ rust_pgn_reader_python_binding-4.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.16.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp315-cp315-musllinux_1_2_i686