{"id":17161563,"url":"https://github.com/itajaja/hopi","last_synced_at":"2025-04-13T13:31:02.350Z","repository":{"id":40282120,"uuid":"284514381","full_name":"itajaja/hopi","owner":"itajaja","description":"python-in-node interop ⬢ 🐍","archived":false,"fork":false,"pushed_at":"2023-01-09T23:03:27.000Z","size":1776,"stargazers_count":38,"open_issues_count":14,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T18:11:12.522Z","etag":null,"topics":["interop","interoperability","javascript","nodejs","pandas","python"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/itajaja.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-02T18:00:40.000Z","updated_at":"2023-12-24T16:05:09.000Z","dependencies_parsed_at":"2023-02-08T15:46:33.400Z","dependency_job_id":null,"html_url":"https://github.com/itajaja/hopi","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itajaja%2Fhopi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itajaja%2Fhopi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itajaja%2Fhopi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itajaja%2Fhopi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/itajaja","download_url":"https://codeload.github.com/itajaja/hopi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248720991,"owners_count":21151023,"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":["interop","interoperability","javascript","nodejs","pandas","python"],"created_at":"2024-10-14T22:43:21.200Z","updated_at":"2025-04-13T13:31:01.907Z","avatar_url":"https://github.com/itajaja.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hopi [![npm version](https://badge.fury.io/js/hopi.svg)](https://badge.fury.io/js/hopi) [![hopi](https://circleci.com/gh/itajaja/hopi.svg?style=svg)](https://circleci.com/gh/itajaja/hopi)\n\n_If it looks like python, swims like python, and quacks like python, then it probably is python._\n\nHopi is a Python-in-node interoperability library focused on seamlessness and developer experience.\n\nThe goal of the project is to be able to make use of python libraries and features as if they were written in Javascript.\nThe result is code where it's really hard to tell which parts are executed in python and which in node. Whether this is an actual good thing, it's up for debate, but it's certaintly fun ⭐️.\n\nUse at your own risk! Hopi is currently not production ready. The APIs might change and there might be significant performance issues. Moreover, the current iteration does not offer any GC capabilities and there is significant risk of using too much memory if the python envs are long lived.\n\n## Getting started\n\n```sh\n# yarn\nyarn add hopi\n# npm\nnpm install hopi\n```\n\n## Example\n\nWorth a thousand words:\n\n```ts\nimport { createPythonEnv, kwargs } from 'hopi';\n\nconst py = createPythonEnv('python');\n\nasync function run() {\n  try {\n    await shell.addDecoder({\n      typeName: 'pandas._libs.tslibs.timestamps.Timestamp',\n      encode: 'lambda v: v.isoformat()',\n      decode: (s: string) =\u003e new Date(s).toDateString(),\n    });\n\n    const pd = await py.import('pandas');\n    let df = pd.read_csv(\n      'https://covid.ourworldindata.org/data/owid-covid-data.csv',\n    );\n    df = df.assign(kwargs({ date: pd.to_datetime(df.date) }));\n    // remove total world count\n    df = df`[${df}.iso_code != 'OWID_WRL']`;\n    const newCases = df.groupby('date').new_cases;\n    const diffCases = newCases\n      .sum()\n      .diff()\n      .sort_values(kwargs({ ascending: false }));\n\n    const biggestIncrease = diffCases.iloc[0];\n    const biggestIncreaseDay = diffCases.index[0];\n    console.log(\n      `the biggest increase in daily new cases was ${await biggestIncrease._} and it happened on ${(\n        await biggestIncreaseDay._\n      ).toDateString()}`,\n    );\n\n    const juneData = df`[${df}.date.between('2020-06-01', '2020-07-01')]`;\n    const usJuneDeaths = juneData`[${juneData}.iso_code == 'USA']`.new_deaths.describe();\n    const median = await usJuneDeaths['50%']._;\n    console.log(\n      `in june, the median of daily new cases in the United states was ${median}`,\n    );\n\n    const requests = await py.import('requests');\n\n    const resp = requests.get('https://example.com/');\n    await resp.raise_for_status()._;\n    console.log(await resp.text._);\n  } catch (e) {\n    console.log('received an error:', e);\n  } finally {\n    py.shell.kill();\n  }\n}\n\nrun();\n```\n\n## Documentation\n\nFirst, to create a new environment:\n\n```ts\nconst py = createPythonEnv('path_to_python_binary');\n```\n\nyou can use `py` to run python code directly from javaScript.\n\n\u003e 💡In order to properly dispose of the environment, make sure you call `py.shell.kill()` at the end of your program.\n\n### Execute code\n\nTo execute any code:\n\n```ts\nawait py.x`import pandas`;\nawait py.x`x = 'abc'`;\nawait py.x`def add(x, y):\n  return x + y`;\n```\n\n### Evaluate code\n\n```ts\nconst myVal = await py.e`[1, 2, 3][-1]`; // 3\nconsole.log(myVal); // 3\nconst myVal2 = await py.e`len({1, 2, ()}) == 3`;\nconsole.log(myVal2); // true\n```\n\n### using PyVar\n\n`PyVar`s are powerful objects that lets you compose python constructs as javaScript and extract the results when needed. To create a `PyVar`, call `py` directly with a string template:\n\n```ts\nconst v = py`1 + 2`;\n```\n\nin the code above, `v` is not `3`, but rather a reference to a python variable that holds that value. To get the value, use the `_` property\n\n```ts\nconst result = await v._;\nprint(v); // 3\n```\n\n`PyVars` are composable in many different ways. They can be used in `py`s string template:\n\n```ts\nconst v1 = py`1 + 2`;\nconst v2 = py`3 + ${v1}`;\nconsole.log(await v2._); // 6\n```\n\nThey can be called:\n\n```ts\nimport { kwargs } from './py';\n\nconst foo = py`lambda x: x.lower()`;\nconsole.log(await foo('my JavaScript string')._);\nconsole.log(await foo(py`\"a python string!\"`)._);\nconsole.log(await foo(kwargs({ x: 'string' })));\n```\n\nThey can be accessed with dot notation or square brackets notation:\n\n```ts\nconst myString = py`\" abc \"`;\nconst upperString = myString.upper().strip()[2];\n```\n\n\u003e 💡Unfortunately there is a mismatch between python and JavaScript: in JavaScript dot notation and square bracket are interchangeable, while in python they mean very different things\n\u003e\n\u003e Therefore, There are a couple of rules that apply to dot or square brackets notations\n\u003e\n\u003e - number properties are passed as integers in square brackets: eg `[1]`, `[1.1]`\n\u003e - strings that are not valid propertry names in python are stringified and passed in square brackets: eg `['0a']`, `['.']`, `['?']`\n\u003e - everything else is passed with dot notation, eg `.foo`\n\nLastly, `PyVars` also accept interpolated strings to be chained:\n\n```ts\nconst myList = py`[1, 2, 3, 4, 5]`;\nconst val = myList`[2:4].index(3)`;\n// which is equivalent to\nconst val = myList`[2:4]`.index(3);\n// which is equivalent to\nconst val = myList`[2:4].index(3)`;\n// which is equivalent to\nconst val = myList`[2:4]`.index`(3)`;\n// which is equivalent to\nconst val = myList`[2:4]`.index`(${py`3`})`;\n```\n\n### Decoders\n\nIn order to read values from python in JavaScript, they need to be properly encoded in strings and then decoded in JavaScript. Custom decoders can be defined as such:\n\n```ts\nawait shell.addDecoder({\n  // the fully qualified type name\n  typeName: 'pandas._libs.tslibs.timestamps.Timestamp',\n  // stringified lambda function to encode the python value into a string\n  encode: 'lambda v: v.isoformat()',\n  // function to transform the value into the desired Javascript value.\n  // The `decode` argument can be used to recursively call the full decoder\n  decode: (v, decode) =\u003e new Date(v).toDateString(),\n});\n```\n\n## TODO\n\n- [ ] gc\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitajaja%2Fhopi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitajaja%2Fhopi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitajaja%2Fhopi/lists"}