Skip to content

Build a polyglot app

Combine an npm package and a PyPI package in one Elide program: pass values, functions and errors between JavaScript and Python with --interop.

On this page

This page builds one program that uses an npm package from JavaScript and a PyPI package from Python, in one process.

NeedsHow
Pythonelide setup python; elide info lists python under Languages
Cross-language calls--interop on every run; there is no manifest setting
npm packagesdependencies.npm in elide.pkl, then elide install --ecosystems=npm
PyPI packagesA Python 3.13 virtual environment, selected with VIRTUAL_ENV

Set up the project

// elide.pkl
amends "elide:project.pkl"

name = "poly"

dependencies {
  npm { packages { "pretty-ms@9.3.1" } }
  pypi { packages { "humanize==4.12.3" } }
}
elide install --ecosystems=npm

A bare elide install installs Maven only. Elide does not install PyPI packages: elide install --ecosystems=pypi exits 2 with pypi installs are not available in this build. Keep the pypi block as the record, and install it with a Python 3.13 pip into a virtual environment:

python3.13 -m venv .venv
.venv/bin/pip install humanize==4.12.3
export VIRTUAL_ENV=$PWD/.venv

Elide loads packages from $VIRTUAL_ENV/lib/python3.13/site-packages. Pure-Python packages work; some C extension modules do not load (see Python).

Write the program

# report.py
import humanize

def size(n):
    return humanize.naturalsize(n)

def summarize(files):
    total = sum(f.bytes for f in files)
    return {"count": len(files), "total": humanize.naturalsize(total)}
// app.mjs
import prettyMs from "pretty-ms";
import report from "./report.py";

const files = [
  { name: "build.log", bytes: 1_500_000, ageMs: 172_800_000 },
  { name: "test.log", bytes: 32_000, ageMs: 5_400_000 },
];

for (const f of files) {
  console.log(`${f.name}: ${report.size(f.bytes)}, ${prettyMs(f.ageMs)} old`);
}

const summary = report.summarize(files);
console.log(`${summary.count} files, ${summary.total}`);
elide --interop run app.mjs
build.log: 1.5 MB, 2d old
test.log: 32.0 kB, 1h 30m old
2 files, 1.5 MB

A Python file’s default export is its module; import it from an .mjs entry. Two common failures:

MissingError
--interopError: Cannot import 'report.py': JavaScript entries run without Python. Re-run with --interop to use it.
VIRTUAL_ENVModuleNotFoundError: No module named 'humanize'

Pass values

# shapes.py
def label(order):
    return f"{order.item} x{order.qty}"

def doubled(prices):
    return [p * 2 for p in prices]

def config():
    return {"region": "eu", "retries": 3}

def apply(fn, value):
    return fn(value)

def fail():
    raise ValueError("bad input")
// values.mjs
import shapes from "./shapes.py";

console.log(shapes.label({ item: "bolt", qty: 4 }));
console.log(shapes.doubled([1, 2.5, 3]).map((n) => n + 1));
const config = shapes.config();
console.log(config.region, config.get("retries"));
console.log(JSON.stringify(Object.fromEntries(config.items())));
console.log(shapes.apply((s) => s.toUpperCase(), "shout"));
console.log(shapes.apply((v) => v, null), shapes.apply((v) => v, undefined));
elide --interop run values.mjs
bolt x4
(3)[3, 6, 7]
eu 3
{"region":"eu","retries":3}
SHOUT
null undefined
ValueFrom JavaScript into PythonFrom Python into JavaScript
Numbers, strings, booleansNative valuesNative values
null, undefined / NoneNone, and back unchangednull
Object / dictRead fields as attributes (order.item); order["item"] raises TypeErrorRead keys as d.key, d["key"] or d.get("key"); Object.keys, in and JSON.stringify see the dict’s methods, not its keys. Convert with Object.fromEntries(d.items()).
Array / listIterable, indexable, len() worksArray.isArray is true; map, spread and Array.from work
FunctionCallableCallable

Call JavaScript from Python

The elide module evaluates JavaScript and returns its value:

# calls.py
from elide import js

slug = js("(s) => s.trim().toLowerCase().replaceAll(' ', '-')")
print(slug("  Hello Polyglot World "))

point = js("({ x: 1, y: 2, tags: ['a', 'b'] })")
print(point.x + point.y, point.tags[0], len(point.tags))
print(sorted(js("[3, 1, 2]")))

try:
    js("JSON.parse('{oops')")
except RuntimeError as e:
    print("caught:", e)

js("Promise.resolve(42)").then(lambda v: print("resolved:", v))
elide --interop python calls.py
hello-polyglot-world
3 a 2
[1, 2, 3]
caught: JSException: SyntaxError: Unexpected token o in JSON at position 1
resolved: 42

Handle errors

A Python exception reaches JavaScript as an Error whose name is the exception class; a JavaScript error reaches Python as RuntimeError. An uncaught error prints one stack across both languages:

// errors.mjs
import shapes from "./shapes.py";

try {
  shapes.fail();
} catch (e) {
  console.log(e.name, "|", e.message);
}

shapes.apply((x) => {
  throw new RangeError(`bad value ${x}`);
}, 7);
elide --interop run errors.mjs
ValueError | ValueError: bad input
RangeError: bad value 7
    at :=> (errors.mjs:10:9)
    at apply (shapes.py:11:12)
    at :module:eval (errors.mjs:9:1)

The program exits 1.

Call async code

FromWorksDoes not work
JavaScript calling PythonCall a sync wrapper that runs asyncio.run(...)await on a Python coroutine returns the coroutine, unrun
Python holding a JavaScript promisepromise.then(callback)await promise raises TypeError: object polyglot.ForeignObject can't be used in 'await' expression
# rates.py
import asyncio

async def fetch_rate(code):
    await asyncio.sleep(0.01)
    return {"EUR": 0.92, "GBP": 0.79}[code]

def rate(code):
    return asyncio.run(fetch_rate(code))

def when_ready(promise, done):
    promise.then(lambda value: done(f"python saw {value}"))
// rates.mjs
import rates from "./rates.py";

console.log("rate:", rates.rate("EUR"));

const total = new Promise((resolve) => setTimeout(() => resolve(7), 10));
rates.when_ready(total, (msg) => console.log(msg));
await total;
elide --interop run rates.mjs
rate: 0.92
python saw 7

asyncio.run blocks the JavaScript event loop until the coroutine finishes. Python threads are not available under --interop.

Java and Kotlin

elide run Hello.kt runs Kotlin on its own; JavaScript and Python cannot call it. Importing it fails:

import hello from "./Hello.kt";
TypeError: Unsupported file extension: 'file:/path/to/Hello.kt'

See also Languages and Interop, Python and NPM Installer.

Samples

Was this page helpful?