Skip to content

Commit 005d82f

Browse files
WIP
1 parent e2c805a commit 005d82f

File tree

10 files changed

+527
-393
lines changed

10 files changed

+527
-393
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ dev = [
2727
]
2828

2929
[tool.pytest.ini_options]
30+
# asyncio_debug = true
31+
asyncio_mode = "auto"
32+
asyncio_default_fixture_loop_scope = "session"
3033
timeout = 10

src/deno_sandbox/api_generated.py

Lines changed: 251 additions & 226 deletions
Large diffs are not rendered by default.

src/deno_sandbox/api_types_generated.py

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class TimelineListOptions:
106106

107107
@dataclass_json(letter_case=LetterCase.CAMEL)
108108
@dataclass
109-
class SandboxConnectOptions:
109+
class SandboxCreateOptions:
110110
region: str | None = None
111111
env: dict[str, str] | None = None
112112
timeout: str | None = None
@@ -121,6 +121,17 @@ class SandboxConnectOptions:
121121
org: str | None = None
122122

123123

124+
@dataclass_json(letter_case=LetterCase.CAMEL)
125+
@dataclass
126+
class SandboxConnectOptions:
127+
id: str
128+
region: str | None = None
129+
debug: bool | None = None
130+
ssh: bool | None = None
131+
token: str | None = None
132+
org: str | None = None
133+
134+
124135
@dataclass_json(letter_case=LetterCase.CAMEL)
125136
@dataclass
126137
class SandboxListOptions:
@@ -186,38 +197,22 @@ class WriteFileOptions:
186197

187198
@dataclass_json(letter_case=LetterCase.CAMEL)
188199
@dataclass
189-
class WriteFileWithContent:
190-
path: str
191-
content: str
192-
abort_id: int | None = None
193-
options: WriteFileOptions | None = None
194-
195-
196-
@dataclass_json(letter_case=LetterCase.CAMEL)
197-
@dataclass
198-
class WriteFileWithStream:
199-
path: str
200-
content_stream_id: int
201-
abort_id: int | None = None
202-
options: WriteFileOptions | None = None
203-
204-
205-
@dataclass_json(letter_case=LetterCase.CAMEL)
206-
@dataclass
207-
class WriteTextFileWithContent:
200+
class WriteFileArgs:
208201
path: str
209-
content: str
210202
abort_id: int | None = None
211203
options: WriteFileOptions | None = None
204+
content: str | None = None
205+
content_stream_id: int | None = None
212206

213207

214208
@dataclass_json(letter_case=LetterCase.CAMEL)
215209
@dataclass
216-
class WriteTextFileWithStream:
210+
class WriteTextFileArgs:
217211
path: str
218-
content_stream_id: int
219212
abort_id: int | None = None
220213
options: WriteFileOptions | None = None
214+
content: str | None = None
215+
content_stream_id: int | None = None
221216

222217

223218
@dataclass_json(letter_case=LetterCase.CAMEL)
@@ -620,20 +615,7 @@ class KillArgs:
620615

621616
@dataclass_json(letter_case=LetterCase.CAMEL)
622617
@dataclass
623-
class SpawnDenoByEntrypoint:
624-
entrypoint: str
625-
stdout: Literal["piped", "null"] = "piped"
626-
stderr: Literal["piped", "null"] = "piped"
627-
env: dict[str, str] | None = None
628-
clear_env: bool | None = None
629-
cwd: str | None = None
630-
stdin_stream_id: int | None = None
631-
script_args: list[str] | None = None
632-
633-
634-
@dataclass_json(letter_case=LetterCase.CAMEL)
635-
@dataclass
636-
class SpawnDenoByCode:
618+
class RunArgs:
637619
code: str
638620
extension: Literal["js", "cjs", "mjs", "ts", "cts", "mts", "jsx", "tsx"]
639621
stdout: Literal["piped", "null"] = "piped"
@@ -643,6 +625,7 @@ class SpawnDenoByCode:
643625
cwd: str | None = None
644626
stdin_stream_id: int | None = None
645627
script_args: list[str] | None = None
628+
entrypoint: str | None = None
646629

647630

648631
@dataclass_json(letter_case=LetterCase.CAMEL)
@@ -723,16 +706,10 @@ class NetFetchResult:
723706

724707
@dataclass_json(letter_case=LetterCase.CAMEL)
725708
@dataclass
726-
class ExposeHttpByPort:
709+
class ExposeHttpArgs:
727710
domain: str
728-
port: int
729-
730-
731-
@dataclass_json(letter_case=LetterCase.CAMEL)
732-
@dataclass
733-
class ExposeHttpByPid:
734-
domain: str
735-
pid: int
711+
port: int | None = None
712+
pid: int | None = None
736713

737714

738715
@dataclass_json(letter_case=LetterCase.CAMEL)

src/deno_sandbox/api_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from dataclasses import is_dataclass
2+
from typing import Any
13
from deno_sandbox.api_types_generated import PaginatedList
24

35

@@ -8,3 +10,10 @@ def convert_paginated_list_response(response, item_converter):
810
total_count=response.total_count,
911
next_page_token=response.next_page_token,
1012
)
13+
14+
15+
def convert_dict_to_typed(data: dict[str, Any] | None, type_converter) -> Any:
16+
if data is not None and not is_dataclass(data):
17+
return type_converter(**data)
18+
19+
return data

src/deno_sandbox/console.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ class AsyncConsoleClient:
99
def __init__(self, options: Options):
1010
self._options = get_internal_options(options or Options())
1111

12-
self.client = httpx.AsyncClient(
13-
headers={
14-
"Content-Type": "application/json",
15-
"Authorization": f"Bearer {self._options.token}",
16-
}
17-
)
12+
@property
13+
def client(self) -> httpx.AsyncClient:
14+
if self._client is None:
15+
self._client = httpx.AsyncClient(
16+
headers={
17+
"Content-Type": "application/json",
18+
"Authorization": f"Bearer {self._options.token}",
19+
}
20+
)
21+
return self._client
1822

1923
async def post(self, path: str, data: any) -> dict:
2024
req_url = HttpUrl(self._options.console_url, path=path)
@@ -47,7 +51,6 @@ class ConsoleClient:
4751
def __init__(self, options: Options):
4852
self._options = get_internal_options(options or Options())
4953

50-
print(self._options)
5154
self.client = httpx.Client(
5255
headers={
5356
"Content-Type": "application/json",

src/deno_sandbox/rpc.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,69 @@
11
import asyncio
22
import base64
3+
from dataclasses import dataclass
34
import json
4-
from typing import Any, Dict, Optional
5-
from pydantic import BaseModel
5+
from typing import Any, Dict
6+
from dataclasses_json import dataclass_json
67
from websockets import ConnectionClosed
78

89
from deno_sandbox.bridge import AsyncBridge
9-
from deno_sandbox.transport import Transport
10+
from deno_sandbox.transport import WebSocketTransport
1011

1112

12-
class RpcRequest(BaseModel):
13-
method: str
14-
params: Dict[str, Any]
13+
@dataclass_json
14+
@dataclass
15+
class RpcRequest:
1516
id: int
17+
method: str
18+
params: dict[str, Any]
1619
jsonrpc: str = "2.0"
1720

1821

19-
class RpcResult[T](BaseModel):
20-
ok: Optional[T] = None
21-
error: Optional[Any] = None
22+
@dataclass_json
23+
@dataclass
24+
class RpcResult[T]:
25+
ok: T | None = None
26+
error: Any | None = None
2227

2328

24-
class RpcResponse[T](BaseModel):
25-
jsonrpc: str = "2.0"
26-
result: Optional[RpcResult[T]] = None
27-
error: Dict[str, Any] | None = None
29+
@dataclass_json
30+
@dataclass
31+
class RpcResponse[T]:
2832
id: int
33+
jsonrpc: str = "2.0"
34+
result: RpcResult[T] | None = None
35+
error: dict[str, Any] | None = None
2936

3037

3138
class AsyncRpcClient:
32-
def __init__(self, transport: Transport):
39+
def __init__(self, transport: WebSocketTransport):
3340
self._transport = transport
3441
self._id = 0
3542
self._pending_requests: Dict[int, asyncio.Future[Any]] = {}
36-
self._listen_task = asyncio.create_task(self._listener())
43+
self._listen_task: asyncio.Task[Any] | None = None
3744
self._pending_processes: Dict[int, asyncio.StreamReader] = {}
45+
self._loop = None
3846

3947
async def close(self):
4048
await self._transport.close()
4149

4250
async def call(self, method: str, params: Dict[str, Any]) -> Any:
51+
if self._listen_task is None or self._listen_task.done():
52+
self._loop = asyncio.get_running_loop()
53+
self._listen_task = self._loop.create_task(self._listener())
54+
4355
req_id = self._id + 1
4456
self._id = req_id
4557

4658
payload = RpcRequest(method=method, params=params, id=req_id)
4759

48-
loop = asyncio.get_running_loop()
49-
future = loop.create_future()
60+
future = self._loop.create_future()
5061
self._pending_requests[req_id] = future
5162

52-
await self._transport.send(payload.model_dump_json())
63+
await self._transport.send(payload.to_json())
5364

5465
raw_response = await future
55-
response = RpcResponse[Any](**raw_response)
66+
response = RpcResponse[Any].from_json(json.dumps(raw_response))
5667

5768
if response.error:
5869
raise Exception(response.error)
@@ -91,6 +102,7 @@ async def _listener(self) -> None:
91102
del self._pending_processes[stream_id]
92103

93104
except ConnectionClosed:
105+
print("Transport connection closed.") # --- IGNORE ---
94106
pass
95107
except Exception as e:
96108
for future in self._pending_requests.values():

0 commit comments

Comments
 (0)