{"id":13484651,"url":"https://github.com/extremeheat/JSPyBridge","last_synced_at":"2025-03-27T16:31:06.110Z","repository":{"id":38187147,"uuid":"377026887","full_name":"extremeheat/JSPyBridge","owner":"extremeheat","description":"🌉. Bridge to interoperate Node.js and Python ","archived":false,"fork":false,"pushed_at":"2024-05-29T01:28:16.000Z","size":1159,"stargazers_count":622,"open_issues_count":37,"forks_count":44,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-05-29T13:26:31.418Z","etag":null,"topics":["bridge","interop","javascript","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extremeheat.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-15T03:42:42.000Z","updated_at":"2024-06-01T02:55:39.609Z","dependencies_parsed_at":"2023-02-14T07:15:39.365Z","dependency_job_id":"a9abe4c3-2757-4da5-a10f-3a898072ae0f","html_url":"https://github.com/extremeheat/JSPyBridge","commit_stats":{"total_commits":139,"total_committers":9,"mean_commits":"15.444444444444445","dds":0.06474820143884896,"last_synced_commit":"2f2752285e2c627c7b6ae88eb66a18a6517e8eaf"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extremeheat%2FJSPyBridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extremeheat%2FJSPyBridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extremeheat%2FJSPyBridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extremeheat%2FJSPyBridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extremeheat","download_url":"https://codeload.github.com/extremeheat/JSPyBridge/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245882333,"owners_count":20687866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bridge","interop","javascript","python"],"created_at":"2024-07-31T17:01:28.455Z","updated_at":"2025-03-27T16:31:05.402Z","avatar_url":"https://github.com/extremeheat.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# JSPyBridge\n[![NPM version](https://img.shields.io/npm/v/pythonia.svg)](http://npmjs.com/package/pythonia)\n[![PyPI](https://img.shields.io/pypi/v/javascript)](https://pypi.org/project/javascript/)\n[![Build Status](https://github.com/extremeheat/JSPyBridge/actions/workflows/node.yml/badge.svg)](https://github.com/extremeheat/JSPyBridge/actions/workflows/)\n[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/extremeheat/jspybridge)\n\n\nInteroperate Node.js and Python. You can run Python from Node.js, *or* run Node.js from Python. **Work in progress.** \n\nRequires Node.js 18 and Python 3.8 or newer.\n\n## Key Features\n\n* Ability to call async and sync functions and get object properties with a native feel\n* Built-in garbage collection\n* Bidirectional callbacks with arbitrary arguments\n* Iteration and exception handling support\n* Object inspection allows you to easily `console.log` or `print()` any foreign objects\n* (Bridge to call Python from JS) Python class extension and inheritance. [See pytorch and tensorflow examples](https://github.com/extremeheat/JSPyBridge/blob/master/examples/javascript/pytorch-train.js).\n* (Bridge to call JS from Python) Native decorator-based event emitter support\n* (Bridge to call JS from Python) First-class Jupyter Notebook/Google Colab support. See some Google Colab uses below.\n\n\n## Basic usage example\n\nSee some examples [here](https://github.com/extremeheat/JSPyBridge/tree/master/examples). See [documentation](https://github.com/extremeheat/JSPyBridge#documentation) below and in [here](https://github.com/extremeheat/JSPyBridge/tree/master/docs).\n\n### Access JavaScript from Python\n\n\n```sh\npip3 install javascript\n```\n\n\n```py\nfrom javascript import require, globalThis\n\nchalk, fs = require(\"chalk\"), require(\"fs\")\n\nprint(\"Hello\", chalk.red(\"world!\"), \"it's\", globalThis.Date().toLocaleString())\nfs.writeFileSync(\"HelloWorld.txt\", \"hi!\")\n```\n\n### Access Python from JavaScript\n\nMake sure to have the dependencies installed before hand!\n\n```sh\nnpm i pythonia\n```\n\n```js\nimport { python } from 'pythonia'\n// Import tkinter\nconst tk = await python('tkinter')\n// All Python API access must be prefixed with await\nconst root = await tk.Tk()\n// A function call with a $ suffix will treat the last argument as a kwarg dict\nconst a = await tk.Label$(root, { text: 'Hello World' })\nawait a.pack()\nawait root.mainloop()\npython.exit() // Make sure to exit Python in the end to allow node to exit. You can also use process.exit.\n```\n\n### Examples\n[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/extremeheat/jspybridge)\n\nCheck out some cool examples below! Try them on Gitpod! Click the Open in Gitpod link above, and then open the examples folder.\n\n\n[![PyTorch](https://www.vectorlogo.zone/logos/pytorch/pytorch-ar21.svg)](https://github.com/extremeheat/JSPyBridge/blob/master/examples/javascript/pytorch-train.js)\n[![numpy](https://www.vectorlogo.zone/logos/numpy/numpy-ar21.svg)](https://github.com/extremeheat/JSPyBridge/blob/master/examples/javascript/matplotlib.js)\n[![tensorflow](https://www.vectorlogo.zone/logos/tensorflow/tensorflow-ar21.svg)](https://github.com/extremeheat/JSPyBridge/blob/master/examples/javascript/tensorflow.js)\n[![mineflayer](https://www.vectorlogo.zone/logos/minecraft/minecraft-ar21.svg)](https://github.com/extremeheat/JSPyBridge/blob/master/examples/python/mineflayer.py)\n\u003c!-- \u003cimg src=\"https://matplotlib.org/stable/_static/logo2_compressed.svg\" alt=\"matplotlib\" width=\"120\" height=\"70\"\u003e\n --\u003e\n\n\n### Bridge feature comparison\n\nUnlike other bridges, you may notice you're not just writing Python code in JavaScript, or vice-versa. You can operate on objects\non the other side of the bridge as if the objects existed on your side. This is achieved through real interop support: you can call\ncallbacks, and do loss-less function calls with any arguments you like (with the exception of floating points percision of course).\n\n|  | python(ia) bridge | javascript bridge | [npm:python-bridge](https://www.npmjs.com/package/python-bridge) |\n|---|---|---|---|\n| Garbage collection | ✔ | ✔ | ❌ |\n| Class extension support | ✔ | Not built-in (rare use case), can be manually done with custom proxy | ❌ |\n| Passthrough stdin | ❌ (Standard input is not piped to bridge processes. Instead, listen to standard input then expose an API on the other side of the bridge recieve the data.) | ❌ | ✔ |\n| Passthrough stdout, stderr | ✔ | ✔ | ✔ |\n| Long-running sync calls | ✔ | ✔ | ✔ |\n| Long-running async calls | ❌ (need to manually create new thread) | ✔ (AsyncTask) | ❌ (need to manually create new thread) |\n| Callbacks | ✔ | ✔ | ❌ |\n| Call classes | ✔ | ✔ |  |\n| Iterators | ✔ | ✔ | ❌ |\n| Inline eval | ✔ | ✔ |  |\n| Dependency Management | ❌ | ✔ | ❌ |\n| Local File Imports | ✔ | ✔ | ❌ |\n| Error Management | ✔ | ✔ | ✔ |\n| Object inspection | ✔ | ✔ | ❌ |\n\n## Who's using it\n* [PrismarineJS/mineflayer](https://github.com/PrismarineJS/mineflayer) -- [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PrismarineJS/mineflayer/blob/master/docs/mineflayer.ipynb)\n\n# Documentation\n\n## From Python\n\nYou can import the bridge module with \n```py\nfrom javascript import require\n```\n\nThis will import the require function which you can use just like in Node.js. This is a slightly\nmodified require function which does dependency management for you. The first paramater is the name\nor location of the file to import. Internally, this calls the ES6 dynamic `import()` function. Which\nsupports both CommonJS and ES6 modules.\n\nIf you are passing a module name (does not start with / or include a .) such as 'chalk', it will search \nfor the dependency in the internal node_module folder and if not found, install it automatically. \nThis install will only happen once, it won't impact startup afterwards.\n\nThe second paramater to the built-in require function is the version of the package you want, for\nexample `require('chalk', '^3')` to get a version greater than major version 3. Just like you would\nif you were using `npm install`. It's reccomended to only use the major version as the name and version\nwill be internally treated as a unique package, for example 'chalk--^3'. If you leave this empty, \nwe will install `latest` version instead, or use the version that may already be installed globally.\n\n### Usage\n\n* All function calls to JavaScript are thread synchronous\n* ES6 classes can be constructed without new\n* ES5 classes can be constructed with the .new psuedo method\n* Use `@On` decorator when binding event listeners. Use `off()` to disable it.\n* All callbacks run on a dedicated callback thread. DO NOT BLOCK in a callback or all other events will be blocked. Instead:\n* Use the @AsyncTask decorator when you need to spawn a new thread for an async JS task.\n\nFor more, see [docs/python.md](https://github.com/extremeheat/JSPyBridge/blob/master/docs/python.md).\n\n### Usage\n\n\u003cdetails\u003e\n  \u003csummary\u003e👉 Click here to see some code usage examples 👈\u003c/summary\u003e\n\n### Basic import\n\nLet's say we have a file in JS like this called `time.js` ...\n```js\nfunction whatTimeIsIt() {\n    return (new Date()).toLocaleString()\n}\nmodule.exports = { whatTimeIsIt }\n```\n\nThen we can call it from Python !\n```py\nfrom javascript import require\ntime = require('./time.js')\nprint(time.whatTimeIsIt())\n```\n\n### Event emitter\n\n*You must use the provided On, Once, decorator and off function over the normal dot methods.*\n\nemitter.js\n```js\nconst { EventEmitter } = require('events')\nclass MyEmitter extends EventEmitter {\n    counter = 0\n    inc() {\n        this.emit('increment', ++this.counter)\n    }\n}\nmodule.exports = { MyEmitter }\n```\n\nlistener.py\n```py\nfrom javascript import require, On, off\nMyEmitter = require('./emitter.js')\n# New class instance\nmyEmitter = MyEmitter()\n# Decorator usage\n@On(myEmitter, 'increment')\ndef handleIncrement(this, counter):\n    print(\"Incremented\", counter)\n    # Stop listening. `this` is the this variable in JS.\n    off(myEmitter, 'increment', handleIncrement)\n# Trigger the event handler\nmyEmitter.inc()\n```\n\n### ES5 class\n\nes5.js\n```js\nfunction MyClass(num) {\n    this.getNum = () =\u003e num\n}\nmodule.exports = { MyClass }\n```\n\n\nes5.py\n```py\nMyEmitter = require('./es5.js')\nmyClass = MyClass.new(3)\nprint(myClass.getNum())\n```\n\n### Iteration\nitems.js\n```js\nmodule.exports = { items: [5, 6, 7, 8] }\n```\n\nitems.py\n```py\nitems = require('./items.js')\nfor item in items:\n    print(item)\n```\n\n### Callback\n\ncallback.js\n```js\nexport function method(cb, salt) {\n    cb(42 + salt)\n}\n```\ncallback.py\n```py\nmethod = require('./callback').method\n# Example with a lambda, but you can also pass a function ref\nmethod(lambda v: print(v), 2) # Prints 44\n```\n\n\u003c/details\u003e\n\n## From JavaScript\n\n* All the Python APIs are async. You must await them all. \n* Use `python.exit()` or `process.exit()` at the end to quit the Python process.\n* This library doesn't manage the packaging. \n  * Right now you need to install all the deps from pip globally, but later on we may allow loading from pip-envs.\n* When you do a normal Python function call, you can supply \"positional\" arguments, which must \n  be in the correct order to what the Python function expects.\n* Some Python objects accept arbitrary keyword arguments. You can call these functions by using\n  the special `$` function syntax. \n  * When you do a function call with a `$` before the parenthesis, such as `await some.pythonCall$()`, \n    the final argument is evaluated as a kwarg dictionary. You can supply named arguments this way.\n* Property access with a $ at the end acts as a error suppression operator. \n  * Any errors will be ignored and instead undefined will be returned\n* See [docs/javascript.md](docs/javascript.md) for more docs, and the examples for more info\n\n### Usage\n\n\u003cdetails\u003e\n  \u003csummary\u003e👉 Click here to see some code usage examples 👈\u003c/summary\u003e\n\n### Basic import\n\nLet's say we have a file in Python like this called `time.py` ...\n```py\nimport datetime\ndef what_time_is_it():\n  return str(datetime.datetime.now())\n```\n\nThen we can call it from JavaScript !\n```js\nimport { python } from 'pythonia'\nconst time = await python('./time.py')\nconsole.log(\"It's\", await time.what_time_is_it())\npython.exit()\n```\n\n### Iterating\n\n* When iterating a Python object, you *must* use a `for await` loop instead of a normal `for-of` loop.\n\niter.py\n```py\nimport os\ndef get_files():\n  for f in os.listdir():\n    yield f\n```\n\niter.js\n```js\nconst iter = await python('./iter.py')\nconst files = await iter.get_files()\nfor await (const file of files) {\n  console.log(file)\n}\n```\n\u003c/details\u003e\n\n## Extra details\n\n* When doing a function call, any returned foreign objects will be sent to you as a reference. For example, if you're in JavaScript and do a function call to Python that returns an array, you won't get a JS array back, but you will get a reference to the Python array. You can still access the array normally with the [] notation, as long as you use await.\n\n* This behavior makes it very fast to pass objects directly between same-language functions, avoiding costly cross-language data transfers.\n\n* However, this does not apply with callbacks or non-native function input parameters. The bridge will try to serialize what it can, and will give you a foreign reference if it's unable to serialize something. So if you pass a JS object, you'll get a Python dict, but if the dict contains something like a class, you'll get a reference in its place.\n\n* (On the bridge to call JavaScript from Python) If you would like the bridge to turn a foreign reference to something native, you can use `.valueOf()` to transfer an object via JSON serialization, or `.blobValueOf()` to write an object into the communication pipe directly.\n  - `.valueOf()` can be used on any JSON-serializable object, but may be very slow for big data.\n  - `.blobValueOf()` can be used on any pipe-writeable object implementing the `length` property (e.g. `Buffer`). It can be massively faster by circumventing the JSON+UTF8 encode/decode layer, which is inept for large byte arrays.\n\n* You can use custom Node.js/Python binary paths by setting the `NODE_BIN` or `PYTHON_BIN` enviornment variables before importing the library. Otherwise, the `node` and `python3` or `python` binaries will be called relative to your PATH enviornment variable. \n\n* The inter-process communication can be inspected by setting the `DEBUG` env var to `jspybridge`.\n\n#### Limitations\n\n* The `ffid` keyword is reserved. You cannot use it in variable names, object keys or values as this is used to internlly track objects.\n\n* On the bridge to call JavaScript from Python, due to the limiatations of Python and cross-platform IPC, we currently communicate over standard error which means that specific output in JS standard error can interfere with the bridge (as of this writing, the prefices `{\"r\"` and `blob!` are reserved). A similar issue exists on Windows with Python. You are however very unlikely to have issues with this.\n\n* Function calls will timeout after 100000 ms and throw a `BridgeException` error. That default value can be overridden by defining the new value of `REQ_TIMEOUT` in an environment variable, and setting it to 0 will disable timeout checks.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextremeheat%2FJSPyBridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextremeheat%2FJSPyBridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextremeheat%2FJSPyBridge/lists"}