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
43from uuid import uuid4
54
6- import anyio
75import httpx
86
9- from . import utils
107from .codebox import CodeBox , CodeBoxFile , ExecChunk
11- from .config import settings
8+ from .utils import raise_error , resolve_pathlike
129
1310
1411class 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