lean-interact 0.4.1__tar.gz → 0.5.0__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.
Files changed (22) hide show
  1. lean_interact-0.5.0/.github/workflows/ci.yml +62 -0
  2. {lean_interact-0.4.1 → lean_interact-0.5.0}/PKG-INFO +20 -3
  3. {lean_interact-0.4.1 → lean_interact-0.5.0}/README.md +19 -1
  4. {lean_interact-0.4.1 → lean_interact-0.5.0}/pyproject.toml +1 -2
  5. {lean_interact-0.4.1 → lean_interact-0.5.0}/src/lean_interact/config.py +96 -34
  6. {lean_interact-0.4.1 → lean_interact-0.5.0}/src/lean_interact/interface.py +1 -1
  7. {lean_interact-0.4.1 → lean_interact-0.5.0}/src/lean_interact/server.py +143 -33
  8. {lean_interact-0.4.1 → lean_interact-0.5.0}/src/lean_interact/utils.py +118 -35
  9. lean_interact-0.5.0/tests/test_concurrency.py +429 -0
  10. {lean_interact-0.4.1 → lean_interact-0.5.0}/tests/test_server.py +156 -78
  11. {lean_interact-0.4.1 → lean_interact-0.5.0}/uv.lock +1 -3
  12. lean_interact-0.4.1/.github/workflows/ci.yml +0 -25
  13. lean_interact-0.4.1/tests/test_concurrency.py +0 -163
  14. {lean_interact-0.4.1 → lean_interact-0.5.0}/.devcontainer/devcontainer.json +0 -0
  15. {lean_interact-0.4.1 → lean_interact-0.5.0}/.github/workflows/publish-to-pypi.yml +0 -0
  16. {lean_interact-0.4.1 → lean_interact-0.5.0}/.gitignore +0 -0
  17. {lean_interact-0.4.1 → lean_interact-0.5.0}/LICENSE +0 -0
  18. {lean_interact-0.4.1 → lean_interact-0.5.0}/examples/beq_plus.py +0 -0
  19. {lean_interact-0.4.1 → lean_interact-0.5.0}/examples/proof_generation_and_autoformalization.py +0 -0
  20. {lean_interact-0.4.1 → lean_interact-0.5.0}/examples/type_check.py +0 -0
  21. {lean_interact-0.4.1 → lean_interact-0.5.0}/src/lean_interact/__init__.py +0 -0
  22. {lean_interact-0.4.1 → lean_interact-0.5.0}/tests/test_utils.py +0 -0
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ name: Test on ${{ matrix.os }}
8
+ runs-on: ${{ matrix.os }}
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ os: [ubuntu-latest, macos-latest, windows-latest]
13
+ python-version: ['3.10']
14
+
15
+ steps:
16
+ - name: Configure Windows for long paths
17
+ if: runner.os == 'Windows'
18
+ shell: pwsh
19
+ run: |
20
+ # Enable long paths in registry
21
+ New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
22
+ # Configure Git for long paths
23
+ git config --system core.longpaths true
24
+
25
+ - name: Checkout
26
+ uses: actions/checkout@v3
27
+
28
+ - name: Set up Python ${{ matrix.python-version }}
29
+ uses: actions/setup-python@v4
30
+ with:
31
+ python-version: ${{ matrix.python-version }}
32
+
33
+ - name: Install dependencies
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ python -m pip install .
37
+
38
+ - name: Install Lean (Linux/macOS)
39
+ if: runner.os != 'Windows'
40
+ run: |
41
+ install-lean
42
+ echo "$HOME/.elan/bin" >> $GITHUB_PATH
43
+
44
+ - name: Install Lean (Windows)
45
+ if: runner.os == 'Windows'
46
+ shell: pwsh
47
+ run: |
48
+ install-lean
49
+ echo "$env:USERPROFILE\.elan\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
50
+
51
+ - name: Run tests (Linux/macOS)
52
+ if: runner.os != 'Windows'
53
+ run: python -m unittest discover -s ./tests
54
+ env:
55
+ PYTHONIOENCODING: utf-8
56
+
57
+ - name: Run tests (Windows)
58
+ if: runner.os == 'Windows'
59
+ shell: pwsh
60
+ run: python -m unittest discover -s ./tests
61
+ env:
62
+ PYTHONIOENCODING: utf-8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-interact
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: LeanInteract is a Python package that allows you to interact with the Lean theorem prover.
5
5
  Author-email: Auguste Poiroux <auguste.poiroux@epfl.ch>
6
6
  License: MIT License
@@ -29,7 +29,6 @@ Keywords: Lean,REPL,autoformalization,theorem proving
29
29
  Requires-Python: >=3.10
30
30
  Requires-Dist: filelock>=3.18.0
31
31
  Requires-Dist: gitpython>=3.1.44
32
- Requires-Dist: pexpect>=4.9.0
33
32
  Requires-Dist: psutil>=6.1.0
34
33
  Requires-Dist: pydantic>=2.11.1
35
34
  Requires-Dist: requests>=2.32.3
@@ -38,7 +37,8 @@ Description-Content-Type: text/markdown
38
37
 
39
38
  # LeanInteract
40
39
 
41
- [![PyPI version](https://badge.fury.io/py/lean-interact.svg)](https://badge.fury.io/py/lean-interact)
40
+ [![PyPI version](https://img.shields.io/pypi/v/lean-interact.svg)](https://pypi.org/project/lean-interact/)
41
+ [![PyPI downloads](https://img.shields.io/pepy/dt/lean-interact.svg)](https://pypi.org/project/lean-interact/)
42
42
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
43
43
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
44
 
@@ -48,6 +48,7 @@ Description-Content-Type: text/markdown
48
48
 
49
49
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
50
50
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
51
+ - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
51
52
  - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.19.0-rc2`.
52
53
  - We backport the latest features of Lean REPL to older versions of Lean.
53
54
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
@@ -480,6 +481,22 @@ Common issues and their solutions:
480
481
 
481
482
  3. **Long waiting times during first run**: This is expected as Lean REPL is being downloaded and built. Additionally, if you are importing Mathlib it will take even more time. Subsequent runs will be much faster.
482
483
 
484
+ 4. **(Windows) Path too long error**: Windows has a maximum path length limitation of 260 characters.
485
+ If you get an error similar to the following one, you are likely affected by this problem:
486
+
487
+ ```
488
+ error: external command 'git' exited with code 128
489
+ ERROR Failed during Lean project setup: Command '['lake', 'update']' returned non-zero exit status 1.
490
+ ```
491
+
492
+ To resolve this, you can enable long paths in Windows 10 and later versions. For more information, refer to the [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation).
493
+ Alternatively, run the following command in a terminal with administrator privileges:
494
+
495
+ ```bash
496
+ New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name LongPathsEnabled -Value 1 -PropertyType DWord -Force
497
+ git config --system core.longpaths true
498
+ ```
499
+
483
500
  ## Contributing
484
501
 
485
502
  Contributions are welcome! Here's how you can help:
@@ -1,6 +1,7 @@
1
1
  # LeanInteract
2
2
 
3
- [![PyPI version](https://badge.fury.io/py/lean-interact.svg)](https://badge.fury.io/py/lean-interact)
3
+ [![PyPI version](https://img.shields.io/pypi/v/lean-interact.svg)](https://pypi.org/project/lean-interact/)
4
+ [![PyPI downloads](https://img.shields.io/pepy/dt/lean-interact.svg)](https://pypi.org/project/lean-interact/)
4
5
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
@@ -10,6 +11,7 @@
10
11
 
11
12
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
12
13
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
14
+ - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
13
15
  - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.19.0-rc2`.
14
16
  - We backport the latest features of Lean REPL to older versions of Lean.
15
17
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
@@ -442,6 +444,22 @@ Common issues and their solutions:
442
444
 
443
445
  3. **Long waiting times during first run**: This is expected as Lean REPL is being downloaded and built. Additionally, if you are importing Mathlib it will take even more time. Subsequent runs will be much faster.
444
446
 
447
+ 4. **(Windows) Path too long error**: Windows has a maximum path length limitation of 260 characters.
448
+ If you get an error similar to the following one, you are likely affected by this problem:
449
+
450
+ ```
451
+ error: external command 'git' exited with code 128
452
+ ERROR Failed during Lean project setup: Command '['lake', 'update']' returned non-zero exit status 1.
453
+ ```
454
+
455
+ To resolve this, you can enable long paths in Windows 10 and later versions. For more information, refer to the [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation).
456
+ Alternatively, run the following command in a terminal with administrator privileges:
457
+
458
+ ```bash
459
+ New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name LongPathsEnabled -Value 1 -PropertyType DWord -Force
460
+ git config --system core.longpaths true
461
+ ```
462
+
445
463
  ## Contributing
446
464
 
447
465
  Contributions are welcome! Here's how you can help:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lean-interact"
3
- version = "0.4.1"
3
+ version = "0.5.0"
4
4
  description = "LeanInteract is a Python package that allows you to interact with the Lean theorem prover."
5
5
  keywords = ["Lean", "theorem proving", "autoformalization", "REPL"]
6
6
  license = { file = "LICENSE" }
@@ -10,7 +10,6 @@ requires-python = ">=3.10"
10
10
  dependencies = [
11
11
  "filelock>=3.18.0",
12
12
  "gitpython>=3.1.44",
13
- "pexpect>=4.9.0",
14
13
  "psutil>=6.1.0",
15
14
  "pydantic>=2.11.1",
16
15
  "requests>=2.32.3",
@@ -58,8 +58,11 @@ class LocalProject(BaseProject):
58
58
  stderr = None if verbose else subprocess.DEVNULL
59
59
 
60
60
  with FileLock(f"{self.directory}.lock"):
61
- # check that the project is buildable
62
- subprocess.run(["lake", "build"], cwd=self.directory, check=True, stdout=stdout, stderr=stderr)
61
+ try:
62
+ subprocess.run(["lake", "build"], cwd=self.directory, check=True, stdout=stdout, stderr=stderr)
63
+ except subprocess.CalledProcessError as e:
64
+ logger.error("Failed to build local project: %s", e)
65
+ raise
63
66
 
64
67
 
65
68
  @dataclass(frozen=True)
@@ -70,8 +73,15 @@ class GitProject(BaseProject):
70
73
  rev: str | None = None
71
74
 
72
75
  def _get_directory(self, cache_dir: str, lean_version: str | None = None) -> str:
73
- repo_name = "/".join(self.url.split("/")[-2:]).replace(".git", "")
74
- return os.path.join(cache_dir, "git_projects", repo_name, self.rev or "latest")
76
+ repo_parts = self.url.split("/")
77
+ if len(repo_parts) >= 2:
78
+ owner = repo_parts[-2]
79
+ repo = repo_parts[-1].replace(".git", "")
80
+ return os.path.join(cache_dir, "git_projects", owner, repo, self.rev or "latest")
81
+ else:
82
+ # Fallback for malformed URLs
83
+ repo_name = self.url.replace(".git", "").split("/")[-1]
84
+ return os.path.join(cache_dir, "git_projects", repo_name, self.rev or "latest")
75
85
 
76
86
  def _instantiate(self, cache_dir: str, lean_version: str, verbose: bool = True):
77
87
  """Instantiate the git project."""
@@ -91,8 +101,11 @@ class GitProject(BaseProject):
91
101
 
92
102
  repo.submodule_update(init=True, recursive=True)
93
103
 
94
- # check that the project is buildable
95
- subprocess.run(["lake", "build"], cwd=project_dir, check=True, stdout=stdout, stderr=stderr)
104
+ try:
105
+ subprocess.run(["lake", "build"], cwd=project_dir, check=True, stdout=stdout, stderr=stderr)
106
+ except subprocess.CalledProcessError as e:
107
+ logger.error("Failed to build the git project: %s", e)
108
+ raise
96
109
 
97
110
 
98
111
  @dataclass(frozen=True)
@@ -117,8 +130,8 @@ class BaseTempProject(BaseProject):
117
130
 
118
131
  # Lock the temporary project directory during setup
119
132
  with FileLock(f"{tmp_project_dir}.lock"):
120
- # check if the Lean project is already built
121
- if not os.path.exists(os.path.join(tmp_project_dir, "lakefile.lean")):
133
+ # check if the Lean project already exists
134
+ if not os.path.exists(os.path.join(tmp_project_dir, "lake-manifest.json")):
122
135
  # clean the content of the folder in case of a previous aborted build
123
136
  shutil.rmtree(tmp_project_dir, ignore_errors=True)
124
137
  os.makedirs(tmp_project_dir, exist_ok=True)
@@ -127,16 +140,31 @@ class BaseTempProject(BaseProject):
127
140
  cmd_init = ["lake", f"+{lean_version}", "init", "dummy", "exe.lean"]
128
141
  if lean_version.startswith("v4") and int(lean_version.split(".")[1]) <= 7:
129
142
  cmd_init = ["lake", f"+{lean_version}", "init", "dummy", "exe"]
130
- subprocess.run(cmd_init, cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
143
+
144
+ try:
145
+ subprocess.run(cmd_init, cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
146
+ except subprocess.CalledProcessError as e:
147
+ logger.error("Failed to initialize Lean project: %s", e)
148
+ raise
131
149
 
132
150
  # Create or modify the lakefile
133
151
  self._modify_lakefile(tmp_project_dir, lean_version)
134
152
 
135
153
  logger.info("Preparing Lean environment with dependencies (may take a while the first time)...")
136
- subprocess.run(["lake", "update"], cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
137
- # in case mathlib is used as a dependency, we try to get the cache
138
- subprocess.run(["lake", "exe", "cache", "get"], cwd=tmp_project_dir, stdout=stdout, stderr=stderr)
139
- subprocess.run(["lake", "build"], cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
154
+
155
+ # Run lake commands with appropriate platform handling
156
+ try:
157
+ subprocess.run(["lake", "update"], cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
158
+ # in case mathlib is used as a dependency, we try to get the cache
159
+ subprocess.run(
160
+ ["lake", "exe", "cache", "get"], cwd=tmp_project_dir, check=False, stdout=stdout, stderr=stderr
161
+ )
162
+ subprocess.run(["lake", "build"], cwd=tmp_project_dir, check=True, stdout=stdout, stderr=stderr)
163
+ except subprocess.CalledProcessError as e:
164
+ logger.error("Failed during Lean project setup: %s", e)
165
+ # delete the project directory to avoid conflicts
166
+ shutil.rmtree(tmp_project_dir, ignore_errors=True)
167
+ raise
140
168
 
141
169
  def _get_hash_content(self, lean_version: str) -> str:
142
170
  """Return a unique hash for the project content."""
@@ -159,7 +187,7 @@ class TemporaryProject(BaseTempProject):
159
187
 
160
188
  def _modify_lakefile(self, project_dir: str, lean_version: str) -> None:
161
189
  """Write the content to the lakefile."""
162
- with open(os.path.join(project_dir, "lakefile.lean"), "w") as f:
190
+ with open(os.path.join(project_dir, "lakefile.lean"), "w", encoding="utf-8") as f:
163
191
  f.write(self.content)
164
192
 
165
193
 
@@ -173,15 +201,26 @@ class TempRequireProject(BaseTempProject):
173
201
  [MiniF2F](https://github.com/yangky11/miniF2F-lean4)) without having to manually set up a Lean project.
174
202
  """
175
203
 
176
- require: Literal["mathlib"] | list[LeanRequire]
204
+ require: Literal["mathlib"] | LeanRequire | list[LeanRequire | Literal["mathlib"]]
177
205
 
178
206
  def _normalize_require(self, lean_version: str) -> list[LeanRequire]:
179
207
  """Normalize the require field to always be a list."""
180
208
  require = self.require
181
- if require == "mathlib":
182
- return [LeanRequire("mathlib", "https://github.com/leanprover-community/mathlib4.git", lean_version)]
183
- assert isinstance(require, list)
184
- return sorted(require, key=lambda x: x.name)
209
+ if not isinstance(require, list):
210
+ require = [require]
211
+
212
+ normalized_require: list[LeanRequire] = []
213
+ for req in require:
214
+ if req == "mathlib":
215
+ normalized_require.append(
216
+ LeanRequire("mathlib", "https://github.com/leanprover-community/mathlib4.git", lean_version)
217
+ )
218
+ elif isinstance(req, LeanRequire):
219
+ normalized_require.append(req)
220
+ else:
221
+ raise ValueError(f"Invalid requirement type: {type(req)}")
222
+
223
+ return sorted(normalized_require, key=lambda x: x.name)
185
224
 
186
225
  def _get_hash_content(self, lean_version: str) -> str:
187
226
  """Return a unique hash based on dependencies."""
@@ -191,7 +230,7 @@ class TempRequireProject(BaseTempProject):
191
230
  def _modify_lakefile(self, project_dir: str, lean_version: str) -> None:
192
231
  """Add requirements to the lakefile."""
193
232
  require = self._normalize_require(lean_version)
194
- with open(os.path.join(project_dir, "lakefile.lean"), "a") as f:
233
+ with open(os.path.join(project_dir, "lakefile.lean"), "a", encoding="utf-8") as f:
195
234
  for req in require:
196
235
  f.write(f'\n\nrequire {req.name} from git\n "{req.git}"' + (f' @ "{req.rev}"' if req.rev else ""))
197
236
 
@@ -208,16 +247,18 @@ class LeanREPLConfig:
208
247
  verbose: bool = False,
209
248
  ):
210
249
  """
211
- Configuration class used to specify the Lean environment we wish to use.
250
+ Initialize the Lean REPL configuration.
212
251
 
213
252
  Args:
214
- lean_version: Lean version you want to use with the REPL.
215
- If `None`, `LeanREPLConfig` will try to infer `lean_version` from `project`, otherwise
216
- the latest version available in the Lean REPL repository will be used.
217
- project: Source configuration for the Lean project. Use one of:
218
- - `LocalProject`: A directory where a local Lean project is stored.
219
- - `GitProject`: A git repository with a Lean project.
220
- - `TemporaryProject`: A temporary Lean project with a custom lakefile that will be created.
253
+ lean_version:
254
+ The Lean version you want to use.
255
+ Default is `None`, which means the latest version compatible with the project will be selected.
256
+ project:
257
+ The project you want to use. There are 4 options:
258
+ - `None`: The project will only depend on Lean and its standard library.
259
+ - `LocalProject`: An existing local Lean project.
260
+ - `GitProject`: A git repository with a Lean project that will be cloned.
261
+ - `TemporaryProject`: A temporary Lean project with a custom lakefile.lean that will be created.
221
262
  - `TempRequireProject`: A temporary Lean project with dependencies that will be created.
222
263
  repl_rev:
223
264
  The REPL version you want to use. It is not recommended to change this value unless you know what you are doing.
@@ -228,6 +269,7 @@ class LeanREPLConfig:
228
269
  Default is inside the package directory.
229
270
  memory_hard_limit_mb:
230
271
  The maximum memory usage in MB for the Lean server. Setting this value too low may lead to more command processing failures.
272
+ Only available on Linux platforms.
231
273
  Default is `None`, which means no limit.
232
274
  verbose:
233
275
  Whether to print additional information during the setup process.
@@ -236,14 +278,21 @@ class LeanREPLConfig:
236
278
  self.project = project
237
279
  self.repl_git = repl_git
238
280
  self.repl_rev = repl_rev
239
- self.cache_dir = cache_dir
281
+ self.cache_dir = os.path.normpath(cache_dir)
240
282
  self.memory_hard_limit_mb = memory_hard_limit_mb
241
283
 
242
284
  self.verbose = verbose
243
285
  self._stdout = None if self.verbose else subprocess.DEVNULL
244
286
  self._stderr = None if self.verbose else subprocess.DEVNULL
245
287
 
246
- self.repo_name = "/".join(self.repl_git.split("/")[-2:]).replace(".git", "")
288
+ repo_parts = self.repl_git.split("/")
289
+ if len(repo_parts) >= 2:
290
+ owner = repo_parts[-2]
291
+ repo = repo_parts[-1].replace(".git", "")
292
+ self.repo_name = os.path.join(owner, repo)
293
+ else:
294
+ self.repo_name = self.repl_git.replace(".git", "")
295
+
247
296
  self.cache_clean_repl_dir = os.path.join(self.cache_dir, self.repo_name, "repl_clean_copy")
248
297
 
249
298
  # check if lake is installed
@@ -322,10 +371,13 @@ class LeanREPLConfig:
322
371
  f"Please open an issue on GitHub if you think this is a bug."
323
372
  )
324
373
 
325
- # build the repl
326
- subprocess.run(
327
- ["lake", "build"], cwd=self._cache_repl_dir, check=True, stdout=self._stdout, stderr=self._stderr
328
- )
374
+ try:
375
+ subprocess.run(
376
+ ["lake", "build"], cwd=self._cache_repl_dir, check=True, stdout=self._stdout, stderr=self._stderr
377
+ )
378
+ except subprocess.CalledProcessError as e:
379
+ logger.error("Failed to build the REPL: %s", e)
380
+ raise
329
381
 
330
382
  def _get_available_lean_versions_sha(self) -> list[tuple[str, str]]:
331
383
  """
@@ -344,5 +396,15 @@ class LeanREPLConfig:
344
396
  """
345
397
  return [commit[0] for commit in self._get_available_lean_versions_sha()]
346
398
 
399
+ @property
400
+ def working_dir(self) -> str:
401
+ """Get the working directory for the Lean environment."""
402
+ return self._working_dir
403
+
404
+ @property
405
+ def cache_repl_dir(self) -> str:
406
+ """Get the cache directory for the Lean REPL."""
407
+ return self._cache_repl_dir
408
+
347
409
  def is_setup(self) -> bool:
348
410
  return hasattr(self, "_working_dir")
@@ -254,7 +254,7 @@ class CommandResponse(BaseREPLResponse):
254
254
 
255
255
  env: int
256
256
  tactics: list[Tactic] = Field(default_factory=list)
257
- infotree: dict | None = None
257
+ infotree: list | None = None
258
258
 
259
259
 
260
260
  class ProofStepResponse(BaseREPLResponse):