Skip to main content

Run JavaScript code from Python.

Project description

ExeJS

Run JavaScript from Python — the modern successor to PyExecJS.

Zero dependencies · Multi-runtime auto-detection · Async-ready · No temp files · Timeout guard

PyPI - Version Conda - Version PyPI - Python PyPI - License PyPI - Status PyPI - Wheel PyPI - Downloads


Table of Contents

Features

  • Multi-runtime — auto-detects among Node, JavaScriptCore, SpiderMonkey, JScript, PhantomJS, SlimerJS, and Nashorn.
  • Zero dependencies — built on the Python standard library; pip install exejs and you're done.
  • Async-readyevaluate_async / execute_async / call_async built on asyncio.
  • No temp files — compiles and runs via stdin (except JScript), so it won't trigger antivirus alerts.
  • Timeout guardtimeout parameter kills runaway scripts instead of hanging forever.
  • Structured argscall() auto JSON-serializes objects and supports property paths like calc.mul.
  • Typed — ships py.typed, fully annotated, Python 3.9+.

Why ExeJS

PyExecJS has been EOL since 2018. ExeJS exists to fix three concrete pain points:

  1. PyExecJS is unmaintained, and downstream builds that depend on it fail or get cancelled. (Issue#1, Issue#2)
  2. PyExecJS writes compiled code to a temp file by default, which triggers antivirus alerts and blocks execution. (Issue#3)
  3. No async API, no timeout control, still carries Python 2 baggage.

Comparison

PyExecJS (EOL 2018) ExeJS
Maintained stopped 2018 active
Python 2 yes no (3.9+)
Async API yes
Temp files yes (antivirus alerts) no (stdin, except JScript)
Timeout yes
Context reuse (compile/call) limited yes
Structured args (auto JSON, property paths) yes
Dependencies some (six) zero
Typed (py.typed) yes

Requirements

  • Python >= 3.9
  • A JavaScript runtime installed and on PATH — Node.js is the recommended default. See Supported Runtimes for the full list and install hints.

Installation

# PyPI
pip install --upgrade exejs

# Conda
conda install conda-forge::exejs

# From source
git clone https://github.com/UlionTse/exejs.git
cd exejs
pip install .

Quick Start

import exejs

exejs.evaluate("[1, 2, 3].map(x => x * 2)")  # [2, 4, 6]

# evaluate (one-shot expression)
exejs.evaluate("'red yellow blue'.split(' ')")  # ['red', 'yellow', 'blue']

# execute (one-shot statements; use `return` to send a value back)
exejs.execute('var x = 40 + 2; return x;')  # 42

For context reuse, structured args, async, and timeout, see Advanced.

Supported Runtimes

Runtime Command Engine Install / Notes
Node node Chrome (V8) recommended, all platforms — nodejs.org/download
NodeJS nodejs Chrome (V8) Debian/Ubuntu alias of Node
JavaScriptCore jsc Safari (WebKit) macOS only (bundled)
SpiderMonkey js Firefox (Gecko) js package on most Linux distros
Phantomjs phantomjs WebKit upstream abandoned 2018
SlimerJS slimerjs Gecko upstream abandoned 2021
Nashorn jjs Java (JVM) Oracle JDK; removed since JDK 15
JScript cscript //E:jscript //Nologo IE (Trident) Windows only (built-in); uses temp file

ExeJS auto-detects the first available runtime in the order above. Use reset_runtime to pick one explicitly.

API Reference

Top-level functions

exejs.evaluate(source: str, timeout: float | None = None) -> Any
exejs.execute(source: str, timeout: float | None = None) -> Any
exejs.compile(source: str = '', cwd: str | None = None) -> RuntimeCompileContext

async exejs.evaluate_async(source: str, timeout: float | None = None) -> Any
async exejs.execute_async(source: str, timeout: float | None = None) -> Any

Compile context

ctx = exejs.compile(source='', cwd=None)

ctx.call(key: str, *args, timeout: float | None = None) -> Any        # key supports paths like 'calc.mul'
ctx.evaluate(source: str, timeout: float | None = None) -> Any
ctx.execute(source: str, timeout: float | None = None) -> Any

async ctx.call_async(key: str, *args, timeout: float | None = None) -> Any
async ctx.evaluate_async(source: str, timeout: float | None = None) -> Any
async ctx.execute_async(source: str, timeout: float | None = None) -> Any

Runtime management

exejs.reset_runtime(name: str) -> None                       # switch runtime by name
exejs.get_current_runtime() -> Runtime
exejs.get_current_runtime_name() -> str
exejs.find_available_runtime() -> Runtime                    # first available
exejs.find_all_runtime_name_list(is_available: bool = True) -> list[str]

Exception hierarchy

ExejsError
├── ExejsRuntimeNameError          # unknown runtime name passed to reset_runtime
├── ExejsRuntimeUnavailableError   # no runtime found / runtime not on PATH
├── ExejsProcessExitError          # subprocess failed / non-zero exit
├── ExejsProgramError              # JS crashed or returned invalid JSON
└── ExejsTimeoutError              # timeout exceeded

Advanced

Compile & call (context reuse)

Reuse a single JS context across multiple calls instead of re-launching a runtime each time:

import exejs

ctx = exejs.compile('function add(x, y) { return x + y; }')
ctx.call('add', 1, 2)  # 3
ctx.call('add', 10, 20)  # 30

Structured arguments

call() auto JSON-serializes arguments and supports property paths as the key:

# call an object method (key may be a property path)
ctx = exejs.compile('var calc = { mul: function(a, b) { return a * b; } };')
ctx.call('calc.mul', 3, 4)  # 12

# call with a dict argument (auto JSON-serialized)
ctx = exejs.compile('function greet(u) { return "hi " + u.name + ", age " + u.age; }')
ctx.call('greet', {'name': 'Tom', 'age': 18})  # 'hi Tom, age 18'

Async API

Async is useful inside async web frameworks (FastAPI, aiohttp) or when you want to avoid blocking the event loop:

import asyncio
import exejs

asyncio.run(exejs.evaluate_async("'red yellow blue'.split(' ')"))

Timeout

timeout (seconds) applies to both sync and async variants. On expiry the subprocess is killed and ExejsTimeoutError is raised:

try:
    exejs.execute('while (true) {}', timeout=2.0)
except exejs.ExejsTimeoutError as e:
    print('killed:', e)

Choosing a runtime

import exejs

# see what's available on this machine
print(exejs.find_all_runtime_name_list())  # e.g. ['Node', 'JScript']

# force a specific one
exejs.reset_runtime('Node')
print(exejs.get_current_runtime_name())  # 'Node'

Working directory

compile(source, cwd=...) sets the subprocess working directory, useful when your JS reads relative files:

ctx = exejs.compile('return require("./config.json")', cwd='/path/to/project')
ctx.evaluate('')

FAQ

  • ExejsRuntimeUnavailableError: Unable to find available javascript runtime — No JS runtime is installed or none is on PATH. Install Node.js from nodejs.org and reopen your terminal, or call exejs.reset_runtime('JScript') on Windows where cscript is built in.
  • Antivirus blocks execution — If you are using the JScript runtime (Windows), it still writes a temp file which may trigger antivirus. Switch to Node (exejs.reset_runtime('Node')) to avoid this. All other runtimes use stdin and should not be affected.
  • cscript not recognized on Windows — JScript is provided by Windows Script Host. Ensure cscript.exe exists (usually at C:\Windows\System32\) and is on PATH; on stripped-down Windows images you may need to enable the Windows Script Host feature.
  • Blocks the event loop in asyncio code — The sync evaluate / execute block the calling thread. Inside an async framework use evaluate_async / execute_async instead so the event loop stays responsive.

Changelog

See change_log.md.

Contributing

Pull requests are welcome. To set up a dev environment:

git clone https://github.com/UlionTse/exejs.git
cd exejs
pip install -e .
pip install pytest
pytest exejs/tests

Run pytest exejs/tests before submitting a PR. Code style follows PEP 8. Please open an issue first to discuss any non-trivial change.

Acknowledgements

Notable Usage

ExeJS is a core dependency of Translators, where it handles the JavaScript execution layer that translation engines rely on.

License

Apache-2.0 © UlionTse

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

exejs-1.0.1.tar.gz (24.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

exejs-1.0.1-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

Details for the file exejs-1.0.1.tar.gz.

File metadata

  • Download URL: exejs-1.0.1.tar.gz
  • Upload date:
  • Size: 24.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for exejs-1.0.1.tar.gz
Algorithm Hash digest
SHA256 4fb81c58bbdeea01d210cae68bafadf4d620bbcfabe11a6aa6c47158c18cfd15
MD5 7d31ace976d240926566383f5b5e5ade
BLAKE2b-256 bf29a30a35ebfd34021f648209fa4c9be4bbff1c3f2e51d02eea5e11d13772f6

See more details on using hashes here.

File details

Details for the file exejs-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: exejs-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 15.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for exejs-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d5536447b61323bf941497b16bea0f5b4705e0eb61509f35e85437026ea910e3
MD5 79e40e7e68ea56f3a3efb9d8f8e887c2
BLAKE2b-256 cdd48a02500810353b8c4c6428ee11483eeecfc715beea719ccdc213acc1edf1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page