Skip to content

Built-in Extensions

dart_monty ships three extensions that provide host functions to sandboxed Python code. All extensions work with MontyRuntime.

Extension Functions Description
TemplateExtension tmpl_render Jinja2 template rendering
MessageBusExtension msg_send, msg_recv, msg_peek, msg_close, msg_stats In-memory named channels
SandboxExtension sandbox_spawn, sandbox_await, sandbox_gather, sandbox_free Isolated child interpreters

TemplateExtension

Class: JinjaTemplateExtension Namespace: tmpl

Renders Jinja2 templates using the dinja Dart package. Supports {{ variables }}, {% for %} loops, {% if %} conditionals, and filters.

Functions

tmpl_render(template, context)

Render a Jinja2 template string with a context dict.

# Basic variable substitution
tmpl_render(template='Hello {{ name }}!', context={'name': 'World'})
# -> 'Hello World!'

# For loop
tmpl_render(
    template='{% for item in items %}{{ item }} {% endfor %}',
    context={'items': ['Alice', 'Bob', 'Charlie']}
)
# -> 'Alice Bob Charlie '

# Conditional
tmpl_render(
    template='{% if admin %}Admin{% else %}Guest{% endif %}',
    context={'admin': True}
)
# -> 'Admin'

Typical Pattern

Compute data in Python, render with the host template engine:

scores = [85, 92, 78, 95, 88]
report = {
    'avg': sum(scores) / len(scores),
    'top': max(scores),
    'n': len(scores),
}
tmpl_render(
    template='{{ n }} students, avg={{ avg }}, top={{ top }}',
    context=report,
)
# -> '5 students, avg=87.6, top=95'

Configuration

JinjaTemplateExtension(
  maxInputSize: 512 * 1024,  // 512 KB default
)

MessageBusExtension

Class: MessageBusExtension Namespace: msg

In-memory named message channels (FIFO queues). Useful for inter-process communication when combining multiple extensions or coordinating between parent and child sandboxes.

MessageBus Functions

msg_send(name, message) — Send a message to a named channel.

msg_send('tasks', {'id': 1, 'action': 'analyze'})

msg_recv(name, timeout_ms=None) — Receive the next message. Blocks until a message is available or timeout expires.

task = msg_recv('tasks')
# -> {'id': 1, 'action': 'analyze'}

msg_peek(name) — Check the next message without consuming it. Returns None if the channel is empty.

msg_peek('tasks')  # -> {'id': 1, ...} or None

msg_close(name) — Close a channel. Subsequent msg_recv calls raise an error.

msg_close('tasks')

msg_stats(name) — Get channel statistics.

msg_stats('tasks')
# -> {'pending': 2, 'delivered': 5, ...}

MessageBus Pattern

Log events from multiple operations:

msg_send('audit', {'action': 'upload', 'file': 'data.csv'})
msg_send('audit', {'action': 'query', 'room': 'analysis'})

# Later: collect all audit entries
logs = []
while msg_peek('audit') is not None:
    logs.append(msg_recv('audit'))

SandboxExtension

Class: SandboxExtension Namespace: sandbox

Spawns Python scripts in isolated child interpreters. Each child gets its own MontyPlatform and DefaultMontyBridge. Children can inherit extensions from the parent and even spawn their own children (grandchildren).

See Sandbox Architecture for the full deep dive.

Sandbox Functions

sandbox_spawn(code, timeout_ms=None, memory_bytes=None) — Spawn a child interpreter. Returns an integer handle.

h = sandbox_spawn(code='sum(range(100))')

sandbox_await(handle) — Wait for a child to complete. Returns the result value.

result = sandbox_await(h)  # -> 4950

sandbox_await_all(handles) — Wait for multiple children.

results = sandbox_await_all(handles=[h1, h2, h3])

sandbox_gather(handles) — Wait and return attributed results with handle, value, and output for each child.

results = sandbox_gather(handles=[h1, h2, h3])
# [{'handle': 0, 'value': 10, 'output': None}, ...]

sandbox_is_alive(handle) — Check if a child is still running.

sandbox_free(handle) — Release a completed child's resources.

sandbox_get_output(handle) — Get a completed child's captured print() output.

Sandbox Pattern

Parallel computation with result aggregation:

# Spawn 3 independent computations
h1 = sandbox_spawn(code='2 ** 16')
h2 = sandbox_spawn(code='sum(range(1000))')
h3 = sandbox_spawn(code='len([x for x in range(100) if x % 7 == 0])')

# Wait for all and get attributed results
results = sandbox_gather(handles=[h1, h2, h3])
for r in results:
    print(f"Handle {r['handle']}: {r['value']}")

Sandbox Configuration

SandboxExtension(
  platformFactory: () async => createPlatformMonty(),
  parentExtensions: [tmpl, msgBus],  // children inherit these
  maxChildren: 16,       // concurrent child limit
  maxDepth: 3,           // grandchild recursion limit
  childLimits: MontyLimits(
    timeoutMs: 10000,
    memoryBytes: 4 * 1024 * 1024,
  ),
)

Using Extensions with MontyRuntime

final session = MontyRuntime(
  extensions: [
    JinjaTemplateExtension(),
    MessageBusExtension(),
    SandboxExtension(
      platformFactory: () async => createPlatformMonty(),
      parentExtensions: [JinjaTemplateExtension()],
    ),
  ],
);

// All extension functions are now available in Python
await session.execute("help()").result;  // lists all functions
await session.execute("tmpl_render(template='{{ x }}', context={'x': 1})").result;
await session.execute("msg_send('ch', 'hello')").result;
await session.execute("h = sandbox_spawn(code='42')").result;

await session.dispose();

Writing Custom Extensions

See the host functions guide for how to create your own extensions with custom host functions.