Skip to content

Commit 229bba1

Browse files
authored
Merge pull request #255 from pathsim/feature/remove-venv-isolation
Remove venv isolation, use host Python for workers and pin pathsim deps at build time
2 parents 8d7b9dd + 3cddf70 commit 229bba1

9 files changed

Lines changed: 69 additions & 47 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pathview_server/ # Python package (pip install pathview)
9898
├── cli.py # CLI entry point (pathview serve)
9999
└── static/ # Bundled frontend (generated at build time)
100100
101+
101102
scripts/
102103
├── config/ # Configuration files for extraction
103104
│ ├── schemas/ # JSON schemas for validation
@@ -496,8 +497,9 @@ npm run dev # Starts Vite dev server (separate terminal)
496497

497498
**Key properties:**
498499
- **Process isolation** — each session gets its own Python subprocess
500+
- **Host environment** — workers run with the same Python used to install pathview, so all packages in the user's environment are available in the code editor
499501
- **Namespace persistence** — variables persist across exec/eval calls within a session
500-
- **Dynamic packages** — packages from `PYTHON_PACKAGES` (the same config used by Pyodide) are pip-installed on first init
502+
- **Dynamic packages** — packages from `PYTHON_PACKAGES` (the same config used by Pyodide) are pip-installed on first init if not already present
501503
- **Session TTL** — stale sessions cleaned up after 1 hour of inactivity
502504
- **Streaming** — simulations stream via SSE, with the same code injection support as Pyodide
503505

pathview_server/app.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
from flask import Flask, request, jsonify, send_from_directory
2222
from flask_cors import CORS
2323

24-
from pathview_server.venv import get_venv_python
25-
2624
# ---------------------------------------------------------------------------
2725
# Configuration
2826
# ---------------------------------------------------------------------------
@@ -44,7 +42,7 @@ def __init__(self, session_id: str):
4442
self.last_active = time.time()
4543
self.lock = threading.Lock()
4644
self.process = subprocess.Popen(
47-
[get_venv_python(), "-u", WORKER_SCRIPT],
45+
[sys.executable, "-u", WORKER_SCRIPT],
4846
stdin=subprocess.PIPE,
4947
stdout=subprocess.PIPE,
5048
stderr=subprocess.PIPE,

pathview_server/cli.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import webbrowser
88

99
from pathview_server import __version__
10-
from pathview_server.venv import VENV_DIR, ensure_venv
1110

1211

1312
def main():
@@ -30,8 +29,6 @@ def main():
3029

3130
args = parser.parse_args()
3231

33-
ensure_venv()
34-
3532
from pathview_server.app import create_app
3633

3734
app = create_app(serve_static=not args.debug)
@@ -52,7 +49,7 @@ def open_browser_when_ready():
5249
threading.Thread(target=open_browser_when_ready, daemon=True).start()
5350

5451
print(f"PathView v{__version__}")
55-
print(f" Python venv: {VENV_DIR}")
52+
print(f" Python: {sys.executable}")
5653
print(f"Running at http://{args.host}:{args.port}")
5754

5855
if args.host == "0.0.0.0":

pathview_server/venv.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

pathview_server/worker.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ def initialize(packages: list[dict] | None = None) -> None:
160160
send({"type": "stderr", "value": f"Optional package {pkg.get('import', '?')} failed: {e}\n"})
161161

162162
# Import numpy AFTER packages are installed (numpy comes with pathsim).
163-
# In a fresh venv without simulation packages, numpy won't be available.
164163
try:
165164
exec("import numpy as np", _namespace)
166165
except Exception:

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ dependencies = [
2020
"flask-cors>=4.0",
2121
"numpy",
2222
"waitress>=3.0",
23+
"pathsim",
24+
"pathsim-chem==0.2rc3",
2325
]
2426

2527
[project.optional-dependencies]

scripts/extract.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,10 @@ def extract(self) -> dict:
694694
class DependencyExtractor:
695695
"""Extract and generate dependency configuration."""
696696

697+
# Packages managed by the extraction script in pyproject.toml.
698+
# These are added/updated automatically; all other dependencies are left untouched.
699+
MANAGED_PACKAGE_NAMES = {"pathsim", "pathsim-chem"}
700+
697701
def __init__(self, config_loader: ConfigLoader):
698702
self.config = config_loader
699703

@@ -735,6 +739,59 @@ def extract(self) -> dict:
735739
"extracted_versions": extracted_versions
736740
}
737741

742+
def update_pyproject(self, packages: list[dict], project_root: Path) -> None:
743+
"""Update pyproject.toml dependencies with pinned package versions.
744+
745+
Adds/updates entries for packages from requirements-pyodide.txt
746+
while leaving all other dependencies (flask, numpy, etc.) untouched.
747+
"""
748+
pyproject_path = project_root / "pyproject.toml"
749+
if not pyproject_path.exists():
750+
print(" Warning: pyproject.toml not found, skipping")
751+
return
752+
753+
text = pyproject_path.read_text(encoding="utf-8")
754+
755+
# Build pip specs for managed packages (e.g. "pathsim==0.17.0")
756+
new_specs: list[str] = []
757+
for pkg in packages:
758+
base_name = pkg["pip"].split(">=")[0].split("==")[0].split("<=")[0].split("<")[0].split(">")[0]
759+
if base_name in self.MANAGED_PACKAGE_NAMES:
760+
new_specs.append(pkg["pip"])
761+
762+
# Parse the existing dependencies list
763+
pattern = re.compile(
764+
r'(dependencies\s*=\s*\[)(.*?)(\])',
765+
re.DOTALL,
766+
)
767+
match = pattern.search(text)
768+
if not match:
769+
print(" Warning: Could not find dependencies in pyproject.toml, skipping")
770+
return
771+
772+
existing_block = match.group(2)
773+
774+
# Keep non-managed dependencies
775+
kept: list[str] = []
776+
for line in existing_block.strip().splitlines():
777+
dep = line.strip().strip(",").strip('"').strip("'")
778+
if not dep:
779+
continue
780+
dep_name = re.split(r'[><=~!\[]', dep)[0].strip()
781+
if dep_name not in self.MANAGED_PACKAGE_NAMES:
782+
kept.append(dep)
783+
784+
# Append managed packages
785+
all_deps = kept + new_specs
786+
787+
# Rebuild the dependencies block
788+
dep_lines = ",\n".join(f' "{d}"' for d in all_deps)
789+
new_block = f"dependencies = [\n{dep_lines},\n]"
790+
text = text[:match.start()] + new_block + text[match.end():]
791+
792+
pyproject_path.write_text(text, encoding="utf-8")
793+
print(f" Updated pyproject.toml dependencies: {', '.join(new_specs)}")
794+
738795

739796
# =============================================================================
740797
# TypeScript Generation
@@ -1009,8 +1066,11 @@ def main():
10091066

10101067
if extract_all or args.deps:
10111068
print("\nExtracting dependencies...")
1012-
deps = DependencyExtractor(config).extract()
1069+
dep_extractor = DependencyExtractor(config)
1070+
deps = dep_extractor.extract()
10131071
generator.write_dependencies(deps)
1072+
project_root = Path(__file__).parent.parent
1073+
dep_extractor.update_pyproject(deps["packages"], project_root)
10141074

10151075
# Track extracted data for registry generation
10161076
blocks = None

src/lib/constants/dependencies.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Auto-generated by scripts/extract.py - DO NOT EDIT
22
// Source: scripts/config/requirements-pyodide.txt, scripts/config/pyodide.json
33

4-
export const PATHVIEW_VERSION = '0.6.1';
4+
export const PATHVIEW_VERSION = '0.7.0';
55

66
export const PYODIDE_VERSION = '0.26.2';
77
export const PYODIDE_CDN_URL = `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/pyodide.mjs`;

tests/conftest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
import pytest
44

55
from pathview_server.app import create_app, _sessions, _sessions_lock
6-
from pathview_server.venv import ensure_venv
76

87

98
@pytest.fixture()
109
def app():
1110
"""Create a Flask test app (API-only, no static serving)."""
12-
ensure_venv()
1311
application = create_app(serve_static=False)
1412
application.config["TESTING"] = True
1513
yield application

0 commit comments

Comments
 (0)