Run JavaScript code from Python.
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fb81c58bbdeea01d210cae68bafadf4d620bbcfabe11a6aa6c47158c18cfd15
|
|
| MD5 |
7d31ace976d240926566383f5b5e5ade
|
|
| BLAKE2b-256 |
bf29a30a35ebfd34021f648209fa4c9be4bbff1c3f2e51d02eea5e11d13772f6
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5536447b61323bf941497b16bea0f5b4705e0eb61509f35e85437026ea910e3
|
|
| MD5 |
79e40e7e68ea56f3a3efb9d8f8e887c2
|
|
| BLAKE2b-256 |
cdd48a02500810353b8c4c6428ee11483eeecfc715beea719ccdc213acc1edf1
|