dart_monty_core 0.0.17
dart_monty_core: ^0.0.17 copied to clipboard
Sandboxed Python scripting for Dart. Low-level binding for pydantic/monty's interpreter.
dart_monty_core #
Run Python in Dart. A thin binding for pydantic/monty — the sandboxed Python interpreter from Pydantic, written in Rust.
dart_monty_core is the raw binding layer — Monty, MontyRepl,
MontyValue, the FFI/WASM platform glue. For a higher-level API
(Flutter integration, asset auto-loading, plugin scaffolding) see
dart_monty, which depends
on this package.
Pre-1.0 — install via git: from GitHub (see Installation
below). Versioning convention: minor version mirrors the upstream
monty patch (0.X.0 ↔ monty v0.0.X).
Why #
Dart is compiled, no reflection — fast and tree-shakeable, but you can't ship new behaviour without re-shipping a binary. Monty is a sandboxed Python runtime designed to behave as Dart's scripting language: an embeddable Python subset under hard resource limits, on both native (FFI) and web (WASM).
LLMs generate excellent Python. Let them script your Dart app — through
code your app type-checks, runs in a sandbox, exposes only the external
functions and OS calls you whitelist, and inspects the typed result. More
flexible than a plug-in registry, safer than eval — Pydantic runs an
active $5,000 bug bounty at hackmonty.com
for the underlying interpreter.
final errors = await Monty.typeCheck(llmCode);
if (errors.isNotEmpty) return;
final result = await Monty(llmCode).run(
inputs: {'temperatureC': 22},
externalFunctions: {
'fetchWeather': (args) async => weatherApi.get(args['_0'] as String),
'log': (args) async { logger.info(args['_0']); return null; },
},
limits: const MontyLimits(memoryBytes: 32 << 20, timeoutMs: 5000),
);
Quick start #
import 'package:dart_monty_core/dart_monty_core.dart';
// One-shot
final r = await Monty.exec('2 ** 10');
print(r.value); // MontyInt(1024)
// Compiled program — different inputs, no shared state
final program = Monty('x * y');
print((await program.run(inputs: {'x': 10, 'y': 3})).value); // MontyInt(30)
print((await program.run(inputs: {'x': 7, 'y': 6})).value); // MontyInt(42)
// Stateful REPL — variables, functions, imports survive
final repl = MontyRepl();
await repl.feedRun('def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)');
print((await repl.feedRun('fib(10)')).value); // MontyInt(55)
await repl.dispose();
API #
Monty — compiled program #
Monty(code, {scriptName}) |
Hold source as a re-runnable program |
run({inputs, externalFunctions, limits, osHandler, printCallback}) |
Run in a fresh interpreter |
Monty.exec(code, {…}) |
One-shot wrapper |
Monty.compile(code) / Monty.runPrecompiled(bytes, {…}) |
Pre-compile and replay |
Monty.typeCheck(code, {prefixCode, scriptName}) |
Static type analysis → List<MontyTypingError> |
MontyRepl — stateful REPL #
MontyRepl({scriptName, preamble}) |
Auto-detected backend |
feedRun(code, {inputs, externalFunctions, osHandler, printCallback}) |
State persists |
feedStart(code) + resume / resumeWithError |
Iterative externals + OS calls |
detectContinuation(code) |
>>> vs ... mode |
snapshot() / restore(bytes) |
Serialise / restore the heap |
clearState() / dispose() |
Wipe / free |
Multiple MontyRepls coexist — each owns its own Rust heap.
MontyValue — typed Python values #
switch (result.value) {
case MontyInt(:final value): /* … */ ;
case MontyString(:final value): /* … */ ;
case MontyList(:final items): /* … */ ;
case MontyDict(:final entries): /* … */ ;
case MontyDate(:final year): /* … */ ;
case MontyNamedTuple(:final fieldNames, :final values): /* … */ ;
case MontyDataclass(:final name, :final attrs): /* … */ ;
case MontyNone(): /* … */ ;
}
18 subtypes — scalars (MontyInt, MontyFloat, MontyString, MontyBool,
MontyBytes, MontyNone), collections (MontyList, MontyTuple,
MontyDict, MontySet, MontyFrozenSet), datetime (MontyDate,
MontyDateTime, MontyTimeDelta, MontyTimeZone), and structured
(MontyPath, MontyNamedTuple, MontyDataclass).
MontyDataclass.hydrate(factory) turns a Python @dataclass into your
own Dart class:
final user = (result.value as MontyDataclass).hydrate(User.fromAttrs);
Build from Dart with MontyValue.fromDart(value).
Errors #
MontySyntaxError |
Python parse error (subtype of MontyScriptError) |
MontyScriptError |
Python runtime exception |
MontyResourceError |
Limit exceeded (memory / stack / timeout) |
run() / feedRun() surface Python-level exceptions in MontyResult.error
rather than throwing — the interpreter stays alive. Resource limits and
disposal still throw.
External functions #
Python calls Dart callbacks by name. Positional args at _0, _1, …;
kwargs by Python name. Sync or async.
await Monty('compute("mul", 6, 7)').run(externalFunctions: {
'compute': (args) async => switch (args['_0']) {
'mul' => (args['_1'] as int) * (args['_2'] as int),
_ => 0,
},
});
OS calls #
pathlib, os.getenv, datetime.now, time.time pause and call your
OsCallHandler. Optional — provide only when the script touches the OS.
await Monty('os.getenv("HOME")').run(
osHandler: (op, args, kwargs) async => switch (op) {
'os.getenv' => Platform.environment[args[0] as String],
_ => throw OsCallException('not supported',
pythonExceptionType: 'PermissionError'),
},
);
memoryMountedOsHandler (lib/src/mount/) provides a ready-made in-memory
VFS with mount-based sandboxing.
Resource limits #
await Monty(code).run(
limits: const MontyLimits(
memoryBytes: 32 << 20,
stackDepth: 200,
timeoutMs: 5000,
),
);
JS-aligned spelling: MontyLimits.jsAligned(maxMemory:, maxDurationSecs:, maxRecursionDepth:).
Backends #
| Selected when | |
|---|---|
MontyFfi |
dart.library.ffi present (desktop / server / mobile) |
MontyWasm |
dart.library.js_interop present (web) |
createPlatformMonty() |
Auto-pick at compile time |
Installation #
0.17.0 builds the native FFI binary from source on
dart pub get. Every FFI consumer needs a Rust toolchain, including Flutter consumers coming in viadart_monty.Prebuilt binaries arrive in 0.17.1 for macOS (arm64+x86_64), Linux (x86_64-gnu+aarch64-gnu), Windows (x86_64), iOS (xcframework), and Android (4 ABIs). The build hook will download the matching artefact from this repo's GitHub Releases on first
pub get— no Rust toolchain required. SeeAGENTS.md"Native binary release pipeline (0.17.1+)".
Install (from GitHub) #
dart_monty_core is distributed via GitHub. Do not use
dart pub add dart_monty_core — pub.dev does not yet have
0.17.0; the historical dart_monty 0.11.0 there is a different,
older API.
dependencies:
dart_monty_core:
git:
url: https://github.com/runyaga/dart_monty_core.git
ref: main
For local development against a worktree, use path: instead:
dependencies:
dart_monty_core:
git:
url: https://github.com/runyaga/dart_monty_core
ref: v0.17.0 # pin to a tag; do not float on main
Prerequisites for FFI (desktop only) #
hook/build.dart runs cargo build --release --target <host-triple>
on the consumer's machine during pub get. Required toolchain:
- Rust — install via rustup
- C linker for the cdylib link step:
- macOS:
xcode-select --install(providesclang) - Linux:
sudo apt install build-essential/dnf install gcc/ equivalent - Windows: Visual Studio Build Tools with the C++ workload
- macOS:
Supported FFI host triples in v0.17.0: aarch64-apple-darwin,
x86_64-apple-darwin, aarch64-unknown-linux-gnu,
x86_64-unknown-linux-gnu, aarch64-pc-windows-msvc,
x86_64-pc-windows-msvc. Mobile (iOS, Android) is not handled by this
package's hook — the hook returns no native asset for those targets.
If you're using dart_monty_core directly and need Monty on mobile,
compiling and wiring the native crate into your Flutter project's iOS /
Android plugin is your responsibility. For a higher-level Flutter
integration, use dart_monty.
First pub get takes 1–3 minutes (compiling the native crate); subsequent
runs reuse cargo's cache.
Web (WASM) #
WASM ships pre-built — no toolchain required. Copy the three assets into
your web/ and add a script tag:
# Git-deps cache the cloned repo here (path encodes the resolved sha):
SRC=$(find ~/.pub-cache/git -maxdepth 2 -type d -name 'dart_monty_core-*' | head -1)
cp "$SRC/lib/assets/dart_monty_core_bridge.js" web/
cp "$SRC/lib/assets/dart_monty_core_worker.js" web/
cp "$SRC/lib/assets/dart_monty_core_native.wasm" web/
<script src="dart_monty_core_bridge.js"></script>
packages/dart_monty_web/ in this repo demonstrates the full wiring.
Other ecosystems #
- Flutter —
dart_montywraps this package with the Flutter integration layer (asset loading, plugin scaffolding). When usingdart_monty_coredirectly, mobile (iOS / Android) compilation is your responsibility;dart_montyis the alternative. - JS / TS — use
@pydantic/monty; the canonical npm package.
Known upstream limitations #
External functions can't be called from inside iterator-consuming C
builtins — map(ext_fn, …), filter(ext_fn, …), sorted(…, key=ext_fn)
raise RuntimeError upstream. First-class references work everywhere else.
License #
MIT.