lean-interact 0.6.0__tar.gz → 0.6.2__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 (43) hide show
  1. lean_interact-0.6.2/.github/workflows/update-changelog.yml +33 -0
  2. {lean_interact-0.6.0 → lean_interact-0.6.2}/PKG-INFO +3 -3
  3. {lean_interact-0.6.0 → lean_interact-0.6.2}/README.md +2 -2
  4. lean_interact-0.6.2/docs/changelog.md +89 -0
  5. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/examples.md +1 -4
  6. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/index.md +1 -1
  7. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/troubleshooting.md +7 -7
  8. lean_interact-0.6.2/docs/update_changelog.py +156 -0
  9. {lean_interact-0.6.0 → lean_interact-0.6.2}/pyproject.toml +1 -1
  10. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/config.py +22 -10
  11. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/server.py +6 -1
  12. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/sessioncache.py +1 -1
  13. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/utils.py +1 -1
  14. {lean_interact-0.6.0 → lean_interact-0.6.2}/tests/test_server.py +63 -24
  15. {lean_interact-0.6.0 → lean_interact-0.6.2}/uv.lock +1 -1
  16. lean_interact-0.6.0/.github/workflows/update-changelog.yml +0 -55
  17. lean_interact-0.6.0/docs/changelog.md +0 -65
  18. {lean_interact-0.6.0 → lean_interact-0.6.2}/.devcontainer/devcontainer.json +0 -0
  19. {lean_interact-0.6.0 → lean_interact-0.6.2}/.github/workflows/ci.yml +0 -0
  20. {lean_interact-0.6.0 → lean_interact-0.6.2}/.github/workflows/docs.yml +0 -0
  21. {lean_interact-0.6.0 → lean_interact-0.6.2}/.github/workflows/publish-to-pypi.yml +0 -0
  22. {lean_interact-0.6.0 → lean_interact-0.6.2}/.gitignore +0 -0
  23. {lean_interact-0.6.0 → lean_interact-0.6.2}/LICENSE +0 -0
  24. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/.nojekyll +0 -0
  25. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/api/config.md +0 -0
  26. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/api/interface.md +0 -0
  27. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/api/server.md +0 -0
  28. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/api/sessioncache.md +0 -0
  29. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/api/utils.md +0 -0
  30. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/contributing.md +0 -0
  31. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/installation.md +0 -0
  32. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/user-guide/basic-usage.md +0 -0
  33. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/user-guide/custom-lean-configuration.md +0 -0
  34. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/user-guide/getting-started.md +0 -0
  35. {lean_interact-0.6.0 → lean_interact-0.6.2}/docs/user-guide/tactic-mode.md +0 -0
  36. {lean_interact-0.6.0 → lean_interact-0.6.2}/examples/beq_plus.py +0 -0
  37. {lean_interact-0.6.0 → lean_interact-0.6.2}/examples/proof_generation_and_autoformalization.py +0 -0
  38. {lean_interact-0.6.0 → lean_interact-0.6.2}/examples/type_check.py +0 -0
  39. {lean_interact-0.6.0 → lean_interact-0.6.2}/mkdocs.yml +0 -0
  40. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/__init__.py +0 -0
  41. {lean_interact-0.6.0 → lean_interact-0.6.2}/src/lean_interact/interface.py +0 -0
  42. {lean_interact-0.6.0 → lean_interact-0.6.2}/tests/test_concurrency.py +0 -0
  43. {lean_interact-0.6.0 → lean_interact-0.6.2}/tests/test_utils.py +0 -0
@@ -0,0 +1,33 @@
1
+ name: Update Changelog
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ update-changelog:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: write
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: '3.10'
20
+
21
+ - name: Update Changelog
22
+ env:
23
+ GITHUB_TOKEN: ${{ github.token }}
24
+ run: |
25
+ python docs/update_changelog.py --release-tag "${{ github.event.release.tag_name }}"
26
+
27
+ - name: Commit and push changelog
28
+ run: |
29
+ git config --local user.email "action@github.com"
30
+ git config --local user.name "GitHub Action"
31
+ git add docs/changelog.md
32
+ git commit -m "Update changelog with ${{ github.event.release.tag_name }} release notes"
33
+ git push origin ${GITHUB_REF_NAME}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-interact
3
- Version: 0.6.0
3
+ Version: 0.6.2
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
@@ -51,8 +51,8 @@ Description-Content-Type: text/markdown
51
51
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
52
52
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
53
53
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
54
- - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.21.0-rc3`.
55
- - We backport the latest features of Lean REPL to older versions of Lean.
54
+ - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.21.0`.
55
+ - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
56
56
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
57
57
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
58
58
 
@@ -13,8 +13,8 @@
13
13
  - **🔗 Interactivity**: Execute Lean code and files directly from Python.
14
14
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
15
15
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems.
16
- - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.21.0-rc3`.
17
- - We backport the latest features of Lean REPL to older versions of Lean.
16
+ - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.21.0`.
17
+ - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
18
18
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
19
19
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
20
20
 
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ This page documents the notable changes to LeanInteract.
4
+
5
+ ## v0.6.1 (June 24, 2025)
6
+
7
+ ## What's Changed
8
+
9
+ * Fix Lean version inference for `LocalProject` and `GitProject` by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/26>
10
+ * Fix `ResourceWarning` issues when killing the REPL
11
+ * Improve memory monitoring in `AutoLeanServer`
12
+
13
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.6.0...v0.6.1>
14
+
15
+ ## v0.6.0 (June 05, 2025)
16
+
17
+ ## What's Changed
18
+
19
+ * Support for local / custom REPL + Lean versions up to v4.21.0-rc3 by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/21>
20
+ * Separate the session cache logic from `AutoLeanServer` by @sorgfresser in <https://github.com/augustepoiroux/LeanInteract/pull/18>
21
+ * Add documentation by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/19>
22
+ * Add support for modern Python Path by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/20>
23
+
24
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.5.3...v0.6.0>
25
+
26
+ ## v0.5.3 (May 18, 2025)
27
+
28
+ ## What's Changed
29
+
30
+ * Add optional build boolean for LocalProject by @sorgfresser in <https://github.com/augustepoiroux/LeanInteract/pull/16>
31
+ * Slightly improve sorry detection in `lean_code_is_valid` by checking `message` instead of just `sorries` in REPL output.
32
+
33
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.5.2...v0.5.3>
34
+
35
+ ## v0.5.2 (May 01, 2025)
36
+
37
+ Introduce compatibility with Lean v4.19.0
38
+
39
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.5.1...v0.5.2>
40
+
41
+ ## v0.5.1 (April 30, 2025)
42
+
43
+ ## What's Changed
44
+
45
+ * Add fix for non-respected timeout by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/13>
46
+ * Query Lake cache for all Project types by @habemus-papadum in <https://github.com/augustepoiroux/LeanInteract/pull/10>
47
+ * Bump REPL version to v1.0.7 fixing `"auxiliary declaration cannot be created when declaration name is not available"` in tactic mode for Lean <= v4.18.0 <https://github.com/leanprover-community/repl/issues/44#issuecomment-2814069261>
48
+
49
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.5.0...v0.5.1>
50
+
51
+ ## v0.5.0 (April 21, 2025)
52
+
53
+ ## What's Changed
54
+
55
+ * Make LeanInteract cross-platform by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/4>
56
+ * Fix infotree parsing issue by @sorgfresser in <https://github.com/augustepoiroux/LeanInteract/pull/1>
57
+ * Implement `async_run` + make calls to the REPL thread-safe by @augustepoiroux in <https://github.com/augustepoiroux/LeanInteract/pull/2>
58
+
59
+ ## v0.4.1 (April 18, 2025)
60
+
61
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.4.0...v0.4.1>
62
+
63
+ ## v0.4.0 (April 11, 2025)
64
+
65
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.3.3...v0.4.0>
66
+
67
+ ## v0.3.3 (April 04, 2025)
68
+
69
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.3.2...v0.3.3>
70
+
71
+ ## v0.3.2 (April 03, 2025)
72
+
73
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.3.1...v0.3.2>
74
+
75
+ ## v0.3.1 (April 02, 2025)
76
+
77
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.3.0...v0.3.1>
78
+
79
+ ## v0.3.0 (April 02, 2025)
80
+
81
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/compare/v0.2.0...v0.3.0>
82
+
83
+ ## v0.2.0 (March 18, 2025)
84
+
85
+ **Full Changelog**: <https://github.com/augustepoiroux/LeanInteract/commits/v0.2.0>
86
+
87
+ ## Pre-release Development
88
+
89
+ For development history prior to the first release, please see the [GitHub commit history](https://github.com/augustepoiroux/LeanInteract/commits/main).
@@ -34,10 +34,7 @@ This example shows how to use Mathlib to work with more advanced mathematical co
34
34
  from lean_interact import LeanREPLConfig, LeanServer, Command, TempRequireProject
35
35
 
36
36
  # Create configuration with Mathlib
37
- config = LeanREPLConfig(
38
- project=TempRequireProject("mathlib"),
39
- verbose=True
40
- )
37
+ config = LeanREPLConfig(project=TempRequireProject("mathlib"))
41
38
  server = LeanServer(config)
42
39
 
43
40
  # Define a theorem using Mathlib's real numbers
@@ -13,7 +13,7 @@
13
13
  - **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction
14
14
  - **💻 Cross-platform**: Works on Windows, macOS, and Linux operating systems
15
15
  - **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.19.0`
16
- - We backport the latest features of Lean REPL to older versions of Lean
16
+ - We backport the latest features of Lean REPL to older versions of Lean (see [fork](https://github.com/augustepoiroux/repl)).
17
17
  - **📦 Temporary Projects**: Easily instantiate temporary Lean environments
18
18
  - Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4)
19
19
 
@@ -15,15 +15,14 @@ This guide covers common issues you might encounter when using LeanInteract.
15
15
  **Solutions**:
16
16
 
17
17
  - Reduce parallel processing or increase system memory
18
- - Use `AutoLeanServer` with conservative memory settings:
18
+ - Limit the maximum amount of memory usage allocated to the REPL with `LeanREPLConfig`:
19
19
 
20
20
  ```python
21
- from lean_interact import AutoLeanServer
22
- server = AutoLeanServer(config, memory_threshold_mb=1000) # Limit to 1GB
21
+ from lean_interact import AutoLeanServer, LeanREPLConfig
22
+ server = AutoLeanServer(LeanREPLConfig(memory_hard_limit_mb=1000)) # Limit to 1GB
23
23
  ```
24
24
 
25
- - Avoid working with large files or complex proofs in a single session
26
- - Use environment pickling to save and restore states across sessions
25
+ - If you are working with large files or complex proofs in a single session, consider breaking them into smaller, more manageable pieces.
27
26
 
28
27
  ### Timeout Errors
29
28
 
@@ -34,10 +33,11 @@ This guide covers common issues you might encounter when using LeanInteract.
34
33
 
35
34
  **Solutions**:
36
35
 
37
- - Increase the timeout in the configuration:
36
+ - Pass a higher timeout to `run()`/`async_run()`:
38
37
 
39
38
  ```python
40
- config = LeanREPLConfig(timeout=60) # 60 seconds
39
+ server = AutoLeanServer(LeanREPLConfig())
40
+ result = server.run(Command(cmd="..."), timeout=60)
41
41
  ```
42
42
 
43
43
  - Use `AutoLeanServer` for automatic recovery from timeouts:
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Update the changelog from GitHub releases.
4
+ This script fetches release information from the GitHub API and updates the changelog file.
5
+ """
6
+
7
+ import argparse
8
+ import os
9
+ import re
10
+ import sys
11
+ from datetime import datetime
12
+
13
+ import requests
14
+
15
+ # Configuration
16
+ REPO_OWNER = "augustepoiroux"
17
+ REPO_NAME = "LeanInteract"
18
+ CHANGELOG_PATH = "docs/changelog.md"
19
+ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") # Set this environment variable if you're hitting rate limits
20
+
21
+
22
+ def format_date(date_string):
23
+ """Format the date as Month Day, Year"""
24
+ date_obj = datetime.fromisoformat(date_string.replace("Z", "+00:00"))
25
+ return date_obj.strftime("%B %d, %Y")
26
+
27
+
28
+ def fetch_releases():
29
+ """Fetch releases from GitHub API"""
30
+ headers = {}
31
+ if GITHUB_TOKEN:
32
+ headers["Authorization"] = f"token {GITHUB_TOKEN}"
33
+
34
+ url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/releases"
35
+ response = requests.get(url, headers=headers, timeout=30) # 30 seconds timeout
36
+
37
+ if response.status_code != 200:
38
+ print(f"Failed to fetch releases: {response.status_code}")
39
+ print(response.text)
40
+ sys.exit(1)
41
+
42
+ return response.json()
43
+
44
+
45
+ def format_release(release):
46
+ """Format a release for the changelog"""
47
+ tag = release["tag_name"]
48
+ name = release.get("name") or tag
49
+ body = release["body"].strip()
50
+ published_at = format_date(release["published_at"])
51
+
52
+ # If the name is just the tag, make it more presentable
53
+ if name == tag:
54
+ name = tag
55
+
56
+ return f"\n\n## {name} ({published_at})\n\n{body}\n"
57
+
58
+
59
+ def update_changelog(releases):
60
+ """Update the changelog file with the releases"""
61
+ # Create changelog if it doesn't exist
62
+ if not os.path.exists(CHANGELOG_PATH):
63
+ with open(CHANGELOG_PATH, "w", encoding="utf-8") as f:
64
+ f.write("# Changelog\n\nThis page documents the notable changes to LeanInteract.\n\n")
65
+
66
+ # Read current changelog content
67
+ with open(CHANGELOG_PATH, "r", encoding="utf-8") as f:
68
+ content = f.read()
69
+
70
+ # Extract header and existing releases
71
+ header_match = re.match(r"(# Changelog\s+.*?)\s*##", content, re.DOTALL)
72
+ if header_match:
73
+ header = header_match.group(1)
74
+ else:
75
+ header = "# Changelog\n\nThis page documents the notable changes to LeanInteract.\n\n"
76
+
77
+ # Check if we're only updating a single release
78
+ if len(releases) == 1:
79
+ # For a single release, insert it after the header
80
+ new_release = format_release(releases[0])
81
+
82
+ # Check if this release already exists in the changelog
83
+ release_tag = releases[0]["tag_name"]
84
+ if re.search(rf"## .*{re.escape(release_tag)}.*?\(", content):
85
+ print(f"Release {release_tag} already exists in the changelog. Skipping update.")
86
+ return
87
+
88
+ # Insert the new release after the header
89
+ new_content = re.sub(r"(# Changelog\s+.*?)(\s*##)", f"\\1{new_release}\\2", content, count=1, flags=re.DOTALL)
90
+
91
+ # If the pattern didn't match, append to header
92
+ if new_content == content:
93
+ new_content = header + new_release + content[len(header) :]
94
+ else:
95
+ # Generate new changelog content with all releases
96
+ new_content = header
97
+ for release in releases:
98
+ new_content += format_release(release)
99
+
100
+ # Add a note about pre-release development if it doesn't exist in content
101
+ if "## Pre-release Development" not in content:
102
+ new_content += "## Pre-release Development\n\n"
103
+ new_content += f"For development history prior to the first release, please see the [GitHub commit history](https://github.com/{REPO_OWNER}/{REPO_NAME}/commits/main)."
104
+ else:
105
+ # Extract and add the pre-release section
106
+ pre_release_match = re.search(r"(## Pre-release Development[\s\S]*$)", content)
107
+ if pre_release_match:
108
+ new_content += pre_release_match.group(1)
109
+
110
+ # Write updated changelog
111
+ with open(CHANGELOG_PATH, "w", encoding="utf-8") as f:
112
+ f.write(new_content)
113
+
114
+ print(f"Updated {CHANGELOG_PATH} with {len(releases)} releases.")
115
+
116
+
117
+ def fetch_release_by_tag(tag_name):
118
+ """Fetch a specific release by its tag name"""
119
+ headers = {}
120
+ if GITHUB_TOKEN:
121
+ headers["Authorization"] = f"token {GITHUB_TOKEN}"
122
+
123
+ url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/releases/tags/{tag_name}"
124
+ response = requests.get(url, headers=headers, timeout=30) # 30 seconds timeout
125
+
126
+ if response.status_code != 200:
127
+ print(f"Failed to fetch release with tag '{tag_name}': {response.status_code}")
128
+ print(response.text)
129
+ sys.exit(1)
130
+
131
+ return response.json()
132
+
133
+
134
+ def main():
135
+ parser = argparse.ArgumentParser(description="Update the changelog with GitHub release information")
136
+ parser.add_argument("--release-tag", type=str, help="Update the changelog with only this specific release tag")
137
+ args = parser.parse_args()
138
+
139
+ if args.release_tag:
140
+ # Get a specific release by tag name
141
+ release = fetch_release_by_tag(args.release_tag)
142
+ releases = [release]
143
+ else:
144
+ # Get all releases
145
+ releases = fetch_releases()
146
+ if not releases:
147
+ print("No releases found.")
148
+ return
149
+ # Sort releases by published date (newest first)
150
+ releases.sort(key=lambda r: r["published_at"], reverse=True)
151
+
152
+ update_changelog(releases)
153
+
154
+
155
+ if __name__ == "__main__":
156
+ main()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lean-interact"
3
- version = "0.6.0"
3
+ version = "0.6.2"
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" }
@@ -57,7 +57,7 @@ class LocalProject(BaseProject):
57
57
  return Path(self.directory)
58
58
 
59
59
  def _instantiate(
60
- self, cache_dir: str | PathLike, lean_version: str, lake_path: str | PathLike, verbose: bool = True
60
+ self, cache_dir: str | PathLike, lean_version: str | None, lake_path: str | PathLike, verbose: bool = True
61
61
  ):
62
62
  """Instantiate the local project."""
63
63
  if not self.build:
@@ -97,7 +97,7 @@ class GitProject(BaseProject):
97
97
  return cache_dir / "git_projects" / repo_name / (self.rev or "latest")
98
98
 
99
99
  def _instantiate(
100
- self, cache_dir: str | PathLike, lean_version: str, lake_path: str | PathLike, verbose: bool = True
100
+ self, cache_dir: str | PathLike, lean_version: str | None, lake_path: str | PathLike, verbose: bool = True
101
101
  ):
102
102
  """Instantiate the git project."""
103
103
 
@@ -333,7 +333,7 @@ class LeanREPLConfig:
333
333
  self.memory_hard_limit_mb = memory_hard_limit_mb
334
334
  self.lake_path = Path(lake_path)
335
335
  self.verbose = verbose
336
- self._timeout = 300
336
+ self._timeout_lock = 300
337
337
 
338
338
  # Configure output streams based on verbosity
339
339
  self._stdout = None if self.verbose else subprocess.DEVNULL
@@ -357,13 +357,29 @@ class LeanREPLConfig:
357
357
  "You can try to run `install-lean` or find installation instructions here: https://leanprover-community.github.io/get_started.html"
358
358
  )
359
359
 
360
+ # If the project is not temporary, we first set up the project to infer the Lean version.
361
+ if isinstance(self.project, (LocalProject, GitProject)):
362
+ self.project._instantiate(
363
+ cache_dir=self.cache_dir, lean_version=self.lean_version, lake_path=self.lake_path, verbose=self.verbose
364
+ )
365
+ project_lean_version = get_project_lean_version(self.project._get_directory(self.cache_dir))
366
+ assert project_lean_version is not None, (
367
+ f"Could not determine Lean version for project at {self.project._get_directory(self.cache_dir)}"
368
+ )
369
+ if self.lean_version is not None:
370
+ assert project_lean_version == self.lean_version, (
371
+ f"Project Lean version `{project_lean_version}` does not match the requested Lean version `{self.lean_version}`."
372
+ )
373
+ self.lean_version = project_lean_version
374
+ self._working_dir = self.project._get_directory(cache_dir=self.cache_dir, lean_version=self.lean_version)
375
+
360
376
  self._setup_repl()
361
377
 
362
378
  assert isinstance(self.lean_version, str)
363
379
 
364
380
  if self.project is None:
365
381
  self._working_dir = self._cache_repl_dir
366
- else:
382
+ elif not isinstance(self.project, (LocalProject, GitProject)):
367
383
  self.project._instantiate(
368
384
  cache_dir=self.cache_dir,
369
385
  lean_version=self.lean_version,
@@ -417,15 +433,11 @@ class LeanREPLConfig:
417
433
 
418
434
  assert isinstance(self.repl_rev, str)
419
435
 
420
- # Try to infer Lean version if not specified
421
- if self.lean_version is None and isinstance(self.project, (LocalProject, GitProject)):
422
- self.lean_version = get_project_lean_version(self.project._get_directory(self.cache_dir))
423
-
424
436
  def get_tag_name(lean_version: str) -> str:
425
437
  return f"{self.repl_rev}_lean-toolchain-{lean_version}"
426
438
 
427
439
  # First, ensure we have the clean repository
428
- with FileLock(f"{self.cache_clean_repl_dir}.lock", timeout=self._timeout):
440
+ with FileLock(f"{self.cache_clean_repl_dir}.lock", timeout=self._timeout_lock):
429
441
  # Check if the repl is already cloned
430
442
  if not self.cache_clean_repl_dir.exists():
431
443
  self.cache_clean_repl_dir.mkdir(parents=True, exist_ok=True)
@@ -490,7 +502,7 @@ class LeanREPLConfig:
490
502
 
491
503
  # Prepare the version-specific REPL checkout
492
504
  if not self._cache_repl_dir.exists():
493
- with FileLock(f"{self._cache_repl_dir}.lock", timeout=self._timeout):
505
+ with FileLock(f"{self._cache_repl_dir}.lock", timeout=self._timeout_lock):
494
506
  self._cache_repl_dir.mkdir(parents=True, exist_ok=True)
495
507
  shutil.copytree(self.cache_clean_repl_dir, self._cache_repl_dir, dirs_exist_ok=True)
496
508
 
@@ -107,6 +107,11 @@ class LeanServer:
107
107
  pass
108
108
  except Exception:
109
109
  pass
110
+ finally:
111
+ try:
112
+ self._proc.wait(timeout=1) # Ensure the process is properly reaped
113
+ except Exception:
114
+ pass
110
115
  if self._proc.stdin:
111
116
  self._proc.stdin.close()
112
117
  if self._proc.stdout:
@@ -377,7 +382,7 @@ class AutoLeanServer(LeanServer):
377
382
  and self._proc is not None
378
383
  and self.config.memory_hard_limit_mb is not None
379
384
  and self._max_process_memory is not None
380
- and get_total_memory_usage(psutil.Process())
385
+ and get_total_memory_usage(psutil.Process(self._proc.pid))
381
386
  >= self._max_process_memory * self.config.memory_hard_limit_mb * 1024**2
382
387
  ):
383
388
  self.kill()
@@ -188,7 +188,7 @@ class PickleSessionCache(BaseSessionCache):
188
188
  def clear(self, verbose: bool = False) -> None:
189
189
  for state_data in list(self):
190
190
  self.remove(session_state_id=state_data.session_id, verbose=verbose)
191
- assert not self._cache
191
+ assert not self._cache, f"Cache is not empty after clearing: {self._cache}"
192
192
 
193
193
  def __iter__(self) -> Iterator[PickleSessionState]:
194
194
  return iter(self._cache.values())
@@ -22,7 +22,7 @@ logger.addHandler(handler)
22
22
  ROOT_DIR = Path(__file__).resolve().parent
23
23
  DEFAULT_CACHE_DIR = ROOT_DIR / "cache"
24
24
  DEFAULT_REPL_GIT_URL = "https://github.com/augustepoiroux/repl"
25
- DEFAULT_REPL_VERSION = "v1.0.9"
25
+ DEFAULT_REPL_VERSION = "v1.0.10"
26
26
 
27
27
  os.makedirs(DEFAULT_CACHE_DIR, exist_ok=True)
28
28
 
@@ -35,6 +35,7 @@ from lean_interact.interface import (
35
35
  )
36
36
  from lean_interact.server import DEFAULT_TIMEOUT, AutoLeanServer, LeanServer
37
37
  from lean_interact.sessioncache import PickleSessionCache, PickleSessionState
38
+ from lean_interact.utils import get_total_memory_usage
38
39
 
39
40
 
40
41
  class TestLeanServer(unittest.TestCase):
@@ -155,7 +156,18 @@ lean_exe "dummy" where
155
156
  project = TemporaryProject(temp_content)
156
157
  config = LeanREPLConfig(lean_version="v4.14.0", project=project, verbose=True)
157
158
  server = AutoLeanServer(config=config)
158
- server.run(Command(cmd="#eval Lean.versionString"), verbose=True)
159
+ response = server.run(Command(cmd="#eval Lean.versionString"), verbose=True)
160
+ self.assertEqual(
161
+ response,
162
+ CommandResponse(
163
+ messages=[
164
+ Message(
165
+ start_pos=Pos(line=1, column=0), end_pos=Pos(line=1, column=5), severity="info", data='"4.14.0"'
166
+ )
167
+ ],
168
+ env=0,
169
+ ),
170
+ )
159
171
 
160
172
  def test_init_with_git_project(self):
161
173
  if platform.system() == "Windows":
@@ -164,7 +176,23 @@ lean_exe "dummy" where
164
176
  git_url = "https://github.com/yangky11/lean4-example"
165
177
  config = LeanREPLConfig(project=GitProject(git_url), verbose=True)
166
178
  server = AutoLeanServer(config=config)
167
- server.run(Command(cmd="#eval Lean.versionString"), verbose=True)
179
+ response = server.run(Command(cmd="#eval Lean.versionString"), verbose=True)
180
+ assert config.lean_version is not None, "Error: Lean version could not be determined from the project"
181
+ lean_version = config.lean_version[1:]
182
+ self.assertEqual(
183
+ response,
184
+ CommandResponse(
185
+ messages=[
186
+ Message(
187
+ start_pos=Pos(line=1, column=0),
188
+ end_pos=Pos(line=1, column=5),
189
+ severity="info",
190
+ data=f'"{lean_version}"',
191
+ )
192
+ ],
193
+ env=0,
194
+ ),
195
+ )
168
196
 
169
197
  def test_run_code_simple(self):
170
198
  server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
@@ -363,7 +391,7 @@ lean_exe "dummy" where
363
391
  server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
364
392
  # Prepare restart_persistent_session_cache
365
393
  assert isinstance(server._session_cache, PickleSessionCache)
366
- server._session_cache._cache[-1] = PickleSessionState(-2, 20, True, "")
394
+ server._session_cache._cache[-2] = PickleSessionState(-2, 20, True, "")
367
395
  with unittest.mock.patch.object(server, "_get_repl_state_id", return_value=20):
368
396
  mock_super.return_value = {"proofState": 20, "goals": [], "proofStatus": "Completed"}
369
397
  result = server.run(ProofStep(proof_state=-2, tactic="test"))
@@ -548,7 +576,7 @@ lean_exe "dummy" where
548
576
  # Get initial memory usage
549
577
  assert server._proc is not None
550
578
  server_process = psutil.Process(server._proc.pid)
551
- start_mem = server_process.memory_info().rss / (1024 * 1024) # Convert to MB
579
+ start_mem = get_total_memory_usage(server_process) / (1024 * 1024) # Convert to MB
552
580
 
553
581
  # Run code in separate thread to allow memory monitoring
554
582
  result_queue = Queue()
@@ -579,7 +607,7 @@ lean_exe "dummy" where
579
607
  max_mem = start_mem
580
608
  while thread.is_alive():
581
609
  try:
582
- current_mem = server_process.memory_info().rss / (1024 * 1024)
610
+ current_mem = get_total_memory_usage(server_process) / (1024 * 1024)
583
611
  max_mem = max(max_mem, current_mem)
584
612
  if current_mem > mem_limit:
585
613
  server.kill()
@@ -606,16 +634,17 @@ lean_exe "dummy" where
606
634
  env_id = result.env
607
635
 
608
636
  # Pickle the environment
609
- temp_pickle_file = tempfile.NamedTemporaryFile(suffix=".olean", delete=False).name
637
+ temp_pickle = tempfile.NamedTemporaryFile(suffix=".olean", delete=False)
638
+ temp_pickle.close() # Close the file to allow writing
610
639
 
611
- pickle_result = server.run(PickleEnvironment(env=env_id, pickle_to=temp_pickle_file), verbose=True)
640
+ pickle_result = server.run(PickleEnvironment(env=env_id, pickle_to=temp_pickle.name), verbose=True)
612
641
  self.assertIsInstance(pickle_result, CommandResponse)
613
642
 
614
643
  # Create a new server
615
644
  new_server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
616
645
 
617
646
  # Unpickle the environment in the new server
618
- unpickle_result = new_server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle_file), verbose=True)
647
+ unpickle_result = new_server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle.name), verbose=True)
619
648
  assert isinstance(unpickle_result, CommandResponse)
620
649
  unpickled_env_id = unpickle_result.env
621
650
 
@@ -640,7 +669,7 @@ lean_exe "dummy" where
640
669
 
641
670
  # delete the temp file
642
671
  try:
643
- os.remove(temp_pickle_file)
672
+ os.remove(temp_pickle.name)
644
673
  except (FileNotFoundError, PermissionError):
645
674
  pass
646
675
 
@@ -657,9 +686,11 @@ lean_exe "dummy" where
657
686
  assert isinstance(proof_state_id, int)
658
687
 
659
688
  # Pickle the proof state
660
- temp_pickle_file = tempfile.NamedTemporaryFile(suffix=".olean", delete=False).name
689
+ temp_pickle = tempfile.NamedTemporaryFile(suffix=".olean", delete=False)
690
+ temp_pickle.close() # Close the file to allow writing
691
+
661
692
  pickle_result = server.run(
662
- PickleProofState(proof_state=proof_state_id, pickle_to=temp_pickle_file), verbose=True
693
+ PickleProofState(proof_state=proof_state_id, pickle_to=temp_pickle.name), verbose=True
663
694
  )
664
695
  self.assertIsInstance(pickle_result, ProofStepResponse)
665
696
 
@@ -667,7 +698,7 @@ lean_exe "dummy" where
667
698
  new_server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
668
699
 
669
700
  # Unpickle the proof state in the new server
670
- unpickle_result = new_server.run(UnpickleProofState(unpickle_proof_state_from=temp_pickle_file), verbose=True)
701
+ unpickle_result = new_server.run(UnpickleProofState(unpickle_proof_state_from=temp_pickle.name), verbose=True)
671
702
  assert isinstance(unpickle_result, ProofStepResponse)
672
703
  unpickled_proof_state_id = unpickle_result.proof_state
673
704
 
@@ -677,7 +708,7 @@ lean_exe "dummy" where
677
708
 
678
709
  # Delete the temp file
679
710
  try:
680
- os.remove(temp_pickle_file)
711
+ os.remove(temp_pickle.name)
681
712
  except (FileNotFoundError, PermissionError):
682
713
  pass
683
714
 
@@ -685,28 +716,34 @@ lean_exe "dummy" where
685
716
  server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
686
717
 
687
718
  # Try to pickle a non-existent environment
688
- temp_pickle_file = tempfile.NamedTemporaryFile(suffix=".olean", delete=False).name
689
- result = server.run(PickleEnvironment(env=999, pickle_to=temp_pickle_file), verbose=True)
719
+ temp_pickle = tempfile.NamedTemporaryFile(suffix=".olean", delete=False)
720
+ temp_pickle.close() # Close the file to allow writing
721
+
722
+ result = server.run(PickleEnvironment(env=999, pickle_to=temp_pickle.name), verbose=True)
690
723
  assert isinstance(result, LeanError)
691
724
  self.assertEqual("unknown environment.", result.message.lower())
692
725
 
693
726
  # delete the temp file
694
- os.remove(temp_pickle_file)
727
+ try:
728
+ os.remove(temp_pickle.name)
729
+ except (FileNotFoundError, PermissionError):
730
+ pass
695
731
 
696
732
  def test_unpickle_fails_with_invalid_data(self):
697
733
  server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
698
734
  # Try to unpickle invalid data
699
- temp_pickle_file = tempfile.NamedTemporaryFile(suffix=".olean", delete=False).name
735
+ temp_pickle = tempfile.NamedTemporaryFile(suffix=".olean", delete=False)
736
+ temp_pickle.close() # Close the file to allow writing
700
737
 
701
738
  # Try to unpickle invalid data
702
739
  with self.assertRaises(ConnectionAbortedError):
703
- server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle_file), verbose=True)
740
+ server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle.name), verbose=True)
704
741
  with self.assertRaises(ConnectionAbortedError):
705
- server.run(UnpickleProofState(unpickle_proof_state_from=temp_pickle_file), verbose=True)
742
+ server.run(UnpickleProofState(unpickle_proof_state_from=temp_pickle.name), verbose=True)
706
743
 
707
744
  # delete the temp file
708
745
  try:
709
- os.remove(temp_pickle_file)
746
+ os.remove(temp_pickle.name)
710
747
  except (FileNotFoundError, PermissionError):
711
748
  pass
712
749
 
@@ -741,13 +778,15 @@ lean_exe "dummy" where
741
778
  )
742
779
 
743
780
  # Pickle the environment
744
- temp_pickle_file = tempfile.NamedTemporaryFile(suffix=".olean", delete=False).name
745
- pickle_result = server.run(PickleEnvironment(env=env_id, pickle_to=temp_pickle_file), verbose=True)
781
+ temp_pickle = tempfile.NamedTemporaryFile(suffix=".olean", delete=False)
782
+ temp_pickle.close() # Close the file to allow writing
783
+
784
+ pickle_result = server.run(PickleEnvironment(env=env_id, pickle_to=temp_pickle.name), verbose=True)
746
785
  self.assertIsInstance(pickle_result, CommandResponse)
747
786
 
748
787
  # Create a new server and unpickle
749
788
  new_server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
750
- unpickle_result = new_server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle_file), verbose=True)
789
+ unpickle_result = new_server.run(UnpickleEnvironment(unpickle_env_from=temp_pickle.name), verbose=True)
751
790
  assert isinstance(unpickle_result, CommandResponse)
752
791
  unpickled_env_id = unpickle_result.env
753
792
 
@@ -768,7 +807,7 @@ lean_exe "dummy" where
768
807
 
769
808
  # delete the temp file
770
809
  try:
771
- os.remove(temp_pickle_file)
810
+ os.remove(temp_pickle.name)
772
811
  except (FileNotFoundError, PermissionError):
773
812
  pass
774
813
 
@@ -540,7 +540,7 @@ wheels = [
540
540
 
541
541
  [[package]]
542
542
  name = "lean-interact"
543
- version = "0.6.0"
543
+ version = "0.6.2"
544
544
  source = { editable = "." }
545
545
  dependencies = [
546
546
  { name = "filelock" },
@@ -1,55 +0,0 @@
1
- name: Update Changelog
2
-
3
- on:
4
- release:
5
- types: [published]
6
-
7
- jobs:
8
- update-changelog:
9
- runs-on: ubuntu-latest
10
- permissions:
11
- contents: write
12
- steps:
13
- - name: Checkout code
14
- uses: actions/checkout@v4
15
-
16
- - name: Set up Python
17
- uses: actions/setup-python@v5
18
- with:
19
- python-version: '3.10'
20
-
21
- - name: Update Changelog
22
- run: |
23
- # Get the release information
24
- RELEASE_TAG="${{ github.event.release.tag_name }}"
25
- RELEASE_NAME="${{ github.event.release.name }}"
26
- RELEASE_BODY="${{ github.event.release.body }}"
27
- RELEASE_DATE=$(date +"%B %d, %Y")
28
-
29
- # Prepare the content to insert
30
- CHANGELOG_ENTRY="## ${RELEASE_NAME} (${RELEASE_DATE})\n\n${RELEASE_BODY}\n\n"
31
-
32
- # Check if changelog file exists
33
- if [ ! -f "docs/changelog.md" ]; then
34
- echo "# Changelog\n\nThis page documents the notable changes to LeanInteract.\n\n" > docs/changelog.md
35
- fi
36
-
37
- # Insert the new release notes after the header
38
- awk -v entry="${CHANGELOG_ENTRY}" '
39
- /^# Changelog/ {
40
- print;
41
- getline;
42
- print;
43
- print entry;
44
- next;
45
- }
46
- { print }
47
- ' docs/changelog.md > changelog.tmp && mv changelog.tmp docs/changelog.md
48
-
49
- - name: Commit and push changelog
50
- run: |
51
- git config --local user.email "action@github.com"
52
- git config --local user.name "GitHub Action"
53
- git add docs/changelog.md
54
- git commit -m "Update changelog with ${{ github.event.release.tag_name }} release notes"
55
- git push origin ${GITHUB_REF_NAME}
@@ -1,65 +0,0 @@
1
- # Changelog
2
-
3
- This page documents the notable changes to LeanInteract.
4
-
5
- ## v0.5.3 (May 18, 2025)
6
-
7
- ## What's Changed
8
- * Add optional build boolean for LocalProject by @sorgfresser in https://github.com/augustepoiroux/LeanInteract/pull/16
9
- * Slightly improve sorry detection in `lean_code_is_valid` by checking `message` instead of just `sorries` in REPL output.
10
-
11
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.5.2...v0.5.3
12
-
13
- ## v0.5.2 (May 01, 2025)
14
-
15
- Introduce compatibility with Lean v4.19.0
16
-
17
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.5.1...v0.5.2
18
-
19
- ## v0.5.1 (April 30, 2025)
20
-
21
- ## What's Changed
22
- * Add fix for non-respected timeout by @augustepoiroux in https://github.com/augustepoiroux/LeanInteract/pull/13
23
- * Query Lake cache for all Project types by @habemus-papadum in https://github.com/augustepoiroux/LeanInteract/pull/10
24
- * Bump REPL version to v1.0.7 fixing `"auxiliary declaration cannot be created when declaration name is not available"` in tactic mode for Lean <= v4.18.0 https://github.com/leanprover-community/repl/issues/44#issuecomment-2814069261
25
-
26
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.5.0...v0.5.1
27
-
28
- ## v0.5.0 (April 21, 2025)
29
-
30
- ## What's Changed
31
- * Make LeanInteract cross-platform by @augustepoiroux in https://github.com/augustepoiroux/LeanInteract/pull/4
32
- * Fix infotree parsing issue by @sorgfresser in https://github.com/augustepoiroux/LeanInteract/pull/1
33
- * Implement `async_run` + make calls to the REPL thread-safe by @augustepoiroux in https://github.com/augustepoiroux/LeanInteract/pull/2
34
-
35
- ## v0.4.1 (April 18, 2025)
36
-
37
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.4.0...v0.4.1
38
-
39
- ## v0.4.0 (April 11, 2025)
40
-
41
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.3.3...v0.4.0
42
-
43
- ## v0.3.3 (April 04, 2025)
44
-
45
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.3.2...v0.3.3
46
-
47
- ## v0.3.2 (April 03, 2025)
48
-
49
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.3.1...v0.3.2
50
-
51
- ## v0.3.1 (April 02, 2025)
52
-
53
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.3.0...v0.3.1
54
-
55
- ## v0.3.0 (April 02, 2025)
56
-
57
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/compare/v0.2.0...v0.3.0
58
-
59
- ## v0.2.0 (March 18, 2025)
60
-
61
- **Full Changelog**: https://github.com/augustepoiroux/LeanInteract/commits/v0.2.0
62
-
63
- ## Pre-release Development
64
-
65
- For development history prior to the first release, please see the [GitHub commit history](https://github.com/augustepoiroux/LeanInteract/commits/main).
File without changes
File without changes
File without changes