boxfab-code-interpreter 0.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boxfab_code_interpreter/__init__.py +15 -0
- boxfab_code_interpreter/charts.py +232 -0
- boxfab_code_interpreter/client.py +95 -0
- boxfab_code_interpreter/code_interpreter_async.py +416 -0
- boxfab_code_interpreter/code_interpreter_sync.py +414 -0
- boxfab_code_interpreter/constants.py +3 -0
- boxfab_code_interpreter/exceptions.py +20 -0
- boxfab_code_interpreter/models.py +526 -0
- boxfab_code_interpreter-0.1.0a1.dist-info/METADATA +105 -0
- boxfab_code_interpreter-0.1.0a1.dist-info/RECORD +12 -0
- boxfab_code_interpreter-0.1.0a1.dist-info/WHEEL +4 -0
- boxfab_code_interpreter-0.1.0a1.dist-info/licenses/LICENSE +10 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from boxfab import NotFoundException, TimeoutException, SandboxException
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import (
|
|
8
|
+
List,
|
|
9
|
+
Literal,
|
|
10
|
+
Optional,
|
|
11
|
+
Iterable,
|
|
12
|
+
Dict,
|
|
13
|
+
TypeVar,
|
|
14
|
+
Callable,
|
|
15
|
+
Awaitable,
|
|
16
|
+
Any,
|
|
17
|
+
Union,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from httpx import Response
|
|
21
|
+
|
|
22
|
+
from .charts import Chart, _deserialize_chart
|
|
23
|
+
|
|
24
|
+
RunCodeLanguage = Union[
|
|
25
|
+
Literal["python", "javascript", "typescript", "r", "java", "bash"],
|
|
26
|
+
str,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
OutputHandler = Union[Callable[[T], Any],]
|
|
31
|
+
|
|
32
|
+
OutputHandlerWithAsync = Union[
|
|
33
|
+
OutputHandler[T],
|
|
34
|
+
Callable[[T], Awaitable[Any]],
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class OutputMessage:
|
|
42
|
+
"""
|
|
43
|
+
Represents an output message from the sandbox code execution.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
line: str
|
|
47
|
+
"""
|
|
48
|
+
The output line.
|
|
49
|
+
"""
|
|
50
|
+
timestamp: int
|
|
51
|
+
"""
|
|
52
|
+
Unix epoch in nanoseconds
|
|
53
|
+
"""
|
|
54
|
+
error: bool = False
|
|
55
|
+
"""
|
|
56
|
+
Whether the output is an error.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return self.line
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class ExecutionError:
|
|
65
|
+
"""
|
|
66
|
+
Represents an error that occurred during the execution of a cell.
|
|
67
|
+
The error contains the name of the error, the value of the error, and the traceback.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
name: str
|
|
71
|
+
"""
|
|
72
|
+
Name of the error.
|
|
73
|
+
"""
|
|
74
|
+
value: str
|
|
75
|
+
"""
|
|
76
|
+
Value of the error.
|
|
77
|
+
"""
|
|
78
|
+
traceback: str
|
|
79
|
+
"""
|
|
80
|
+
The raw traceback of the error.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, name: str, value: str, traceback: str, **kwargs):
|
|
84
|
+
self.name = name
|
|
85
|
+
self.value = value
|
|
86
|
+
self.traceback = traceback
|
|
87
|
+
|
|
88
|
+
def to_json(self) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Returns the JSON representation of the Error object.
|
|
91
|
+
"""
|
|
92
|
+
data = {"name": self.name, "value": self.value, "traceback": self.traceback}
|
|
93
|
+
return json.dumps(data)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class MIMEType(str):
|
|
97
|
+
"""
|
|
98
|
+
Represents a MIME type.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class Result:
|
|
104
|
+
"""
|
|
105
|
+
Represents the data to be displayed as a result of executing a cell in a Jupyter notebook.
|
|
106
|
+
The result is similar to the structure returned by ipython kernel: https://ipython.readthedocs.io/en/stable/development/execution.html#execution-semantics
|
|
107
|
+
|
|
108
|
+
The result can contain multiple types of data, such as text, images, plots, etc. Each type of data is represented
|
|
109
|
+
as a string, and the result can contain multiple types of data. The display calls don't have to have text representation,
|
|
110
|
+
for the actual result the representation is always present for the result, the other representations are always optional.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __getitem__(self, item):
|
|
114
|
+
return getattr(self, item)
|
|
115
|
+
|
|
116
|
+
text: Optional[str] = None
|
|
117
|
+
html: Optional[str] = None
|
|
118
|
+
markdown: Optional[str] = None
|
|
119
|
+
svg: Optional[str] = None
|
|
120
|
+
png: Optional[str] = None
|
|
121
|
+
jpeg: Optional[str] = None
|
|
122
|
+
pdf: Optional[str] = None
|
|
123
|
+
latex: Optional[str] = None
|
|
124
|
+
json: Optional[dict] = None
|
|
125
|
+
javascript: Optional[str] = None
|
|
126
|
+
data: Optional[dict] = None
|
|
127
|
+
chart: Optional[Chart] = None
|
|
128
|
+
is_main_result: bool = False
|
|
129
|
+
"""Whether this data is the result of the cell. Data can be produced by display calls of which can be multiple in a cell."""
|
|
130
|
+
extra: Optional[dict] = None
|
|
131
|
+
"""Extra data that can be included. Not part of the standard types."""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
text: Optional[str] = None,
|
|
136
|
+
html: Optional[str] = None,
|
|
137
|
+
markdown: Optional[str] = None,
|
|
138
|
+
svg: Optional[str] = None,
|
|
139
|
+
png: Optional[str] = None,
|
|
140
|
+
jpeg: Optional[str] = None,
|
|
141
|
+
pdf: Optional[str] = None,
|
|
142
|
+
latex: Optional[str] = None,
|
|
143
|
+
json: Optional[dict] = None,
|
|
144
|
+
javascript: Optional[str] = None,
|
|
145
|
+
data: Optional[dict] = None,
|
|
146
|
+
chart: Optional[dict] = None,
|
|
147
|
+
is_main_result: bool = False,
|
|
148
|
+
extra: Optional[dict] = None,
|
|
149
|
+
**kwargs, # Allows for future expansion
|
|
150
|
+
):
|
|
151
|
+
self.text = text
|
|
152
|
+
self.html = html
|
|
153
|
+
self.markdown = markdown
|
|
154
|
+
self.svg = svg
|
|
155
|
+
self.png = png
|
|
156
|
+
self.jpeg = jpeg
|
|
157
|
+
self.pdf = pdf
|
|
158
|
+
self.latex = latex
|
|
159
|
+
self.json = json
|
|
160
|
+
self.javascript = javascript
|
|
161
|
+
self.data = data
|
|
162
|
+
if chart:
|
|
163
|
+
try:
|
|
164
|
+
self.chart = _deserialize_chart(chart)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(
|
|
167
|
+
f"Error deserializing chart, check if you are using the latest version of the library: {e}"
|
|
168
|
+
)
|
|
169
|
+
self.is_main_result = is_main_result
|
|
170
|
+
self.extra = extra
|
|
171
|
+
|
|
172
|
+
def formats(self) -> Iterable[str]:
|
|
173
|
+
"""
|
|
174
|
+
Returns all available formats of the result.
|
|
175
|
+
|
|
176
|
+
:return: All available formats of the result in MIME types.
|
|
177
|
+
"""
|
|
178
|
+
formats = []
|
|
179
|
+
if self.text:
|
|
180
|
+
formats.append("text")
|
|
181
|
+
if self.html:
|
|
182
|
+
formats.append("html")
|
|
183
|
+
if self.markdown:
|
|
184
|
+
formats.append("markdown")
|
|
185
|
+
if self.svg:
|
|
186
|
+
formats.append("svg")
|
|
187
|
+
if self.png:
|
|
188
|
+
formats.append("png")
|
|
189
|
+
if self.jpeg:
|
|
190
|
+
formats.append("jpeg")
|
|
191
|
+
if self.pdf:
|
|
192
|
+
formats.append("pdf")
|
|
193
|
+
if self.latex:
|
|
194
|
+
formats.append("latex")
|
|
195
|
+
if self.json:
|
|
196
|
+
formats.append("json")
|
|
197
|
+
if self.javascript:
|
|
198
|
+
formats.append("javascript")
|
|
199
|
+
if self.data:
|
|
200
|
+
formats.append("data")
|
|
201
|
+
if self.chart:
|
|
202
|
+
formats.append("chart")
|
|
203
|
+
|
|
204
|
+
if self.extra:
|
|
205
|
+
for key in self.extra:
|
|
206
|
+
formats.append(key)
|
|
207
|
+
|
|
208
|
+
return formats
|
|
209
|
+
|
|
210
|
+
def __str__(self) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Returns the text representation of the data.
|
|
213
|
+
|
|
214
|
+
:return: The text representation of the data.
|
|
215
|
+
"""
|
|
216
|
+
return self.__repr__()
|
|
217
|
+
|
|
218
|
+
def __repr__(self) -> str:
|
|
219
|
+
if self.text:
|
|
220
|
+
return f"Result({self.text})"
|
|
221
|
+
else:
|
|
222
|
+
return "Result(Formats: " + ", ".join(self.formats()) + ")"
|
|
223
|
+
|
|
224
|
+
def _repr_html_(self) -> Optional[str]:
|
|
225
|
+
"""
|
|
226
|
+
Returns the HTML representation of the data.
|
|
227
|
+
|
|
228
|
+
:return: The HTML representation of the data.
|
|
229
|
+
"""
|
|
230
|
+
return self.html
|
|
231
|
+
|
|
232
|
+
def _repr_markdown_(self) -> Optional[str]:
|
|
233
|
+
"""
|
|
234
|
+
Returns the Markdown representation of the data.
|
|
235
|
+
|
|
236
|
+
:return: The Markdown representation of the data.
|
|
237
|
+
"""
|
|
238
|
+
return self.markdown
|
|
239
|
+
|
|
240
|
+
def _repr_svg_(self) -> Optional[str]:
|
|
241
|
+
"""
|
|
242
|
+
Returns the SVG representation of the data.
|
|
243
|
+
|
|
244
|
+
:return: The SVG representation of the data.
|
|
245
|
+
"""
|
|
246
|
+
return self.svg
|
|
247
|
+
|
|
248
|
+
def _repr_png_(self) -> Optional[str]:
|
|
249
|
+
"""
|
|
250
|
+
Returns the base64 representation of the PNG data.
|
|
251
|
+
|
|
252
|
+
:return: The base64 representation of the PNG data.
|
|
253
|
+
"""
|
|
254
|
+
return self.png
|
|
255
|
+
|
|
256
|
+
def _repr_jpeg_(self) -> Optional[str]:
|
|
257
|
+
"""
|
|
258
|
+
Returns the base64 representation of the JPEG data.
|
|
259
|
+
|
|
260
|
+
:return: The base64 representation of the JPEG data.
|
|
261
|
+
"""
|
|
262
|
+
return self.jpeg
|
|
263
|
+
|
|
264
|
+
def _repr_pdf_(self) -> Optional[str]:
|
|
265
|
+
"""
|
|
266
|
+
Returns the PDF representation of the data.
|
|
267
|
+
|
|
268
|
+
:return: The PDF representation of the data.
|
|
269
|
+
"""
|
|
270
|
+
return self.pdf
|
|
271
|
+
|
|
272
|
+
def _repr_latex_(self) -> Optional[str]:
|
|
273
|
+
"""
|
|
274
|
+
Returns the LaTeX representation of the data.
|
|
275
|
+
|
|
276
|
+
:return: The LaTeX representation of the data.
|
|
277
|
+
"""
|
|
278
|
+
return self.latex
|
|
279
|
+
|
|
280
|
+
def _repr_json_(self) -> Optional[dict]:
|
|
281
|
+
"""
|
|
282
|
+
Returns the JSON representation of the data.
|
|
283
|
+
|
|
284
|
+
:return: The JSON representation of the data.
|
|
285
|
+
"""
|
|
286
|
+
return self.json
|
|
287
|
+
|
|
288
|
+
def _repr_javascript_(self) -> Optional[str]:
|
|
289
|
+
"""
|
|
290
|
+
Returns the JavaScript representation of the data.
|
|
291
|
+
|
|
292
|
+
:return: The JavaScript representation of the data.
|
|
293
|
+
"""
|
|
294
|
+
return self.javascript
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclass(repr=False)
|
|
298
|
+
class Logs:
|
|
299
|
+
"""
|
|
300
|
+
Data printed to stdout and stderr during execution, usually by print statements, logs, warnings, subprocesses, etc.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
stdout: List[str] = field(default_factory=list)
|
|
304
|
+
"""List of strings printed to stdout by prints, subprocesses, etc."""
|
|
305
|
+
stderr: List[str] = field(default_factory=list)
|
|
306
|
+
"""List of strings printed to stderr by prints, subprocesses, etc."""
|
|
307
|
+
|
|
308
|
+
def __init__(
|
|
309
|
+
self,
|
|
310
|
+
stdout: Optional[List[str]] = None,
|
|
311
|
+
stderr: Optional[List[str]] = None,
|
|
312
|
+
**kwargs,
|
|
313
|
+
):
|
|
314
|
+
self.stdout = stdout or []
|
|
315
|
+
self.stderr = stderr or []
|
|
316
|
+
|
|
317
|
+
def __repr__(self):
|
|
318
|
+
return f"Logs(stdout: {self.stdout}, stderr: {self.stderr})"
|
|
319
|
+
|
|
320
|
+
def to_json(self) -> str:
|
|
321
|
+
"""
|
|
322
|
+
Returns the JSON representation of the Logs object.
|
|
323
|
+
"""
|
|
324
|
+
data = {"stdout": self.stdout, "stderr": self.stderr}
|
|
325
|
+
return json.dumps(data)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def serialize_results(results: List[Result]) -> List[Dict[str, str]]:
|
|
329
|
+
"""
|
|
330
|
+
Serializes the results to JSON.
|
|
331
|
+
"""
|
|
332
|
+
serialized = []
|
|
333
|
+
for result in results:
|
|
334
|
+
serialized_dict = {}
|
|
335
|
+
for key in result.formats():
|
|
336
|
+
if key == "chart":
|
|
337
|
+
chart = result.chart
|
|
338
|
+
if chart is not None:
|
|
339
|
+
serialized_dict[key] = chart.to_dict()
|
|
340
|
+
else:
|
|
341
|
+
serialized_dict[key] = result[key]
|
|
342
|
+
|
|
343
|
+
serialized_dict["text"] = result.text
|
|
344
|
+
serialized.append(serialized_dict)
|
|
345
|
+
|
|
346
|
+
return serialized
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass(repr=False)
|
|
350
|
+
class Execution:
|
|
351
|
+
"""
|
|
352
|
+
Represents the result of a cell execution.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
results: List[Result] = field(default_factory=list)
|
|
356
|
+
"""List of the result of the cell (interactively interpreted last line), display calls (e.g. matplotlib plots)."""
|
|
357
|
+
logs: Logs = field(default_factory=Logs)
|
|
358
|
+
"""Logs printed to stdout and stderr during execution."""
|
|
359
|
+
error: Optional[ExecutionError] = None
|
|
360
|
+
"""Error object if an error occurred, None otherwise."""
|
|
361
|
+
execution_count: Optional[int] = None
|
|
362
|
+
"""Execution count of the cell."""
|
|
363
|
+
|
|
364
|
+
def __init__(
|
|
365
|
+
self,
|
|
366
|
+
results: Optional[List[Result]] = None,
|
|
367
|
+
logs: Optional[Logs] = None,
|
|
368
|
+
error: Optional[ExecutionError] = None,
|
|
369
|
+
execution_count: Optional[int] = None,
|
|
370
|
+
**kwargs,
|
|
371
|
+
):
|
|
372
|
+
self.results = results or []
|
|
373
|
+
self.logs = logs or Logs()
|
|
374
|
+
self.error = error
|
|
375
|
+
self.execution_count = execution_count
|
|
376
|
+
|
|
377
|
+
def __repr__(self):
|
|
378
|
+
return f"Execution(Results: {self.results}, Logs: {self.logs}, Error: {self.error})"
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def text(self) -> Optional[str]:
|
|
382
|
+
"""
|
|
383
|
+
Returns the text representation of the result.
|
|
384
|
+
|
|
385
|
+
:return: The text representation of the result.
|
|
386
|
+
"""
|
|
387
|
+
for d in self.results:
|
|
388
|
+
if d.is_main_result:
|
|
389
|
+
return d.text
|
|
390
|
+
|
|
391
|
+
def to_json(self) -> str:
|
|
392
|
+
"""
|
|
393
|
+
Returns the JSON representation of the Execution object.
|
|
394
|
+
"""
|
|
395
|
+
data = {
|
|
396
|
+
"results": serialize_results(self.results),
|
|
397
|
+
"logs": self.logs.to_json(),
|
|
398
|
+
"error": self.error.to_json() if self.error else None,
|
|
399
|
+
}
|
|
400
|
+
return json.dumps(data)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
async def aextract_exception(res: Response):
|
|
404
|
+
if res.is_success:
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
await res.aread()
|
|
408
|
+
return extract_exception(res)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def extract_exception(res: Response):
|
|
412
|
+
if res.is_success:
|
|
413
|
+
return None
|
|
414
|
+
|
|
415
|
+
res.read()
|
|
416
|
+
return format_exception(res)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def format_exception(res: Response):
|
|
420
|
+
if res.is_success:
|
|
421
|
+
return None
|
|
422
|
+
|
|
423
|
+
if res.status_code == 404:
|
|
424
|
+
return NotFoundException(res.text)
|
|
425
|
+
elif res.status_code == 502:
|
|
426
|
+
return TimeoutException(
|
|
427
|
+
f"{res.text}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
body = res.text.strip()
|
|
431
|
+
return SandboxException(
|
|
432
|
+
f"{res.status_code} {res.reason_phrase}{f': {body}' if body else ''}"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def parse_output(
|
|
437
|
+
execution: Execution,
|
|
438
|
+
output: str,
|
|
439
|
+
on_stdout: Optional[OutputHandler[OutputMessage]] = None,
|
|
440
|
+
on_stderr: Optional[OutputHandler[OutputMessage]] = None,
|
|
441
|
+
on_result: Optional[OutputHandler[Result]] = None,
|
|
442
|
+
on_error: Optional[OutputHandler[ExecutionError]] = None,
|
|
443
|
+
):
|
|
444
|
+
_parse_output(execution, output, on_stdout, on_stderr, on_result, on_error)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
async def async_parse_output(
|
|
448
|
+
execution: Execution,
|
|
449
|
+
output: str,
|
|
450
|
+
on_stdout: Optional[OutputHandlerWithAsync[OutputMessage]] = None,
|
|
451
|
+
on_stderr: Optional[OutputHandlerWithAsync[OutputMessage]] = None,
|
|
452
|
+
on_result: Optional[OutputHandlerWithAsync[Result]] = None,
|
|
453
|
+
on_error: Optional[OutputHandlerWithAsync[ExecutionError]] = None,
|
|
454
|
+
):
|
|
455
|
+
none_or_awaitable = _parse_output(
|
|
456
|
+
execution, output, on_stdout, on_stderr, on_result, on_error
|
|
457
|
+
)
|
|
458
|
+
if inspect.isawaitable(none_or_awaitable):
|
|
459
|
+
await none_or_awaitable
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _parse_output(
|
|
463
|
+
execution: Execution,
|
|
464
|
+
output: str,
|
|
465
|
+
on_stdout: Optional[OutputHandler[OutputMessage]] = None,
|
|
466
|
+
on_stderr: Optional[OutputHandler[OutputMessage]] = None,
|
|
467
|
+
on_result: Optional[OutputHandler[Result]] = None,
|
|
468
|
+
on_error: Optional[OutputHandler[ExecutionError]] = None,
|
|
469
|
+
) -> Union[None, Awaitable[Any]]:
|
|
470
|
+
data = json.loads(output)
|
|
471
|
+
data_type = data.pop("type")
|
|
472
|
+
|
|
473
|
+
if data_type == "result":
|
|
474
|
+
result = Result(**data)
|
|
475
|
+
execution.results.append(result)
|
|
476
|
+
if on_result:
|
|
477
|
+
return on_result(result)
|
|
478
|
+
elif data_type == "stdout":
|
|
479
|
+
execution.logs.stdout.append(data["text"])
|
|
480
|
+
if on_stdout:
|
|
481
|
+
return on_stdout(OutputMessage(data["text"], data["timestamp"], False))
|
|
482
|
+
elif data_type == "stderr":
|
|
483
|
+
execution.logs.stderr.append(data["text"])
|
|
484
|
+
if on_stderr:
|
|
485
|
+
return on_stderr(OutputMessage(data["text"], data["timestamp"], True))
|
|
486
|
+
elif data_type == "error":
|
|
487
|
+
execution.error = ExecutionError(data["name"], data["value"], data["traceback"])
|
|
488
|
+
if on_error:
|
|
489
|
+
return on_error(execution.error)
|
|
490
|
+
elif data_type == "number_of_executions":
|
|
491
|
+
execution.execution_count = data["execution_count"]
|
|
492
|
+
|
|
493
|
+
return None
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@dataclass
|
|
497
|
+
class Context:
|
|
498
|
+
"""
|
|
499
|
+
Represents a context for code execution.
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
id: str
|
|
503
|
+
"""
|
|
504
|
+
The ID of the context.
|
|
505
|
+
"""
|
|
506
|
+
language: str
|
|
507
|
+
"""
|
|
508
|
+
The language of the context.
|
|
509
|
+
"""
|
|
510
|
+
cwd: str
|
|
511
|
+
"""
|
|
512
|
+
The working directory of the context.
|
|
513
|
+
"""
|
|
514
|
+
|
|
515
|
+
def __init__(self, context_id: str, language: str, cwd: str, **kwargs):
|
|
516
|
+
self.id = context_id
|
|
517
|
+
self.language = language
|
|
518
|
+
self.cwd = cwd
|
|
519
|
+
|
|
520
|
+
@classmethod
|
|
521
|
+
def from_json(cls, data: Dict[str, str]):
|
|
522
|
+
return cls(
|
|
523
|
+
context_id=data["id"],
|
|
524
|
+
language=data["language"],
|
|
525
|
+
cwd=data["cwd"],
|
|
526
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boxfab-code-interpreter
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: BoxFab Code Interpreter: run AI-generated code in BoxFab sandboxes, Jupyter-style
|
|
5
|
+
Author: ContextFab, Inc.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: boxfab>=0.1.0a3,<0.2
|
|
15
|
+
Requires-Dist: httpx>=0.20.0,<1.0.0
|
|
16
|
+
Requires-Dist: attrs>=21.3.0
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Project-URL: Repository, https://github.com/boxfab/python-sdk
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# BoxFab Code Interpreter (Python)
|
|
22
|
+
|
|
23
|
+
Run AI-generated code in isolated [BoxFab](https://boxfab.io) sandboxes, Jupyter-style:
|
|
24
|
+
Python, JavaScript, TypeScript, R, Java and Bash, with results such as text, tables and charts
|
|
25
|
+
returned as data.
|
|
26
|
+
|
|
27
|
+
## Getting started
|
|
28
|
+
|
|
29
|
+
### 1. Install the SDK
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install boxfab-code-interpreter
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Get a BoxFab API key
|
|
36
|
+
|
|
37
|
+
Get a key from the [BoxFab dashboard](https://dashboard.boxfab.io) and set it as `BOXFAB_API_KEY`.
|
|
38
|
+
The SDK reads only `BOXFAB_*` variables.
|
|
39
|
+
|
|
40
|
+
### 3. Execute code with code interpreter inside Sandbox
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
from boxfab_code_interpreter import Sandbox
|
|
44
|
+
|
|
45
|
+
with Sandbox.create() as sandbox:
|
|
46
|
+
sandbox.run_code("x = 1")
|
|
47
|
+
execution = sandbox.run_code("x+=1; x")
|
|
48
|
+
print(execution.text) # outputs 2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 4. Bind the configuration to a client
|
|
52
|
+
|
|
53
|
+
The top-level `Sandbox` and `AsyncSandbox` exports read their configuration from the environment variables. To use an explicit configuration — e.g. several API keys or domains in one process — create a `BoxFab` client and use the resource classes it exposes:
|
|
54
|
+
|
|
55
|
+
```py
|
|
56
|
+
from boxfab_code_interpreter import BoxFab
|
|
57
|
+
|
|
58
|
+
client = BoxFab(api_key="e2b_***", domain="boxfab.io")
|
|
59
|
+
|
|
60
|
+
with client.Sandbox.create() as sandbox:
|
|
61
|
+
execution = sandbox.run_code("x = 1; x += 1; x")
|
|
62
|
+
|
|
63
|
+
# The async variant is exposed as well.
|
|
64
|
+
async_sandbox = await client.AsyncSandbox.create()
|
|
65
|
+
|
|
66
|
+
# The core resources are bound to the client's configuration as well.
|
|
67
|
+
volume = client.Volume.create("my-volume")
|
|
68
|
+
exists = client.Template.exists("my-template")
|
|
69
|
+
secret = client.Secret.create("openai-api-key", "sk-***")
|
|
70
|
+
|
|
71
|
+
# The classes can be assigned and used like the top-level ones.
|
|
72
|
+
Sandbox = client.Sandbox
|
|
73
|
+
paginator = Sandbox.list()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Per-call params still take precedence over the client's params, and clients are isolated from each other and from the env-configured top-level exports.
|
|
77
|
+
|
|
78
|
+
## Under the hood
|
|
79
|
+
|
|
80
|
+
Sandboxes are created from the `code-interpreter-v1` template, which BoxFab provides to every team:
|
|
81
|
+
a Jupyter server with common data-science packages. BoxFab sandboxes are
|
|
82
|
+
[Firecracker](https://firecracker-microvm.github.io/) microVMs, run on the open-source
|
|
83
|
+
[E2B](https://github.com/e2b-dev/infra) sandbox infrastructure. This package is derived from the
|
|
84
|
+
open-source E2B Code Interpreter SDK;
|
|
85
|
+
[UPSTREAM.md](https://github.com/boxfab/python-sdk/blob/main/UPSTREAM.md) lists every change.
|
|
86
|
+
|
|
87
|
+
**Using the E2B Code Interpreter SDKs.** BoxFab serves the same API and provides the
|
|
88
|
+
`code-interpreter-v1` template, so the E2B Code Interpreter SDKs work with it as well. Set both
|
|
89
|
+
variables, the domain first:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
export E2B_DOMAIN=boxfab.io
|
|
93
|
+
export E2B_API_KEY="<your BoxFab API key>"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Without `E2B_DOMAIN`, the E2B SDKs send requests, including the key, to E2B's own service.
|
|
97
|
+
|
|
98
|
+
Tested against BoxFab with `e2b-code-interpreter` 2.11.6 (Python) and `@e2b/code-interpreter` 2.9.0
|
|
99
|
+
(JavaScript).
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT, as the E2B Code Interpreter SDK it is derived from; see [LICENSE](LICENSE).
|
|
104
|
+
|
|
105
|
+
BoxFab is a product of ContextFab, Inc.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
boxfab_code_interpreter/__init__.py,sha256=4k96jiR2qqLbWV5sTOT05fc5P-Dv80LSwAk3pUyyYbs,349
|
|
2
|
+
boxfab_code_interpreter/charts.py,sha256=ivhSndofDJZAg-Nup3BxtnmkDAxwNUpaKou7Zf_5gYU,5475
|
|
3
|
+
boxfab_code_interpreter/client.py,sha256=6YZRemh8DO_y-CT-ifnG6_H8no49L8YVhe1TeEA8tZc,3535
|
|
4
|
+
boxfab_code_interpreter/code_interpreter_async.py,sha256=CUluRG364egG4Lptr3dNpdlPO6F1HPypkDhpYSxRWbU,15491
|
|
5
|
+
boxfab_code_interpreter/code_interpreter_sync.py,sha256=A9ekJYWNYtJwteW66ud0us0UzfU3e1NGHm4zcU-mzeU,15137
|
|
6
|
+
boxfab_code_interpreter/constants.py,sha256=Gs-ONgzrfrvFnCZzV9drv_LgmF2Domb56LZ_kcd9H8o,84
|
|
7
|
+
boxfab_code_interpreter/exceptions.py,sha256=aCrhoLKW6kpWeTy_6pRb4u74Bz7R9A8JBps8FIdevxQ,781
|
|
8
|
+
boxfab_code_interpreter/models.py,sha256=gbAU3IzHZ4bGjlBsZaVh5UNy2uIW_wqNqytcp3jzukY,14778
|
|
9
|
+
boxfab_code_interpreter-0.1.0a1.dist-info/licenses/LICENSE,sha256=Owp2F6JmvLCebzBDPeOq8ZFKqd05UisLlsTLWFFKC24,1110
|
|
10
|
+
boxfab_code_interpreter-0.1.0a1.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
|
|
11
|
+
boxfab_code_interpreter-0.1.0a1.dist-info/METADATA,sha256=Fhlu62euI0uRpA4h8U7ZBSbqxSY2TyraT6imSrLOPqU,3748
|
|
12
|
+
boxfab_code_interpreter-0.1.0a1.dist-info/RECORD,,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FOUNDRYLABS, INC.
|
|
4
|
+
Copyright (c) 2026 ContextFab, Inc.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
|
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|