|
5 | 5 | import asyncio |
6 | 6 | import contextlib |
7 | 7 | import json |
8 | | -from pathlib import Path |
9 | 8 | import shutil |
10 | 9 | import time |
11 | 10 | from typing import TYPE_CHECKING, Any, Self |
12 | 11 |
|
13 | 12 | from anyenv.processes import create_process |
| 13 | +from upathtools.filesystems.sandbox_filesystems.pyodide_fs import PyodideFS, build_command |
14 | 14 |
|
15 | 15 | from exxec.base import ExecutionEnvironment |
16 | 16 | from exxec.events import OutputEvent, ProcessCompletedEvent, ProcessErrorEvent, ProcessStartedEvent |
17 | 17 | from exxec.models import ExecutionResult |
18 | | -from exxec.pyodide_provider.filesystem import PyodideFS |
19 | 18 |
|
20 | 19 |
|
21 | 20 | if TYPE_CHECKING: |
22 | 21 | from collections.abc import AsyncIterator |
23 | 22 | from contextlib import AbstractAsyncContextManager |
24 | 23 | from types import TracebackType |
25 | 24 |
|
| 25 | + from upathtools.filesystems.sandbox_filesystems.pyodide_fs import PyodideMethod |
| 26 | + |
26 | 27 | from exxec.events import ExecutionEvent |
27 | 28 | from exxec.models import ServerInfo |
28 | | - from exxec.pyodide_provider.filesystem import PyodideMethod |
29 | | - |
30 | | - |
31 | | -# Path to the TypeScript server relative to this file |
32 | | -SERVER_SCRIPT = Path(__file__).parent / "pyodide_server.ts" |
33 | | - |
34 | | - |
35 | | -def _build_permission_flag(flag: str, value: bool | list[str]) -> str | None: |
36 | | - """Build a Deno permission flag string.""" |
37 | | - if value is True: |
38 | | - return flag |
39 | | - if isinstance(value, list) and value: |
40 | | - return f"{flag}={','.join(value)}" |
41 | | - return None |
42 | 29 |
|
43 | 30 |
|
44 | 31 | class PyodideExecutionEnvironment(ExecutionEnvironment): |
@@ -118,46 +105,23 @@ def __init__( |
118 | 105 | # Pyodide emulates a Linux-like environment |
119 | 106 | self._os_type = "Linux" |
120 | 107 |
|
121 | | - def _build_command(self) -> list[str]: |
122 | | - """Build the Deno command with permissions.""" |
123 | | - if not self.deno_executable: |
124 | | - msg = "Deno executable not found. Install from https://deno.land" |
125 | | - raise RuntimeError(msg) |
126 | | - |
127 | | - cmd = [self.deno_executable, "run"] |
128 | | - |
129 | | - # Build permission flags |
130 | | - permission_defs = [ |
131 | | - ("--allow-net", self.allow_net), |
132 | | - ("--allow-read", self.allow_read), |
133 | | - ("--allow-write", self.allow_write), |
134 | | - ("--allow-env", self.allow_env), |
135 | | - ("--allow-run", self.allow_run), |
136 | | - ("--allow-ffi", self.allow_ffi), |
137 | | - ] |
138 | | - |
139 | | - for flag, value in permission_defs: |
140 | | - perm = _build_permission_flag(flag, value) |
141 | | - if perm: |
142 | | - cmd.append(perm) |
143 | | - |
144 | | - # Always need read for node_modules (Pyodide downloads) |
145 | | - if not self.allow_read: |
146 | | - cmd.append("--allow-read=node_modules") |
147 | | - if not self.allow_write: |
148 | | - cmd.append("--allow-write=node_modules") |
149 | | - |
150 | | - cmd.append("--node-modules-dir=auto") |
151 | | - cmd.append(str(SERVER_SCRIPT)) |
152 | | - |
153 | | - return cmd |
154 | | - |
155 | 108 | async def __aenter__(self) -> Self: |
156 | 109 | """Start the Deno/Pyodide server process.""" |
157 | 110 | import anyenv |
158 | 111 |
|
159 | 112 | await super().__aenter__() |
160 | | - cmd = self._build_command() |
| 113 | + if not self.deno_executable: |
| 114 | + msg = "Deno executable not found. Install from https://deno.land" |
| 115 | + raise RuntimeError(msg) |
| 116 | + cmd = build_command( |
| 117 | + self.deno_executable, |
| 118 | + allow_net=self.allow_net, |
| 119 | + allow_read=self.allow_read, |
| 120 | + allow_write=self.allow_write, |
| 121 | + allow_env=self.allow_env, |
| 122 | + allow_run=self.allow_run, |
| 123 | + allow_ffi=self.allow_ffi, |
| 124 | + ) |
161 | 125 | self._process = await create_process(*cmd, stdin="pipe", stdout="pipe", stderr="pipe") |
162 | 126 | # Wait for ready signal |
163 | 127 | try: |
@@ -316,10 +280,8 @@ async def stream_code(self, code: str) -> AsyncIterator[ExecutionEvent]: |
316 | 280 |
|
317 | 281 | match event_type: |
318 | 282 | case "started": |
319 | | - yield ProcessStartedEvent( |
320 | | - process_id=pid, |
321 | | - command=f"execute({len(code)} chars)", |
322 | | - ) |
| 283 | + cmd = f"execute({len(code)} chars)" |
| 284 | + yield ProcessStartedEvent(process_id=pid, command=cmd) |
323 | 285 | case "output": |
324 | 286 | yield OutputEvent( |
325 | 287 | process_id=pid, |
@@ -390,12 +352,9 @@ async def stream_command( |
390 | 352 | # Delegate to execute_command since streaming shell commands |
391 | 353 | # is not really supported in Pyodide |
392 | 354 | process_id = f"pyodide_cmd_{self._request_id + 1}" |
393 | | - |
394 | 355 | yield ProcessStartedEvent(process_id=process_id, command=command) |
395 | | - |
396 | 356 | try: |
397 | 357 | result = await self.execute_command(command, timeout=timeout) |
398 | | - |
399 | 358 | if result.stdout: |
400 | 359 | yield OutputEvent(process_id=process_id, data=result.stdout, stream="stdout") |
401 | 360 | if result.stderr: |
|
0 commit comments