Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/monty-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ For async external functions, use `runMontyAsync()`:
```ts
import { Monty, runMontyAsync } from '@pydantic/monty'

const m = new Monty('fetch_data(url)', {
const m = new Monty('await fetch_data(url)', {
inputs: ['url'],
})

Expand All @@ -61,7 +61,8 @@ const result = await runMontyAsync(m, {

## Iterative Execution

For fine-grained control over external function calls, use `start()` and `resume()`:
For fine-grained control over external function calls, use `start()`, `resume()`,
`resumePending()`, and `MontyResolveFutures.resume()`:

```ts
const m = new Monty('a() + b()')
Expand Down
77 changes: 63 additions & 14 deletions crates/monty-js/__test__/async.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test('runMontyAsync with sync external function', async (t) => {
})

test('runMontyAsync with async external function', async (t) => {
const m = new Monty('fetch_data()')
const m = new Monty('await fetch_data()')

const result = await runMontyAsync(m, {
externalFunctions: {
Expand All @@ -37,9 +37,9 @@ test('runMontyAsync with async external function', async (t) => {
test('runMontyAsync with multiple async calls', async (t) => {
const m = new Monty(
`
a = fetch_a()
b = fetch_b()
a + b
import asyncio
results = await asyncio.gather(fetch_a(), fetch_b())
results[0] + results[1]
`,
{},
)
Expand All @@ -60,8 +60,57 @@ a + b
t.is(result, 30)
})

test('runMontyAsync runs gathered async external calls concurrently', async (t) => {
const m = new Monty(
`
import asyncio
results = await asyncio.gather(fetch(1), fetch(2))
results[0] + results[1]
`,
{},
)
let active = 0
let maxActive = 0

const result = await runMontyAsync(m, {
externalFunctions: {
fetch: async (n: number) => {
active += 1
maxActive = Math.max(maxActive, active)
await new Promise((resolve) => setTimeout(resolve, 10))
active -= 1
return n * 10
},
},
})

t.is(result, 30)
t.is(maxActive, 2)
})

test('runMontyAsync does not parse string output as an internal future marker', async (t) => {
const m = new Monty(
`
fetch()
'<coroutine external_future(0)>'
`,
{},
)

const result = await runMontyAsync(m, {
externalFunctions: {
fetch: async () => {
await new Promise((resolve) => setTimeout(resolve, 5))
return 'promise result'
},
},
})

t.is(result, '<coroutine external_future(0)>')
})

test('runMontyAsync with inputs', async (t) => {
const m = new Monty('multiply(x)', { inputs: ['x'] })
const m = new Monty('await multiply(x)', { inputs: ['x'] })

const result = await runMontyAsync(m, {
inputs: { x: 5 },
Expand All @@ -74,7 +123,7 @@ test('runMontyAsync with inputs', async (t) => {
})

test('runMontyAsync with args and kwargs', async (t) => {
const m = new Monty('process(1, 2, name="test")')
const m = new Monty('await process(1, 2, name="test")')

const result = await runMontyAsync(m, {
externalFunctions: {
Expand Down Expand Up @@ -112,7 +161,7 @@ test('runMontyAsync sync function throws exception', async (t) => {
})

test('runMontyAsync async function throws exception', async (t) => {
const m = new Monty('fail_async()')
const m = new Monty('await fail_async()')

class ValueError extends Error {
override name = 'ValueError'
Expand All @@ -136,7 +185,7 @@ test('runMontyAsync exception caught in try/except', async (t) => {
const m = new Monty(
`
try:
might_fail()
await might_fail()
except ValueError:
result = 'caught'
result
Expand Down Expand Up @@ -189,7 +238,7 @@ result
// =============================================================================

test('runMontyAsync returns complex types', async (t) => {
const m = new Monty('get_data()')
const m = new Monty('await get_data()')

const result = await runMontyAsync(m, {
externalFunctions: {
Expand All @@ -207,7 +256,7 @@ test('runMontyAsync returns complex types', async (t) => {
})

test('runMontyAsync with list input', async (t) => {
const m = new Monty('sum_list(items)', { inputs: ['items'] })
const m = new Monty('await sum_list(items)', { inputs: ['items'] })

const result = await runMontyAsync(m, {
inputs: { items: [1, 2, 3, 4, 5] },
Expand All @@ -229,7 +278,7 @@ test('runMontyAsync mixed sync and async functions', async (t) => {
const m = new Monty(
`
sync_result = sync_func()
async_result = async_func()
async_result = await async_func()
sync_result + async_result
`,
{},
Expand All @@ -251,9 +300,9 @@ sync_result + async_result
test('runMontyAsync chained async calls', async (t) => {
const m = new Monty(
`
first = get_first()
second = process(first)
finalize(second)
first = await get_first()
second = await process(first)
await finalize(second)
`,
{},
)
Expand Down
5 changes: 3 additions & 2 deletions crates/monty-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ mod mount;
pub use exceptions::{ExceptionInfo, Frame, JsMontyException, MontyTypingError};
pub use limits::JsResourceLimits;
pub use monty_cls::{
ExceptionInput, Monty, MontyComplete, MontyNameLookup, MontyOptions, MontyRepl, MontySnapshot,
NameLookupLoadOptions, NameLookupResumeOptions, ResumeOptions, RunOptions, SnapshotLoadOptions, StartOptions,
ExceptionInput, FutureResultInput, Monty, MontyComplete, MontyNameLookup, MontyOptions, MontyRepl,
MontyResolveFutures, MontySnapshot, NameLookupLoadOptions, NameLookupResumeOptions, ResolveFuturesLoadOptions,
ResolveFuturesResumeOptions, ResumeOptions, RunOptions, SnapshotLoadOptions, StartOptions,
};
pub use mount::{MountDir, MountDirOptions};
Loading
Loading