Run JavaScript code from Python.「髯祭司」是一个旨在使用python运行javascript的库。
See the codeRun JavaScript from Python — the modern successor to PyExecJS.
Zero dependencies · Multi-runtime auto-detection · Async-ready · No temp files · Timeout guard
pip install exejs and you're done.evaluate_async / execute_async / call_async built on asyncio.JScript), so it won't trigger antivirus alerts.timeout parameter kills runaway scripts instead of hanging forever.call() auto JSON-serializes objects and supports property paths like calc.mul.py.typed, fully annotated, Python 3.9+.PyExecJS has been EOL since 2018. ExeJS exists to fix three concrete pain points:
| 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 |
PATH — Node.js is the recommended default. See Supported Runtimes for the full list and install hints.# 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 .
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.
| 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.
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
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
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]
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
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
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 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 (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)
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'
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('')
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.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.evaluate / execute block the calling thread. Inside an async framework use evaluate_async / execute_async instead so the event loop stays responsive.See change_log.md.
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.
JSON.ExeJS is a core dependency of Translators, where it handles the JavaScript execution layer that translation engines rely on.
Apache-2.0 © UlionTse
Run JavaScript code from Python.「髯祭司」是一个旨在使用python运行javascript的库。
See the codeRun JavaScript from Python — the modern successor to PyExecJS.
Zero dependencies · Multi-runtime auto-detection · Async-ready · No temp files · Timeout guard
pip install exejs and you're done.evaluate_async / execute_async / call_async built on asyncio.JScript), so it won't trigger antivirus alerts.timeout parameter kills runaway scripts instead of hanging forever.call() auto JSON-serializes objects and supports property paths like calc.mul.py.typed, fully annotated, Python 3.9+.PyExecJS has been EOL since 2018. ExeJS exists to fix three concrete pain points:
| 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 |
PATH — Node.js is the recommended default. See Supported Runtimes for the full list and install hints.# 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 .
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.
| 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.
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
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
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]
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
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
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 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 (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)
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'
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('')
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.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.evaluate / execute block the calling thread. Inside an async framework use evaluate_async / execute_async instead so the event loop stays responsive.See change_log.md.
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.
JSON.ExeJS is a core dependency of Translators, where it handles the JavaScript execution layer that translation engines rely on.
Apache-2.0 © UlionTse