Skip to content

Commit 46fed72

Browse files
committed
✨ ⏫ way simpler remotebox
1 parent 847ef13 commit 46fed72

1 file changed

Lines changed: 50 additions & 45 deletions

File tree

src/codeboxapi/remote.py

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,75 @@
1-
from json import loads
2-
from os import PathLike
3-
from typing import AsyncGenerator, AsyncIterator, BinaryIO, Generator, Iterator, Literal
1+
from os import PathLike, getenv
2+
from typing import AsyncGenerator, BinaryIO, Generator, Literal
43
from uuid import uuid4
54

6-
import anyio
75
import httpx
86

9-
from . import utils
107
from .codebox import CodeBox, CodeBoxFile, ExecChunk
11-
from .config import settings
8+
from .utils import raise_error, resolve_pathlike
129

1310

1411
class RemoteBox(CodeBox):
1512
"""
1613
Sandboxed Python Interpreter
1714
"""
1815

19-
def __new__(cls, *args, **kwargs):
20-
if kwargs.pop("local", False) or settings.api_key == "local":
21-
from .local import LocalBox
22-
23-
return LocalBox(*args, **kwargs)
24-
return super().__new__(cls)
16+
def __new__(cls) -> "RemoteBox":
17+
# This is a hack to ignore the CodeBox.__new__ factory method.
18+
return object.__new__(cls)
2519

2620
def __init__(
2721
self,
28-
factory_id: str | None = None,
29-
api_key: str | None = None,
22+
session_id: str | None = None,
23+
api_key: str | Literal["local", "docker"] = "local",
24+
factory_id: str | Literal["default"] = "default",
25+
base_url: str = "https://codeboxapi.com/api/v2",
26+
_new: bool = False,
3027
) -> None:
31-
super().__init__()
32-
self.session_id = uuid4().hex
28+
self.session_id = session_id or uuid4().hex
3329
self.factory_id = factory_id
34-
self.api_key = api_key or settings.api_key
35-
self.aclient = httpx.AsyncClient(
36-
base_url=f"{settings.base_url}/codebox/{self.session_id}"
30+
self.api_key = (
31+
api_key
32+
or getenv("CODEBOX_API_KEY")
33+
or raise_error("CODEBOX_API_KEY is required")
3734
)
35+
self.base_url = f"{base_url}/codebox/{self.session_id}"
36+
self.headers = {"Factory-Id": self.factory_id} if self.factory_id else None
37+
self.client = httpx.Client(base_url=self.base_url, headers=self.headers)
38+
self.aclient = httpx.AsyncClient(base_url=self.base_url, headers=self.headers)
3839

3940
def stream_exec(
4041
self,
4142
code: str | PathLike,
42-
language: Literal["python", "bash"] = "python",
43+
kernel: Literal["ipython", "bash"] = "ipython",
4344
timeout: float | None = None,
4445
cwd: str | None = None,
4546
) -> Generator[ExecChunk, None, None]:
46-
async_gen = self.astream_exec(code, language, timeout, cwd)
47-
return (chunk for chunk in anyio.run(utils.collect_async_gen, async_gen))
48-
49-
def upload(
50-
self,
51-
file_name: str,
52-
content: BinaryIO | bytes | str,
53-
timeout: float | None = None,
54-
) -> CodeBoxFile:
55-
return anyio.run(self.aupload, file_name, content, timeout)
56-
57-
def stream_download(
58-
self,
59-
remote_file_path: str,
60-
timeout: float | None = None,
61-
) -> Iterator[bytes]:
62-
return anyio.run(
63-
utils.collect_async_gen, self.astream_download(remote_file_path, timeout)
64-
)
47+
code = resolve_pathlike(code)
48+
with self.client.stream(
49+
method="POST",
50+
url="/stream",
51+
timeout=timeout,
52+
params={"code": code, "kernel": kernel, "cwd": cwd},
53+
) as response:
54+
for chunk in response.iter_text():
55+
yield ExecChunk.decode(chunk)
6556

6657
async def astream_exec(
6758
self,
6859
code: str | PathLike,
69-
language: Literal["python", "bash"] = "python",
60+
kernel: Literal["ipython", "bash"] = "ipython",
7061
timeout: float | None = None,
7162
cwd: str | None = None,
7263
) -> AsyncGenerator[ExecChunk, None]:
73-
code = utils.resolve_pathlike(code)
64+
code = resolve_pathlike(code)
7465
async with self.aclient.stream(
7566
method="POST",
7667
url="/stream",
7768
timeout=timeout,
78-
params={"code": code, "language": language, "cwd": cwd},
69+
params={"code": code, "kernel": kernel, "cwd": cwd},
7970
) as response:
8071
async for chunk in response.aiter_text():
81-
yield ExecChunk(**loads(chunk))
72+
yield ExecChunk.decode(chunk)
8273

8374
async def aupload(
8475
self,
@@ -96,14 +87,28 @@ async def aupload(
9687
timeout=timeout,
9788
)
9889
).json(),
99-
codebox=self,
90+
codebox_id=self.session_id,
10091
)
10192

93+
def stream_download(
94+
self,
95+
remote_file_path: str,
96+
timeout: float | None = None,
97+
) -> Generator[bytes, None, None]:
98+
with self.client.stream(
99+
method="GET",
100+
url="/download",
101+
timeout=timeout,
102+
params={"file_name": remote_file_path},
103+
) as response:
104+
for chunk in response.iter_bytes():
105+
yield chunk
106+
102107
async def astream_download(
103108
self,
104109
remote_file_path: str,
105110
timeout: float | None = None,
106-
) -> AsyncIterator[bytes]:
111+
) -> AsyncGenerator[bytes, None]:
107112
async with self.aclient.stream(
108113
method="GET",
109114
url="/download",

0 commit comments

Comments
 (0)