| from __future__ import annotations |
|
|
| import importlib.util |
| import shutil |
| import tempfile |
| from collections.abc import Callable |
| from importlib import resources |
| from pathlib import Path |
|
|
| import chess |
|
|
|
|
| WorkspaceEvalFn = Callable[[chess.Board], int] |
|
|
|
|
| class WorkspaceManager: |
| allowed_files = {"eval.py"} |
|
|
| def __init__(self) -> None: |
| self.root = Path(tempfile.mkdtemp(prefix="zero960_")) |
| self._write_template_files() |
|
|
| def _write_template_files(self) -> None: |
| template = resources.files("zero960.workspace_template").joinpath("eval.py").read_text() |
| self.write_file("eval.py", template) |
|
|
| def cleanup(self) -> None: |
| shutil.rmtree(self.root, ignore_errors=True) |
|
|
| def resolve_path(self, relative_path: str) -> Path: |
| if relative_path not in self.allowed_files: |
| raise ValueError(f"writes are restricted to {sorted(self.allowed_files)}") |
| return self.root / relative_path |
|
|
| def read_file(self, relative_path: str) -> str: |
| path = self.resolve_path(relative_path) |
| return path.read_text() |
|
|
| def write_file(self, relative_path: str, content: str) -> None: |
| path = self.resolve_path(relative_path) |
| path.parent.mkdir(parents=True, exist_ok=True) |
| path.write_text(content) |
|
|
| def load_eval_function(self) -> WorkspaceEvalFn: |
| path = self.resolve_path("eval.py") |
| module_name = f"zero960_eval_{id(path)}" |
| spec = importlib.util.spec_from_file_location(module_name, path) |
| if spec is None or spec.loader is None: |
| raise RuntimeError("failed to create module spec for eval.py") |
|
|
| module = importlib.util.module_from_spec(spec) |
| spec.loader.exec_module(module) |
|
|
| eval_fn = getattr(module, "evaluate", None) |
| if eval_fn is None or not callable(eval_fn): |
| raise RuntimeError("eval.py must define evaluate(board)") |
| return eval_fn |
|
|
|
|