Skip to content

Commit 0225b84

Browse files
WIP
1 parent 2c5de00 commit 0225b84

File tree

6 files changed

+449
-21
lines changed

6 files changed

+449
-21
lines changed

src/deno_sandbox/errors.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ class UnknownRpcMethod(Exception):
1919
pass
2020

2121

22+
class ProcessAlreadyExited(Exception):
23+
"""Raised when trying to interact with a process that has already exited."""
24+
25+
pass
26+
27+
28+
class HTTPStatusError(Exception):
29+
"""Raised when an HTTP request returns a non-success status code."""
30+
31+
def __init__(self, status_code: int, message: str) -> None:
32+
self.status_code = status_code
33+
self.message = message
34+
super().__init__(f"HTTP Status {status_code}: {message}")
35+
36+
2237
class ZodErrorRaw(TypedDict):
2338
expected: str
2439
code: str

src/deno_sandbox/rpc.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from websockets import ConnectionClosed
66

77
from deno_sandbox.bridge import AsyncBridge
8-
from deno_sandbox.errors import RpcValidationError, UnknownRpcMethod, ZodErrorRaw
8+
from deno_sandbox.errors import (
9+
ProcessAlreadyExited,
10+
RpcValidationError,
11+
UnknownRpcMethod,
12+
ZodErrorRaw,
13+
)
914
from deno_sandbox.transport import WebSocketTransport
1015
from deno_sandbox.utils import (
1116
convert_to_camel_case,
@@ -41,6 +46,11 @@ def __init__(self, transport: WebSocketTransport):
4146
self._listen_task: asyncio.Task[Any] | None = None
4247
self._pending_processes: Dict[int, asyncio.StreamReader] = {}
4348
self._loop: asyncio.AbstractEventLoop | None = None
49+
self._signal_id = 0
50+
51+
def get_next_signal_id(self) -> int:
52+
self._signal_id += 1
53+
return self._signal_id
4454

4555
async def close(self):
4656
await self._transport.close()
@@ -89,7 +99,16 @@ async def call(self, method: str, params: Dict[str, Any]) -> Any:
8999
raise Exception(response["error"])
90100

91101
if response.get("result") and response["result"].get("error"):
92-
raise Exception(f"Application Error: {response['result']['error']}")
102+
err = response["result"]["error"]
103+
if (
104+
"constructor_name" in err
105+
and err["constructor_name"] == "TypeError"
106+
and "code" in err
107+
and err["code"] == "ENOENT"
108+
):
109+
raise ProcessAlreadyExited("Process has already exited")
110+
111+
raise Exception(f"Application Error: {err}")
93112
return response["result"]["ok"] if response.get("result") else None
94113

95114
async def _listener(self) -> None:
@@ -134,6 +153,9 @@ def __init__(self, async_client: AsyncRpcClient, bridge: AsyncBridge):
134153
self._async_client = async_client
135154
self._bridge = bridge
136155

156+
def get_next_signal_id(self) -> int:
157+
return self._async_client.get_next_signal_id()
158+
137159
def call(self, method: str, params: Dict[str, Any]) -> Any:
138160
return self._bridge.run(self._async_client.call(method, params))
139161

src/deno_sandbox/sandbox.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from deno_sandbox.api_types_generated import (
2222
DenoReplOptions,
23+
DenoRunOptions,
2324
SandboxListOptions,
2425
SandboxCreateOptions,
2526
SandboxConnectOptions,
@@ -35,8 +36,11 @@
3536
from deno_sandbox.utils import to_camel_case, to_snake_case
3637
from deno_sandbox.wrappers import (
3738
AsyncChildProcess,
39+
AsyncDenoProcess,
3840
AsyncDenoRepl,
3941
ChildProcess,
42+
DenoProcess,
43+
DenoRepl,
4044
ProcessSpawnResult,
4145
RemoteProcessOptions,
4246
)
@@ -212,9 +216,41 @@ class VsCodeOptions(TypedDict):
212216

213217

214218
class AsyncSandboxDeno(AsyncSandboxDenoGenerated):
219+
async def run(self, options: DenoRunOptions) -> AsyncDenoProcess:
220+
"""Create a new Deno process from the specified entrypoint file or code. The runtime will execute the given code to completion, and then exit."""
221+
222+
params = {
223+
"stdout": "inherit",
224+
"stderr": "inherit",
225+
}
226+
227+
if options is not None:
228+
for key, value in options.items():
229+
if value is not None:
230+
params[to_snake_case(key)] = value
231+
232+
if "code" in params and "extension" not in params:
233+
params["extension"] = "ts"
234+
235+
opts = RemoteProcessOptions(
236+
stdout_inherit=params["stdout"] == "inherit",
237+
stderr_inherit=params["stderr"] == "inherit",
238+
)
239+
240+
if params["stdout"] == "inherit":
241+
params["stdout"] = "piped"
242+
if params["stderr"] == "inherit":
243+
params["stderr"] = "piped"
244+
245+
result = await self._rpc.call("spawnDeno", params)
246+
247+
return await AsyncDenoProcess.create(result, self._rpc, opts)
248+
215249
async def eval(self, code: str) -> Any:
216250
repl = await self.repl()
217-
return await repl.eval(code)
251+
result = await repl.eval(code)
252+
await repl.close()
253+
return result
218254

219255
async def repl(self, options: Optional[DenoReplOptions] = None) -> AsyncDenoRepl:
220256
params = {"stdout": "piped", "stderr": "piped"}
@@ -243,9 +279,17 @@ def __init__(self, client: ConsoleClient, rpc: RpcClient):
243279

244280
self._async = AsyncSandboxDeno(self._client._async, rpc._async_client)
245281

282+
def run(self, options: DenoRunOptions) -> DenoProcess:
283+
async_deno = self._client._bridge.run(self._async.run(options))
284+
return DenoProcess(self._rpc, async_deno)
285+
246286
def eval(self, code: str) -> Any:
247287
return self._client._bridge.run(self._async.eval(code))
248288

289+
def repl(self, options: Optional[DenoReplOptions] = None) -> DenoRepl:
290+
async_repl = self._client._bridge.run(self._async.repl(options))
291+
return DenoRepl(self._rpc, async_repl)
292+
249293

250294
class AsyncSandbox:
251295
def __init__(

0 commit comments

Comments
 (0)