Package: pythonmonkey
PythonMonkeyAboutPythonMonkeyis a MozillaSpiderMonkeyJavaScript engine embedded into the Python Runtime,
using the Python engine to provide the Javascript host environment.We feature JavaScript Array and Object methods implemented on Python List and Dictionaries using the cPython C API, and the inverse using the Mozilla Firefox Spidermonkey JavaScript C++ API.This product is in an intermediate stage, approximately 90% to MVP as of January 2024. It is under active development byDistributive.
External contributions and feedback are welcome and encouraged.tl;dr$pipinstallpythonmonkeyfrompythonmonkeyimportevalasjs_evaljs_eval("console.log")('hello, world')GoalsFastand memory-efficientMake writing code in either JS or Python a developer preferenceUse JavaScript libraries from PythonUse Python libraries from JavaScriptThe same process runs both JavaScript and Python VirtualMachines - no serialization, pipes, etcPython Lists and Dicts behave as Javacript Arrays and Objects, and vice-versa, fully adapting to the given context.Data InterchangeStrings share immutable backing stores whenever possible (when allocating engine choses UCS-2 or Latin-1 internal string representation) to keep memory consumption under control, and to make it possible to move very large strings between JavaScript and Python library code without memory-copy overhead.TypedArrays share mutable backing stores.JS objects are represented by Python dicts through a Dict subclass for optimal compatibility. Similarly for JS arrays and Python lists.JS Date objects are represented by Python datetime.datetime objectsIntrinsics (boolean, number, null, undefined) are passed by valueJS Functions are automatically wrapped so that they behave like Python functions, and vice-versaPython Lists are represented by JS true arrays and support all Array methods through a JS API Proxy. Similarly for Python Dicts and JS objects.Roadmap[done] JS instrinsics coerce to Python intrinsics[done] JS strings coerce to Python strings[done] JS objects coerce to Python dicts [own-properties only][done] JS functions coerce to Python function wrappers[done] JS exceptions propagate to Python[done] Implementeval()function in Python which accepts JS code and returns JS->Python coerced values[done] NodeJS+NPM-compatible CommonJS module system[done] Python strings coerce to JS strings[done] Python intrinsics coerce to JS intrinsics[done] Python dicts coerce to JS objects[done] Pythonrequirefunction, returns a coerced dict of module exports[done] Python functions coerce to JS function wrappers[done] CommonJS module system .py loader, loads Python modules for use by JS[done] Python host environment supplies event loop, including EventEmitter, setTimeout, etc.[done] Python host environment supplies XMLHttpRequestPython host environment supplies basic subsets of NodeJS's fs, path, process, etc, modules; as-needed by dcp-client[done] Python TypedArrays coerce to JS TypeArrays[done] JS TypedArrays coerce to Python TypeArrays[done] Python lists coerce to JS Arrays[done] JS arrays coerce to Python listsBuild InstructionsRead this if you want to build a local version.You will need the following installed (which can be done automatically by running./setup.sh):bashcmakeDoxygen 1.9 seriesgraphvizllvmrustpython3.8 or later with header files (python3-dev)spidermonkey 115.1.0 or laternpm (nodejs)Poetrypoetry-dynamic-versioningRunpoetry install. This command automatically compiles the project and installs the project as well as dependencies into the poetry virtualenv.If you are using VSCode, you can just pressCtrl+Shift+Btorun build task- We havethetasks.jsonfile configured for you.Running testsCompile the projectInstall development dependencies:poetry install --no-root --only=devFrom the root directory, runpoetry run pytest ./tests/pythonFrom the root directory, runpoetry run bash ./peter-jr ./tests/js/For VSCode users, similar to the Build Task, we have a Test Task ready to use.Using the librarynpm (Node.js) is requiredduring installation onlyto populate the JS dependencies.Install fromPyPI$pipinstallpythonmonkeyInstall thenightly build$pipinstall--extra-index-urlhttps://nightly.pythonmonkey.io/--prepythonmonkeyUse local versionpythonmonkeyis available in the poetry virtualenv once you compiled the project using poetry.$poetryrunpythonPython3.10.6(main,Nov142022,16:10:14)[GCC11.3.0]onlinuxType"help","copyright","credits"or"license"formoreinformation.>>>importpythonmonkeyaspm>>>hello=pm.eval("() => {return 'Hello from Spidermonkey!'}")>>>hello()'Hello from Spidermonkey!'Alternatively, you can build installable packages by running$cdpython/pminit&&poetrybuild--format=sdist&&cd-&&mv-vpython/pminit/dist/*./dist/
$poetrybuild--format=wheeland install them bypip install ./dist/*.Debugging Stepsbuild the project locallyTo use gdb, runpoetry run gdb python.SeePython Wiki: DebuggingWithGdbIf you are using VSCode, it's more convenient to debug inVSCode's built-in debugger. Simply pressF5on an open Python file in the editor to start debugging - We havethelaunch.jsonfile configured for you.Examplesexamples/https://github.com/Distributive-Network/PythonMonkey-exampleshttps://github.com/Distributive-Network/PythonMonkey-Crypto-JS-Fullstack-ExampleAPIThese methods are exported from the pythonmonkey module.eval(code, evalOpts)isCompilableUnit(code)collect()bigint(int)SpiderMonkeyErrorJSObjectProxynullSee definitions inpython/pythonmonkey/pythonmonkey.pyi.require(moduleIdentifier)Return the exports of a CommonJS module identified bymoduleIdentifier, using standard CommonJS
semanticsmodules are singletons and will never be loaded or evaluated more than oncemoduleIdentifier is relative to the Python file invokingrequiremoduleIdentifier should not include a file extensionmoduleIdentifiers which do not begin with ./, ../, or / are resolved by search require.path
and module.paths.Modules are evaluated immediately after loadingModules are not loaded until they are requiredThe following extensions are supported:.js- JavaScript module; source code decoratesexportsobject.py- Python module; source code decoratesexportsdict.json- JSON module; exports are the result of parsing the JSON text in the fileglobalThisA Python Dict which is equivalent to the globalThis object in JavaScript.createRequire(filename, extraPaths, isMain)Factory function which returns a new require functionfilename: the pathname of the module that this require function could be used forextraPaths: [optional] a list of extra paths to search to resolve non-relative and non-absolute module identifiersisMain: [optional] True if the require function is being created for a main modulerunProgramModule(filename, argv, extraPaths)Load and evaluate a program (main) module. Program modules must be written in JavaScript. Program modules are not
necessary unless the main entry point of your program is written in JavaScript.filename: the location of the JavaScript source codeargv: the program's argument vectorextraPaths: [optional] a list of extra paths to search to resolve non-relative and non-absolute module identifiersCare should be taken to ensure that only one program module is run per JS context.Built-In FunctionsSee definitions inpython/pythonmonkey/global.d.ts.consoleatobbtoasetTimeoutclearTimeoutCommonJS Subsystem AdditionsThe CommonJS subsystem is activated by invoking therequireorcreateRequireexports of the (Python)
pythonmonkey module.requireexportsmodule__filename__dirnamepython.print- the Python print functionpython.getenv- the Python getenv functionpython.stdout- an object withreadandwritemethods, which read and write to stdoutpython.stderr- an object withreadandwritemethods, which read and write to stderrpython.exec- the Python exec functionpython.eval- the Python eval functionpython.exit- the Python exit function (wrapped to return BigInt in place of number)python.paths- the Python sys.paths list (currently a copy; will become an Array-like reflection)Type Transfer (Coercion / Wrapping)When sending variables from Python into JavaScript, PythonMonkey will intelligently coerce or wrap your
variables based on their type. PythonMonkey will share backing stores (use the same memory) for ctypes,
typed arrays, and strings; moving these types across the language barrier is extremely fast because
there is no copying involved.Note:There are plans in Python 3.12 (PEP 623) to change the internal string representation so that
every character in the string uses four bytes of memory. This will break fast string transfers
for PythonMonkey, as it relies on the memory layout being the same in Python and JavaScript. As
of this writing (July 2023), "classic" Python strings still work in the 3.12 beta releases.Where shared backing store is not possible, PythonMonkey will automatically emit wrappers that use
the "real" data structure as its value authority. Only immutable intrinsics are copied. This means
that if you update an object in JavaScript, the corresponding Dict in Python will be updated, etc.JavaScript Array and Object methods are implemented on Python List and Dictionaries, and vice-versa.Python TypeJavaScript TypeStringstringIntegernumberBoolbooleanFunctionfunctionDictobjectListArraydatetimeDate objectawaitablePromiseErrorError objectBufferArrayBufferJavaScript TypePython TypestringStringnumberFloatbigintpythonmonkey.bigint (Integer)booleanBoolfunctionFunctionobject - mostpythonmonkey.JSObjectProxy (Dict)object - Datedatetimeobject - Arraypythonmonkey.JSArrayProxy (List)object - Promiseawaitableobject - ArrayBufferBufferobject - type arraysBufferobject - ErrorErrorTricksInteger Type CoercionYou can force a number in JavaScript to be coerced as an integer by casting it to BigInt:functionmyFunction(a,b){constresult=calculate(a,b);returnBigInt(Math.floor(result));}Thepythonmonkey.bigintobject works like an int in Python, but it will be coerced as a BigInt in JavaScript:importpythonmonkeydeffnmyFunction()result=5returnpythonmonkey.bigint(result)Symbol injection via cross-language IIFEYou can use a JavaScript IIFE to create a scope in which you can inject Python symbols:globalThis.python.exit=pm.eval("""'use strict';(exit) => function pythonExitWrapper(exitCode) {if (typeof exitCode === 'number')exitCode = BigInt(Math.floor(exitCode));exit(exitCode);}""")(sys.exit);Run Python event-loopYou need an event-loop running to usesetTimeoutandPromise<=>awaitablecoercion.importasyncioasyncdefasync_fn():awaitpm.eval("""new Promise((resolve) => setTimeout((...args) => {console.log(args);resolve();}, 1000, 42, "abc"))""")awaitpm.eval("async (x) => await x")(asyncio.sleep(0.5))asyncio.run(async_fn())pmjsA basic JavaScript shell,pmjs, ships with PythonMonkey. This shell can act as a REPL or run
JavaScript programs; it is conceptually similar to thenodeshell which ships with Node.js.ModulesPmjs starts PythonMonkey's CommonJS subsystem, which allow it to use CommonJS modules, with semantics
that are similar to Node.js - e.g. searching module.paths, understanding package.json, index.js, and
so on. See thectx-modulefor a full list of supported
features.In addition to CommonJS modules written in JavaScript, PythonMonkey supports CommonJS modules written
in Python. Simply decorate a Dict namedexportsinside a file with a.pyextension, and it can be
loaded byrequire()-- in either JavaScript or Python.Program ModuleThe program module, or main module, is a special module in CommonJS. In a program module,variables defined in the outermost scope are properties ofglobalThisreturning from the outermost scope is a syntax errortheargumentsvariable in an Array-like object which holds your program's argument vector
(command-line arguments)$echo"console.log('hello world')">my-program.js$pmjsmy-program.jshello world$CommonJS Module: JavaScript language// date-lib.js - require("./date-lib")constd=newDate();exports.today=`${d.getFullYear()}-${String(d.getMonth()).padStart(2,'0')}-${String(d.getDay()).padStart(2,'0')}`CommonJS Module: Python language# date-lib.py - require("./date-lib")fromdatetimeimportdate# You can use Python libraries.exports['today']=date.today()Troubleshooting TipsCommonJS (require)If you are having trouble with the CommonJS require function, set environment variableDEBUG='ctx-module*'and you can see the filenames it tries to laod.pmdbPythonMonkey has a built-in gdb-like JavaScript command-line debugger calledpmdb, which would be automatically triggered ondebugger;statements and uncaught exceptions.To enablepmdb, simply callfrom pythonmonkey.lib import pmdb; pmdb.enable()before doing anything on PythonMonkey.importpythonmonkeyaspmfrompythonmonkey.libimportpmdbpmdb.enable()pm.eval("...")Runhelpcommand inpmdbto see available commands.(pmdb)> helpList of commands:• ...• ...pmjsthere is a.helpmenu in the REPLthere is a--helpcommand-line optionthe--inspectoption enablespmdb, a gdb-like JavaScript command-line debuggerthe-roption can be used to load a module before your program or the REPL runsthe-eoption can be used evaluate code -- e.g. define global variables -- before your program or the REPL runsThe REPL can evaluate Python expressions, storing them in variables named$1,$2, etc.$pmjsWelcome to PythonMonkey v0.2.0.Type ".help" for more information.> .python import sys> .python sys.path$1={'0':'/home/wes/git/pythonmonkey2','1': '/usr/lib/python310.zip','2': '/usr/lib/python3.10','3': '/usr/lib/python3.10/lib-dynload','4': '/home/wes/.cache/pypoetry/virtualenvs/pythonmonkey-StuBmUri-py3.10/lib/python3.10/site-packages','5': '/home/wes/git/pythonmonkey2/python' }> $1[3]'/usr/lib/python3.10/lib-dynload'>