|
1 | 1 | # @pydantic/monty |
2 | 2 |
|
3 | | -JavaScript bindings for the Monty sandboxed Python interpreter. |
| 3 | +JavaScript/TypeScript bindings for the Monty sandboxed Python interpreter. |
4 | 4 |
|
5 | 5 | ## Installation |
6 | 6 |
|
7 | 7 | ```bash |
8 | 8 | npm install @pydantic/monty |
9 | 9 | ``` |
10 | 10 |
|
11 | | -## Usage (CommonJS) |
| 11 | +## Basic Usage |
12 | 12 |
|
13 | | -```js |
14 | | -const monty = require('@pydantic/monty') |
| 13 | +```ts |
| 14 | +import { Monty } from '@pydantic/monty' |
| 15 | + |
| 16 | +// Create interpreter and run code |
| 17 | +const m = new Monty('1 + 2') |
| 18 | +const result = m.run() // returns 3 |
| 19 | +``` |
| 20 | + |
| 21 | +## Input Variables |
| 22 | + |
| 23 | +```ts |
| 24 | +const m = new Monty('x + y', { inputs: ['x', 'y'] }) |
| 25 | +const result = m.run({ inputs: { x: 10, y: 20 } }) // returns 30 |
| 26 | +``` |
15 | 27 |
|
16 | | -const { output, result } = monty.run('print("hello")\n1 + 2') |
17 | | -console.log(output) // "hello\n" |
18 | | -console.log(result) // debug representation of the final value |
| 28 | +## External Functions |
| 29 | + |
| 30 | +For synchronous external functions, pass them directly to `run()`: |
| 31 | + |
| 32 | +```ts |
| 33 | +const m = new Monty('add(2, 3)', { externalFunctions: ['add'] }) |
| 34 | + |
| 35 | +const result = m.run({ |
| 36 | + externalFunctions: { |
| 37 | + add: (a: number, b: number) => a + b, |
| 38 | + }, |
| 39 | +}) // returns 5 |
19 | 40 | ``` |
20 | 41 |
|
21 | | -## Usage (ESM / TypeScript) |
| 42 | +For async external functions, use `runMontyAsync()`: |
22 | 43 |
|
23 | 44 | ```ts |
24 | | -import monty from '@pydantic/monty' |
| 45 | +import { Monty, runMontyAsync } from '@pydantic/monty' |
25 | 46 |
|
26 | | -const res = monty.run('print("hi")\n3 * 7') |
27 | | -console.log(res.output) |
28 | | -console.log(res.result) |
| 47 | +const m = new Monty('fetch_data(url)', { |
| 48 | + inputs: ['url'], |
| 49 | + externalFunctions: ['fetch_data'], |
| 50 | +}) |
| 51 | + |
| 52 | +const result = await runMontyAsync(m, { |
| 53 | + inputs: { url: 'https://example.com' }, |
| 54 | + externalFunctions: { |
| 55 | + fetch_data: async (url: string) => { |
| 56 | + const response = await fetch(url) |
| 57 | + return response.text() |
| 58 | + }, |
| 59 | + }, |
| 60 | +}) |
29 | 61 | ``` |
30 | 62 |
|
31 | | -## API |
| 63 | +## Iterative Execution |
| 64 | + |
| 65 | +For fine-grained control over external function calls, use `start()` and `resume()`: |
| 66 | + |
| 67 | +```ts |
| 68 | +const m = new Monty('a() + b()', { externalFunctions: ['a', 'b'] }) |
| 69 | + |
| 70 | +let progress = m.start() |
| 71 | +while (progress instanceof MontySnapshot) { |
| 72 | + console.log(`Calling: ${progress.functionName}`) |
| 73 | + console.log(`Args: ${progress.args}`) |
| 74 | + // Provide the return value and resume |
| 75 | + progress = progress.resume({ returnValue: 10 }) |
| 76 | +} |
| 77 | +// progress is now MontyComplete |
| 78 | +console.log(progress.output) // 20 |
| 79 | +``` |
| 80 | + |
| 81 | +## Error Handling |
| 82 | + |
| 83 | +```ts |
| 84 | +import { Monty, MontySyntaxError, MontyRuntimeError, MontyTypingError } from '@pydantic/monty' |
| 85 | + |
| 86 | +try { |
| 87 | + const m = new Monty('1 / 0') |
| 88 | + m.run() |
| 89 | +} catch (error) { |
| 90 | + if (error instanceof MontySyntaxError) { |
| 91 | + console.log('Syntax error:', error.message) |
| 92 | + } else if (error instanceof MontyRuntimeError) { |
| 93 | + console.log('Runtime error:', error.message) |
| 94 | + console.log('Traceback:', error.traceback()) |
| 95 | + } else if (error instanceof MontyTypingError) { |
| 96 | + console.log('Type error:', error.displayDiagnostics()) |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +## Type Checking |
| 102 | + |
| 103 | +```ts |
| 104 | +const m = new Monty('"hello" + 1') |
| 105 | +try { |
| 106 | + m.typeCheck() |
| 107 | +} catch (error) { |
| 108 | + if (error instanceof MontyTypingError) { |
| 109 | + console.log(error.displayDiagnostics('concise')) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +// Or enable during construction |
| 114 | +const m2 = new Monty('1 + 1', { typeCheck: true }) |
| 115 | +``` |
| 116 | + |
| 117 | +## Resource Limits |
| 118 | + |
| 119 | +```ts |
| 120 | +const m = new Monty('1 + 1') |
| 121 | +const result = m.run({ |
| 122 | + limits: { |
| 123 | + maxAllocations: 10000, |
| 124 | + maxDurationSecs: 5, |
| 125 | + maxMemory: 1024 * 1024, // 1MB |
| 126 | + maxRecursionDepth: 100, |
| 127 | + }, |
| 128 | +}) |
| 129 | +``` |
| 130 | + |
| 131 | +## Serialization |
| 132 | + |
| 133 | +```ts |
| 134 | +// Save parsed code to avoid re-parsing |
| 135 | +const m = new Monty('complex_code()') |
| 136 | +const data = m.dump() |
| 137 | + |
| 138 | +// Later, restore without re-parsing |
| 139 | +const m2 = Monty.load(data) |
| 140 | +const result = m2.run() |
| 141 | + |
| 142 | +// Snapshots can also be serialized |
| 143 | +const snapshot = m.start() |
| 144 | +if (snapshot instanceof MontySnapshot) { |
| 145 | + const snapshotData = snapshot.dump() |
| 146 | + // Later, restore and resume |
| 147 | + const restored = MontySnapshot.load(snapshotData) |
| 148 | + const result = restored.resume({ returnValue: 42 }) |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +## API Reference |
| 153 | + |
| 154 | +### `Monty` Class |
| 155 | + |
| 156 | +- `constructor(code: string, options?: MontyOptions)` - Parse Python code |
| 157 | +- `run(options?: RunOptions)` - Execute and return the result |
| 158 | +- `start(options?: StartOptions)` - Start iterative execution |
| 159 | +- `typeCheck(prefixCode?: string)` - Perform static type checking |
| 160 | +- `dump()` - Serialize to binary format |
| 161 | +- `Monty.load(data)` - Deserialize from binary format |
| 162 | +- `scriptName` - The script name (default: `'main.py'`) |
| 163 | +- `inputs` - Declared input variable names |
| 164 | +- `externalFunctions` - Declared external function names |
| 165 | + |
| 166 | +### `MontyOptions` |
| 167 | + |
| 168 | +- `scriptName?: string` - Name used in tracebacks (default: `'main.py'`) |
| 169 | +- `inputs?: string[]` - Input variable names |
| 170 | +- `externalFunctions?: string[]` - External function names |
| 171 | +- `typeCheck?: boolean` - Enable type checking on construction |
| 172 | +- `typeCheckPrefixCode?: string` - Code to prepend for type checking |
| 173 | + |
| 174 | +### `RunOptions` |
| 175 | + |
| 176 | +- `inputs?: object` - Input variable values |
| 177 | +- `limits?: ResourceLimits` - Resource limits |
| 178 | +- `externalFunctions?: object` - External function callbacks |
| 179 | + |
| 180 | +### `ResourceLimits` |
| 181 | + |
| 182 | +- `maxAllocations?: number` - Maximum heap allocations |
| 183 | +- `maxDurationSecs?: number` - Maximum execution time in seconds |
| 184 | +- `maxMemory?: number` - Maximum heap memory in bytes |
| 185 | +- `gcInterval?: number` - Run GC every N allocations |
| 186 | +- `maxRecursionDepth?: number` - Maximum call stack depth (default: 1000) |
| 187 | + |
| 188 | +### `MontySnapshot` Class |
| 189 | + |
| 190 | +Returned by `start()` when execution pauses at an external function call. |
| 191 | + |
| 192 | +- `scriptName` - The script being executed |
| 193 | +- `functionName` - The external function being called |
| 194 | +- `args` - Positional arguments |
| 195 | +- `kwargs` - Keyword arguments |
| 196 | +- `resume(options: ResumeOptions)` - Resume with return value or exception |
| 197 | +- `dump()` / `MontySnapshot.load(data)` - Serialization |
| 198 | + |
| 199 | +### `MontyComplete` Class |
| 200 | + |
| 201 | +Returned by `start()` or `resume()` when execution completes. |
| 202 | + |
| 203 | +- `output` - The final result value |
| 204 | + |
| 205 | +### Error Classes |
32 | 206 |
|
33 | | -- `run(code: string): { output: string, result: string }` — execute Python code |
34 | | - in a sandboxed Monty VM. `output` contains captured `print()` output; `result` |
35 | | - is the debug (`{:?}`) representation of the last expression's value. |
| 207 | +- `MontyError` - Base class for all Monty errors |
| 208 | +- `MontySyntaxError` - Syntax/parsing errors |
| 209 | +- `MontyRuntimeError` - Runtime exceptions (with `traceback()`) |
| 210 | +- `MontyTypingError` - Type checking errors (with `displayDiagnostics()`) |
0 commit comments