|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -from typing import List, cast |
| 3 | +from typing import List, cast, Iterator |
4 | 4 |
|
5 | 5 | from .base import BaseClient |
| 6 | +import requests |
| 7 | +from ..formats import NDJSON, TEXT |
| 8 | +from ..types.common import UciVariant |
| 9 | +from ..types.external_engine import ExternalEngineRequest, EngineAnalysisOutput |
| 10 | + |
| 11 | +EXTERNAL_ENGINE_URL = "https://engine.lichess.ovh" |
6 | 12 |
|
7 | 13 |
|
8 | 14 | class ExternalEngine(BaseClient): |
9 | 15 | """Client for external engine related endpoints.""" |
10 | 16 |
|
| 17 | + def __init__( |
| 18 | + self, |
| 19 | + session: requests.Session, |
| 20 | + *, |
| 21 | + base_url: str | None = None, |
| 22 | + external_engine_url: str | None = None, |
| 23 | + ): |
| 24 | + """Create a subclient for the endpoints that use a different base url.""" |
| 25 | + super().__init__(session, base_url) |
| 26 | + self._external_client = BaseClient( |
| 27 | + session, external_engine_url or EXTERNAL_ENGINE_URL |
| 28 | + ) |
| 29 | + |
11 | 30 | def get(self) -> List[ExternalEngine]: |
12 | 31 | """Lists all external engines that have been registered for the user, and the credentials required to use them. |
13 | 32 |
|
@@ -112,3 +131,85 @@ def delete(self, engine_id: str) -> None: |
112 | 131 | """ |
113 | 132 | path = f"/api/external-engine/{engine_id}" |
114 | 133 | self._r.request("DELETE", path) |
| 134 | + |
| 135 | + def analyse( |
| 136 | + self, |
| 137 | + engine_id: str, |
| 138 | + client_secret: str, |
| 139 | + session_id: str, |
| 140 | + threads: int, |
| 141 | + hash_table_size: int, |
| 142 | + pri_num_variations: int, |
| 143 | + variant: UciVariant, |
| 144 | + initial_fen: str, |
| 145 | + moves: List[str], |
| 146 | + movetime: int | None = None, |
| 147 | + depth: int | None = None, |
| 148 | + nodes: int | None = None, |
| 149 | + ) -> Iterator[EngineAnalysisOutput]: |
| 150 | + """ |
| 151 | + Analyse with external engine |
| 152 | +
|
| 153 | + Request analysis from an external engine. Response content is streamed as newline delimited JSON. |
| 154 | + The properties are based on the UCI specification. |
| 155 | + Analysis stops when the client goes away, the requested limit is reached, or the provider goes away. |
| 156 | +
|
| 157 | + :param engine_id: external engine id |
| 158 | + :param client_secret: engine credentials |
| 159 | + :param session_id: Arbitary string that identifies the analysis session. Providers may wish to clear the hash table between sessions. |
| 160 | + :param threads: Number of threads to use for analysis. |
| 161 | + :param hash_table_size: Hash table size to use for analysis, in MiB. |
| 162 | + :param pri_num_variations: Requested number of principal variations. (1-5) |
| 163 | + :param variant: uci variant |
| 164 | + :param initial_fen: Initial position of the game. |
| 165 | + :param moves: List of moves played from the initial position, in UCI notation. |
| 166 | + :param movetime: Amount of time to analyse the position, in milliseconds. |
| 167 | + :param depth: Analysis target depth |
| 168 | + :param nodes: Number of nodes to analyse in the position |
| 169 | + """ |
| 170 | + path = f"/api/external-engine/{engine_id}/analyse" |
| 171 | + payload = { |
| 172 | + "clientSecret": client_secret, |
| 173 | + "work": { |
| 174 | + "sessionId": session_id, |
| 175 | + "threads": threads, |
| 176 | + "hash": hash_table_size, |
| 177 | + "multiPv": pri_num_variations, |
| 178 | + "variant": variant, |
| 179 | + "initialFen": initial_fen, |
| 180 | + "moves": moves, |
| 181 | + "movetime": movetime, |
| 182 | + "depth": depth, |
| 183 | + "nodes": nodes, |
| 184 | + }, |
| 185 | + } |
| 186 | + |
| 187 | + for response in self._external_client._r.post( |
| 188 | + path=path, |
| 189 | + payload=payload, |
| 190 | + stream=True, |
| 191 | + fmt=NDJSON, |
| 192 | + ): |
| 193 | + yield cast(EngineAnalysisOutput, response) |
| 194 | + |
| 195 | + def acquire_request(self, provider_secret: str) -> ExternalEngineRequest: |
| 196 | + """Wait for an analysis request to any of the external engines that have been registered with the given secret. |
| 197 | + :param provider_secret: provider credentials |
| 198 | + :return: the requested analysis |
| 199 | + """ |
| 200 | + path = "/api/external-engine/work" |
| 201 | + payload = {"providerSecret": provider_secret} |
| 202 | + return cast( |
| 203 | + ExternalEngineRequest, |
| 204 | + self._external_client._r.post(path=path, payload=payload), |
| 205 | + ) |
| 206 | + |
| 207 | + def answer_request(self, engine_id: str) -> str: |
| 208 | + """Submit a stream of analysis as UCI output. |
| 209 | + The server may close the connection at any time, indicating that the requester has gone away and analysis |
| 210 | + should be stopped. |
| 211 | + :param engine_id: engine ID |
| 212 | + :return: the requested analysis |
| 213 | + """ |
| 214 | + path = f"/api/external-engine/work/{engine_id}" |
| 215 | + return self._external_client._r.post(path=path, fmt=TEXT) |
0 commit comments