Build a stateful, sandboxed code interpreter
This example demonstrates how to build a stateful code interpreter using a Modal Sandbox.
We’ll create a Modal Sandbox that listens for code to execute and then
executes the code in a Python interpreter. Because we’re running in a sandboxed
environment, we can safely use the “unsafe” exec() to execute the code.
Setting up a code interpreter in a Modal Sandbox
Our code interpreter uses a Python “driver program” to listen for code
sent in JSON format to its standard input (stdin), execute the code,
and then return the results in JSON format on standard output (stdout).
import inspect
import json
import sys
from typing import Any, Iterator
import modal
def driver_program():
import json
import sys
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO
# When you `exec` code in Python, you can pass in a dictionary
# that defines the global variables the code has access to.
# We'll use that to store state.
globals: dict[str, Any] = {}
while True:
command = json.loads(input()) # read a line of JSON from stdin
if (code := command.get("code")) is None:
print(json.dumps({"error": "No code to execute"}))
continue
# Capture the executed code's outputs
stdout_io, stderr_io = StringIO(), StringIO()
with redirect_stdout(stdout_io), redirect_stderr(stderr_io):
try:
exec(code, globals)
except Exception as e:
print(f"Execution Error: {e}", file=sys.stderr)
print(
json.dumps(
{"stdout": stdout_io.getvalue(), "stderr": stderr_io.getvalue()}
),
flush=True,
)We run this driver program in a Modal Sandbox.
app = modal.App.lookup("example-simple-code-interpreter", create_if_missing=True)
sb = modal.Sandbox.create(app=app)We have to convert the driver program to a string to pass it to the Sandbox.
Here we use inspect.getsource to get the source code as a string,
but you could also keep the driver program in a separate file and read it in.
driver_program_text = inspect.getsource(driver_program)
driver_program_command = f"""{driver_program_text}\n\ndriver_program()"""We then kick off the program with Sandbox.exec,
which creates a process inside the Sandbox (see modal.container_process for details).
p = sb.exec("python", "-c", driver_program_command, bufsize=1)Running code in a Modal Sandbox
Now we need a way to run code inside that running driver process.
Our driver program already defined a JSON interface on its stdin and stdout,
so we just need to write a quick wrapper to write to the remote stdin and read from the remote stdout.
reader, writer = p.stdin, iter(p.stdout)
def run_code(writer: modal.io_streams.StreamWriter, reader: Iterator[str], code: str):
writer.write(json.dumps({"code": code}) + "\n")
writer.drain()
result = json.loads(next(reader))
print(result["stdout"], end="")
if result["stderr"]:
print("\033[91m" + result["stderr"] + "\033[0m", end="", file=sys.stderr)Now we can execute some code in the Sandbox!
run_code(reader, writer, "print('hello, world!')") # hello, world!The Sandbox and our code interpreter are stateful, so we can define variables and use them in subsequent code.
run_code(reader, writer, "x = 10")
run_code(reader, writer, "y = 5")
run_code(reader, writer, "result = x + y")
run_code(reader, writer, "print(f'The result is: {result}')") # The result is: 15We can also see errors when code fails.
run_code(reader, writer, "print('Attempting to divide by zero...')")
run_code(reader, writer, "1 / 0") # Execution Error: division by zeroFinally, let’s clean up after ourselves and terminate the Sandbox.
sb.terminate()