Skip to content

Commit 5ac2bb2

Browse files
feat: support sync sandbox client (#9)
1 parent fe9c65b commit 5ac2bb2

File tree

4 files changed

+406
-5
lines changed

4 files changed

+406
-5
lines changed

src/deno_sandbox/bridge.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import asyncio
2+
import threading
3+
4+
5+
class AsyncBridge:
6+
def __init__(self):
7+
self.loop = asyncio.new_event_loop()
8+
self.thread = threading.Thread(target=self._run_loop, daemon=True)
9+
self.thread.start()
10+
11+
def _run_loop(self):
12+
asyncio.set_event_loop(self.loop)
13+
self.loop.run_forever()
14+
15+
def run(self, coro):
16+
"""Submit a coroutine to the loop and wait for the result."""
17+
return asyncio.run_coroutine_threadsafe(coro, self.loop).result()
18+
19+
def stop(self):
20+
self.loop.call_soon_threadsafe(self.loop.stop)
21+
self.thread.join()

src/deno_sandbox/rpc.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pydantic import BaseModel
66
from websockets import ConnectionClosed
77

8+
from deno_sandbox.bridge import AsyncBridge
89
from deno_sandbox.transport import Transport
910

1011

@@ -95,3 +96,15 @@ async def _listener(self) -> None:
9596
for future in self._pending_requests.values():
9697
if not future.done():
9798
future.set_exception(e)
99+
100+
101+
class RpcClient:
102+
def __init__(self, async_client: AsyncRpcClient, bridge: AsyncBridge):
103+
self._async_client = async_client
104+
self._bridge = bridge
105+
106+
def call(self, method: str, params: Dict[str, Any]) -> Any:
107+
return self._bridge.run(self._async_client.call(method, params))
108+
109+
def close(self):
110+
self._bridge.run(self._async_client.close())

src/deno_sandbox/sandbox.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import asyncio
2-
from contextlib import asynccontextmanager
2+
from contextlib import asynccontextmanager, contextmanager
33
from dataclasses import asdict, dataclass, field
44
import json
55
from typing import AsyncIterator
66
import uuid
77
from pydantic import BaseModel, Field
88
from typing_extensions import Literal
99

10+
from deno_sandbox.bridge import AsyncBridge
1011
from deno_sandbox.options import Options, get_internal_options
11-
from deno_sandbox.rpc import AsyncRpcClient
12+
from deno_sandbox.rpc import AsyncRpcClient, RpcClient
1213
from deno_sandbox.sandbox_generated import (
1314
AsyncSandboxHandle as GeneratedAsyncSandboxHandle,
1415
SpawnArgs,
1516
AsyncSandboxProcess as GeneratedAsyncSandboxProcess,
17+
SandboxHandle,
1618
)
1719
from deno_sandbox.transport import (
1820
Transport,
@@ -80,6 +82,27 @@ class AppConfig:
8082
memory_mb: int | None
8183

8284

85+
class Sandbox:
86+
def __init__(self, options=None):
87+
self._bridge = AsyncBridge()
88+
self._async_sandbox = AsyncSandbox(options)
89+
90+
@contextmanager
91+
def create(self, options=None):
92+
async_cm = self._async_sandbox.create(options)
93+
async_handle = self._bridge.run(async_cm.__aenter__())
94+
95+
rpc = RpcClient(async_handle._rpc, self._bridge)
96+
97+
try:
98+
yield SandboxHandle(rpc, async_handle.id)
99+
finally:
100+
self._bridge.run(async_cm.__aexit__(None, None, None))
101+
102+
def close(self):
103+
self._bridge.stop()
104+
105+
83106
class AsyncSandboxProcess(GeneratedAsyncSandboxProcess):
84107
async def spawn(self, args: SpawnArgs) -> RemoteProcess:
85108
result: SpawnResult = await super().spawn(args)
@@ -99,6 +122,7 @@ def __init__(
99122
):
100123
self.__options = get_internal_options(options or Options())
101124
self._transport_factory = WebSocketTransportFactory()
125+
self._rpc: AsyncRpcClient | None = None
102126

103127
async def _init_transport(self, app_config: AppConfig) -> Transport:
104128
transport = self._transport_factory.create_transport()
@@ -128,8 +152,8 @@ async def create(
128152
transport = await self._init_transport(app_config)
129153

130154
try:
131-
rpc = AsyncRpcClient(transport)
132-
yield AsyncSandboxHandle(rpc, sandbox_id)
155+
self._rpc = AsyncRpcClient(transport)
156+
yield AsyncSandboxHandle(self._rpc, sandbox_id)
133157
finally:
134158
await transport.close()
135159

0 commit comments

Comments
 (0)