Run JavaScript from Python — the modern successor to PyExecJS.
Zero dependencies · Multi-runtime auto-detection · Async-ready · No temp files · Timeout guard
Table of Contents
- Features
- Why ExeJS
- Comparison
- Requirements
- Installation
- Quick Start
- Supported Runtimes
- API Reference
- Advanced
- FAQ
- Changelog
- Contributing
- Acknowledgements
- Notable Usage
- License
Features
- Multi-runtime — auto-detects among Node, JavaScriptCore, SpiderMonkey, JScript, PhantomJS, SlimerJS, and Nashorn.
- Zero dependencies — built on the Python standard library;
pip install exejsand you're done. - Async-ready —
evaluate_async/execute_async/call_asyncbuilt onasyncio. - No temp files — compiles and runs via stdin (except
JScript), so it won't trigger antivirus alerts. - Timeout guard —
timeoutparameter kills runaway scripts instead of hanging forever. - Structured args —
call()auto JSON-serializes objects and supports property paths likecalc.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:
- PyExecJS is unmaintained, and downstream builds that depend on it fail or get cancelled. (Issue#1, Issue#2)
- PyExecJS writes compiled code to a temp file by default, which triggers antivirus alerts and blocks execution. (Issue#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 onPATH. Install Node.js from nodejs.org and reopen your terminal, or callexejs.reset_runtime('JScript')on Windows wherecscriptis built in.- Antivirus blocks execution — If you are using the
JScriptruntime (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. cscriptnot recognized on Windows — JScript is provided by Windows Script Host. Ensurecscript.exeexists (usually atC:\Windows\System32\) and is onPATH; 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/executeblock the calling thread. Inside an async framework useevaluate_async/execute_asyncinstead 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
- PyExecJS — the original project ExeJS succeeds.
- JSON-js (json2.js) — embedded for legacy runtimes without native
JSON.
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
Metadata
Release files for exejs 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| exejs-1.0.1.tar.gz | 24.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| exejs-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.9 kB
Release files / exejs-1.0.1.tar.gz
| Download URL | exejs-1.0.1.tar.gz |
|---|---|
| Size | 24.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4fb81c58bbdeea01d210cae68bafadf4d620bbcfabe11a6aa6c47158c18cfd15
|
|
BLAKE2b-256 checksum How to use checksums |
bf29a30a35ebfd34021f648209fa4c9be4bbff1c3f2e51d02eea5e11d13772f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|
Release files / exejs-1.0.1-py3-none-any.whl
| Download URL | exejs-1.0.1-py3-none-any.whl |
|---|---|
| Size | 15.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d5536447b61323bf941497b16bea0f5b4705e0eb61509f35e85437026ea910e3
|
|
BLAKE2b-256 checksum How to use checksums |
cdd48a02500810353b8c4c6428ee11483eeecfc715beea719ccdc213acc1edf1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|