├── .gitignore ├── LICENSE ├── README.md ├── doc ├── .nojekyll ├── README.md ├── interfaces │ ├── PyInstallLog.md │ ├── PyLog.md │ └── PyRunner.md └── modules.md ├── examples ├── esm │ ├── README.md │ ├── index.html │ └── package.json ├── umd │ ├── README.md │ ├── index.html │ └── package.json └── vuejs │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── components.d.ts │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── favicon.ico │ ├── src │ ├── App.vue │ ├── assets │ │ ├── index.css │ │ └── logo.png │ ├── env.d.ts │ ├── main.ts │ ├── state.ts │ └── widgets │ │ ├── PyCodeBlock.vue │ │ └── PyStatus.vue │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── package.json ├── rollup.config.mjs ├── src ├── interfaces.ts ├── main.ts ├── py.ts ├── store.ts ├── webworker.js └── worker.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | package-lock.json 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | lerna-debug.log* 17 | 18 | node_modules 19 | dist 20 | dist-ssr 21 | *.local 22 | 23 | # Editor directories and files 24 | .vscode/* 25 | !.vscode/extensions.json 26 | .idea 27 | .DS_Store 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 synw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use Python 2 | 3 | [![pub package](https://img.shields.io/npm/v/usepython)](https://www.npmjs.com/package/usepython) 4 | 5 | A Python scripts runner composable. Run Python scripts in a [Pyodide](https://github.com/pyodide/pyodide) service worker 6 | 7 | [Api doc](https://github.com/synw/usepython/tree/main/doc) 8 | 9 | ## Install 10 | 11 | ### As a package 12 | 13 | ```bash 14 | npm install usepython 15 | ``` 16 | 17 | Then use it: 18 | 19 | ```ts 20 | import { usePython } from "usepython"; 21 | 22 | const py = usePython(); 23 | ``` 24 | 25 | ### As script src 26 | 27 | ```html 28 | 29 | 32 | ``` 33 | 34 | ### As script type module 35 | 36 | ```html 37 | 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Load the runtime 46 | 47 | Load the Python runtime: 48 | 49 | ```ts 50 | await py.load() 51 | ``` 52 | 53 | Listen to the install log: 54 | 55 | ```ts 56 | const unbindInstallLog = py.installLog.listen((val) => { 57 | console.log(`Installing Python, stage ${val.stage}: ${val.msg}`) 58 | }) 59 | await py.load(); 60 | unbindInstallLog(); 61 | ``` 62 | 63 | The install log object is a [nanostore](https://github.com/nanostores/nanostores#maps) 64 | 65 | ### Load libraries 66 | 67 | It is possible to install some Python packages: either [packages built for Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html#packages-in-pyodide), standard pip packages that will be installed with Micropip, or custom wheels 68 | 69 | ```ts 70 | const wheel = '/acustomwheel-0.0.1-py3-none-any.whl'; 71 | const pyodideLibs = ['pandas', 'numpy', 'bokeh']; 72 | await py.load(pyodideLibs, ['altair', wheel, 'vega_datasets']) 73 | ``` 74 | 75 | ### Run Python code 76 | 77 | Run some sync Python code: 78 | 79 | ```ts 80 | const script = `a=1 81 | b=2 82 | a+b` 83 | const { result, error } = await py.run(script); 84 | ``` 85 | 86 | The result is the last line of the script, just like a return value 87 | 88 | See the [documentation](doc/modules.md) 89 | 90 | To run async code use the `runAsync` function. 91 | 92 | #### Namespaces 93 | 94 | An optionnal namespace parameter can be used to isolate Python contexts: 95 | 96 | ```ts 97 | const { result, error } = await py.run("a=1", "ns1"); 98 | ``` 99 | 100 | The variable defined in the script will be accessible only in the 101 | same namespace: 102 | 103 | ```ts 104 | const { result, error } = await py.run("b=a+1", "ns1"); 105 | ``` 106 | 107 | To flush the context of the namespace and reset all user defined 108 | variables use the `clear` function: 109 | 110 | ```ts 111 | await py.clear("ns1"); 112 | ``` 113 | 114 | ### Listen to stdout 115 | 116 | Listen to the Python stdout output: 117 | 118 | ```ts 119 | py.log.listen((val) => { 120 | console.log("LOG", val.stdOut) 121 | // val.stdErr is also available 122 | }); 123 | const script = `print('ok from python')`; 124 | await py.run(script); 125 | ``` 126 | 127 | The log object is a [nanostore](https://github.com/nanostores/nanostores#maps) 128 | 129 | ### State 130 | 131 | [Atom stores](https://github.com/nanostores/nanostores#atoms) are available to listen to 132 | the ready state and execution state of Python. Example: 133 | 134 | ```ts 135 | py.isReady.listen((v) => console.log("Ready state:", v)); 136 | py.isExecuting.listen((v) => console.log("Execution state:", v)); 137 | ``` 138 | 139 | Vuejs example: 140 | 141 | ```ts 142 | import { useStore } from '@nanostores/vue'; 143 | 144 | const isExecuting: Readonly> = useStore(py.isExecuting); 145 | const isReady: Readonly> = useStore(py.isReady); 146 | ``` 147 | 148 | ## Examples 149 | 150 | - [Script src](examples/umd/) 151 | - [Script module](examples/esm/) 152 | - [Vuejs](examples/vuejs/) 153 | 154 | [Api doc](https://github.com/synw/usepython/tree/main/doc) 155 | -------------------------------------------------------------------------------- /doc/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | Documentation / [Exports](modules.md) 2 | 3 | # Use Python 4 | 5 | [![pub package](https://img.shields.io/npm/v/usepython)](https://www.npmjs.com/package/usepython) 6 | 7 | A Python scripts runner composable. Run Python scripts in a [Pyodide](https://github.com/pyodide/pyodide) service worker 8 | 9 | ## Install 10 | 11 | ### As a package 12 | 13 | ```bash 14 | npm install usepython 15 | # or 16 | yarn add usepython 17 | ``` 18 | 19 | Then use it: 20 | 21 | ```ts 22 | import { usePython } from "usepython"; 23 | 24 | const py = usePython(); 25 | ``` 26 | 27 | ### As script src 28 | 29 | ```html 30 | 31 | 34 | ``` 35 | 36 | ### As script type module 37 | 38 | ```html 39 | 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### Load the runtime 48 | 49 | Load the Python runtime: 50 | 51 | ```ts 52 | await py.load() 53 | ``` 54 | 55 | Listen to the install log: 56 | 57 | ```ts 58 | const unbindInstallLog = py.installLog.listen((val) => { 59 | console.log(`Installing Python, stage ${val.stage}: ${val.msg}`) 60 | }) 61 | await py.load(); 62 | unbindInstallLog(); 63 | ``` 64 | 65 | The install log object is a [nanostore](https://github.com/nanostores/nanostores#maps) 66 | 67 | ### Load libraries 68 | 69 | It is possible to install some Python packages: either [packages built for Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html#packages-in-pyodide), standard pip packages that will be installed with Micropip, or custom wheels 70 | 71 | ```ts 72 | const wheel = '/acustomwheel-0.0.1-py3-none-any.whl'; 73 | const pyodideLibs = ['pandas', 'numpy', 'bokeh']; 74 | await py.load(pyodideLibs, ['altair', wheel, 'vega_datasets']) 75 | ``` 76 | 77 | ### Run Python code 78 | 79 | Run some sync Python code: 80 | 81 | ```ts 82 | const script = `a=1 83 | b=2 84 | a+b` 85 | const { result, error } = await py.run(script); 86 | ``` 87 | 88 | The result is the last line of the script, just like a return value 89 | 90 | See the [documentation](doc/modules.md) 91 | 92 | To run async code use the `runAsync` function. 93 | 94 | #### Namespaces 95 | 96 | An optionnal namespace parameter can be used to isolate Python contexts: 97 | 98 | ```ts 99 | const { result, error } = await py.run("a=1", "ns1"); 100 | ``` 101 | 102 | The variable defined in the script will be accessible only in the 103 | same namespace: 104 | 105 | ```ts 106 | const { result, error } = await py.run("b=a+1", "ns1"); 107 | ``` 108 | 109 | To flush the context of the namespace and reset all user defined 110 | variables use the `clear` function: 111 | 112 | ```ts 113 | await py.clear("ns1"); 114 | ``` 115 | 116 | ### Listen to stdout 117 | 118 | Listen to the Python stdout output: 119 | 120 | ```ts 121 | py.log.listen((val) => { 122 | console.log("LOG", val.stdOut) 123 | // val.stdErr is also available 124 | }); 125 | const script = `print('ok from python')`; 126 | await py.run(script); 127 | ``` 128 | 129 | The log object is a [nanostore](https://github.com/nanostores/nanostores#maps) 130 | 131 | ### State 132 | 133 | [Atom stores](https://github.com/nanostores/nanostores#atoms) are available to listen to 134 | the ready state and execution state of Python. Example: 135 | 136 | ```ts 137 | py.isReady.listen((v) => console.log("Ready state:", v)); 138 | py.isExecuting.listen((v) => console.log("Execution state:", v)); 139 | ``` 140 | 141 | Vuejs example: 142 | 143 | ```ts 144 | import { useStore } from '@nanostores/vue'; 145 | 146 | const isExecuting: Readonly> = useStore(py.isExecuting); 147 | const isReady: Readonly> = useStore(py.isReady); 148 | ``` 149 | 150 | ## Examples 151 | 152 | - [Script src](examples/umd/) 153 | - [Script module](examples/esm/) 154 | - [Vuejs](examples/vuejs/) 155 | -------------------------------------------------------------------------------- /doc/interfaces/PyInstallLog.md: -------------------------------------------------------------------------------- 1 | [Documentation](../README.md) / [Exports](../modules.md) / PyInstallLog 2 | 3 | # Interface: PyInstallLog 4 | 5 | Represents a log entry for the installation process of Python packages. 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [msg](PyInstallLog.md#msg) 12 | - [stage](PyInstallLog.md#stage) 13 | 14 | ## Properties 15 | 16 | ### msg 17 | 18 | • **msg**: `string` 19 | 20 | Message related to the current stage of the installation process. 21 | 22 | #### Defined in 23 | 24 | [interfaces.ts:48](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L48) 25 | 26 | ___ 27 | 28 | ### stage 29 | 30 | • **stage**: `number` 31 | 32 | The current stage of the installation process. 33 | 34 | #### Defined in 35 | 36 | [interfaces.ts:42](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L42) 37 | -------------------------------------------------------------------------------- /doc/interfaces/PyLog.md: -------------------------------------------------------------------------------- 1 | [Documentation](../README.md) / [Exports](../modules.md) / PyLog 2 | 3 | # Interface: PyLog 4 | 5 | Represents a log entry for a Python execution. 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [exception](PyLog.md#exception) 12 | - [id](PyLog.md#id) 13 | - [stdErr](PyLog.md#stderr) 14 | - [stdOut](PyLog.md#stdout) 15 | 16 | ## Properties 17 | 18 | ### exception 19 | 20 | • **exception**: `string` 21 | 22 | Exception message, if any occurred during the execution. 23 | 24 | #### Defined in 25 | 26 | [interfaces.ts:30](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L30) 27 | 28 | ___ 29 | 30 | ### id 31 | 32 | • **id**: `string` 33 | 34 | Unique identifier for the log entry. 35 | 36 | #### Defined in 37 | 38 | [interfaces.ts:12](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L12) 39 | 40 | ___ 41 | 42 | ### stdErr 43 | 44 | • **stdErr**: `string`[] 45 | 46 | Array of standard error messages. 47 | 48 | #### Defined in 49 | 50 | [interfaces.ts:24](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L24) 51 | 52 | ___ 53 | 54 | ### stdOut 55 | 56 | • **stdOut**: `string`[] 57 | 58 | Array of standard output messages. 59 | 60 | #### Defined in 61 | 62 | [interfaces.ts:18](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L18) 63 | -------------------------------------------------------------------------------- /doc/interfaces/PyRunner.md: -------------------------------------------------------------------------------- 1 | [Documentation](../README.md) / [Exports](../modules.md) / PyRunner 2 | 3 | # Interface: PyRunner 4 | 5 | Interface for a Python runner object. 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [clear](PyRunner.md#clear) 12 | - [installLog](PyRunner.md#installlog) 13 | - [isExecuting](PyRunner.md#isexecuting) 14 | - [isReady](PyRunner.md#isready) 15 | - [load](PyRunner.md#load) 16 | - [log](PyRunner.md#log) 17 | - [run](PyRunner.md#run) 18 | - [runAsync](PyRunner.md#runasync) 19 | 20 | ## Properties 21 | 22 | ### clear 23 | 24 | • **clear**: [`PyClearMethod`](../modules.md#pyclearmethod) 25 | 26 | Method to clear a namespace. 27 | 28 | **`Param`** 29 | 30 | The namespace to clear. 31 | 32 | #### Defined in 33 | 34 | [interfaces.ts:91](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L91) 35 | 36 | ___ 37 | 38 | ### installLog 39 | 40 | • **installLog**: `MapStore`\<[`PyInstallLog`](PyInstallLog.md)\> 41 | 42 | Store for installation logs. 43 | 44 | #### Defined in 45 | 46 | [interfaces.ts:97](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L97) 47 | 48 | ___ 49 | 50 | ### isExecuting 51 | 52 | • **isExecuting**: `ReadableAtom`\<`boolean`\> 53 | 54 | Atom representing whether the runner is currently executing a script. 55 | 56 | #### Defined in 57 | 58 | [interfaces.ts:109](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L109) 59 | 60 | ___ 61 | 62 | ### isReady 63 | 64 | • **isReady**: `ReadableAtom`\<`boolean`\> 65 | 66 | Atom representing whether the runner is ready to execute scripts. 67 | 68 | #### Defined in 69 | 70 | [interfaces.ts:115](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L115) 71 | 72 | ___ 73 | 74 | ### load 75 | 76 | • **load**: [`PyLoadMethod`](../modules.md#pyloadmethod) 77 | 78 | Method to load Python packages. 79 | 80 | **`Param`** 81 | 82 | Optional array of Python packages to load. 83 | 84 | **`Param`** 85 | 86 | Optional array of additional packages to load. 87 | 88 | **`Param`** 89 | 90 | Optional initialization code to run. 91 | 92 | **`Param`** 93 | 94 | Optional transformation code to run. 95 | 96 | #### Defined in 97 | 98 | [interfaces.ts:64](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L64) 99 | 100 | ___ 101 | 102 | ### log 103 | 104 | • **log**: `MapStore`\<[`PyLog`](PyLog.md)\> 105 | 106 | Store for execution logs. 107 | 108 | #### Defined in 109 | 110 | [interfaces.ts:103](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L103) 111 | 112 | ___ 113 | 114 | ### run 115 | 116 | • **run**: [`PyRunMethod`](../modules.md#pyrunmethod) 117 | 118 | Method to run a Python script synchronously. 119 | 120 | **`Param`** 121 | 122 | The Python script to run. 123 | 124 | **`Param`** 125 | 126 | Optional namespace for the script. 127 | 128 | **`Param`** 129 | 130 | Optional identifier for the script execution. 131 | 132 | **`Param`** 133 | 134 | Optional context for the script execution. 135 | 136 | #### Defined in 137 | 138 | [interfaces.ts:74](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L74) 139 | 140 | ___ 141 | 142 | ### runAsync 143 | 144 | • **runAsync**: [`PyRunAsyncMethod`](../modules.md#pyrunasyncmethod) 145 | 146 | Method to run a Python script asynchronously. 147 | 148 | **`Param`** 149 | 150 | The Python script to run. 151 | 152 | **`Param`** 153 | 154 | Optional namespace for the script. 155 | 156 | **`Param`** 157 | 158 | Optional identifier for the script execution. 159 | 160 | **`Param`** 161 | 162 | Optional context for the script execution. 163 | 164 | #### Defined in 165 | 166 | [interfaces.ts:84](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L84) 167 | -------------------------------------------------------------------------------- /doc/modules.md: -------------------------------------------------------------------------------- 1 | [Documentation](README.md) / Exports 2 | 3 | # Documentation 4 | 5 | ## Table of contents 6 | 7 | ### Interfaces 8 | 9 | - [PyInstallLog](interfaces/PyInstallLog.md) 10 | - [PyLog](interfaces/PyLog.md) 11 | - [PyRunner](interfaces/PyRunner.md) 12 | 13 | ### Type Aliases 14 | 15 | - [PyClearMethod](modules.md#pyclearmethod) 16 | - [PyLoadMethod](modules.md#pyloadmethod) 17 | - [PyRunAsyncMethod](modules.md#pyrunasyncmethod) 18 | - [PyRunMethod](modules.md#pyrunmethod) 19 | 20 | ### Functions 21 | 22 | - [usePython](modules.md#usepython) 23 | 24 | ## Type Aliases 25 | 26 | ### PyClearMethod 27 | 28 | Ƭ **PyClearMethod**: (`namespace`: `string`) => `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 29 | 30 | Type for the `clear` method of the PyRunner interface. 31 | 32 | #### Type declaration 33 | 34 | ▸ (`namespace`): `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 35 | 36 | ##### Parameters 37 | 38 | | Name | Type | Description | 39 | | :------ | :------ | :------ | 40 | | `namespace` | `string` | The namespace to clear. | 41 | 42 | ##### Returns 43 | 44 | `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 45 | 46 | #### Defined in 47 | 48 | [interfaces.ts:172](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L172) 49 | 50 | ___ 51 | 52 | ### PyLoadMethod 53 | 54 | Ƭ **PyLoadMethod**: (`pyoPackages?`: `string`[], `packages?`: `string`[], `initCode?`: `string`, `transformCode?`: `string`) => `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 55 | 56 | Type for the `load` method of the PyRunner interface. 57 | 58 | #### Type declaration 59 | 60 | ▸ (`pyoPackages?`, `packages?`, `initCode?`, `transformCode?`): `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 61 | 62 | ##### Parameters 63 | 64 | | Name | Type | Description | 65 | | :------ | :------ | :------ | 66 | | `pyoPackages?` | `string`[] | Optional array of Python packages to load. | 67 | | `packages?` | `string`[] | Optional array of additional packages to load. | 68 | | `initCode?` | `string` | Optional initialization code to run. | 69 | | `transformCode?` | `string` | Optional transformation code to run. | 70 | 71 | ##### Returns 72 | 73 | `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 74 | 75 | #### Defined in 76 | 77 | [interfaces.ts:127](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L127) 78 | 79 | ___ 80 | 81 | ### PyRunAsyncMethod 82 | 83 | Ƭ **PyRunAsyncMethod**: (`script`: `string`, `namespace?`: `string`, `id?`: `string`, `context?`: `Record`\<`string`, `any`\>) => `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 84 | 85 | Type for the `runAsync` method of the PyRunner interface. 86 | 87 | #### Type declaration 88 | 89 | ▸ (`script`, `namespace?`, `id?`, `context?`): `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 90 | 91 | ##### Parameters 92 | 93 | | Name | Type | Description | 94 | | :------ | :------ | :------ | 95 | | `script` | `string` | The Python script to run. | 96 | | `namespace?` | `string` | Optional namespace for the script. | 97 | | `id?` | `string` | Optional identifier for the script execution. | 98 | | `context?` | `Record`\<`string`, `any`\> | Optional context for the script execution. | 99 | 100 | ##### Returns 101 | 102 | `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 103 | 104 | #### Defined in 105 | 106 | [interfaces.ts:159](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L159) 107 | 108 | ___ 109 | 110 | ### PyRunMethod 111 | 112 | Ƭ **PyRunMethod**: (`script`: `string`, `namespace?`: `string`, `id?`: `string`, `context?`: `Record`\<`string`, `any`\>) => `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 113 | 114 | Type for the `run` method of the PyRunner interface. 115 | 116 | #### Type declaration 117 | 118 | ▸ (`script`, `namespace?`, `id?`, `context?`): `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 119 | 120 | ##### Parameters 121 | 122 | | Name | Type | Description | 123 | | :------ | :------ | :------ | 124 | | `script` | `string` | The Python script to run. | 125 | | `namespace?` | `string` | Optional namespace for the script. | 126 | | `id?` | `string` | Optional identifier for the script execution. | 127 | | `context?` | `Record`\<`string`, `any`\> | Optional context for the script execution. | 128 | 129 | ##### Returns 130 | 131 | `Promise`\<\{ `error`: `any` ; `results`: `any` }\> 132 | 133 | #### Defined in 134 | 135 | [interfaces.ts:143](https://github.com/synw/usepython/blob/58a3740/src/interfaces.ts#L143) 136 | 137 | ## Functions 138 | 139 | ### usePython 140 | 141 | ▸ **usePython**(): [`PyRunner`](interfaces/PyRunner.md) 142 | 143 | The main composable 144 | 145 | #### Returns 146 | 147 | [`PyRunner`](interfaces/PyRunner.md) 148 | 149 | #### Defined in 150 | 151 | [py.ts:6](https://github.com/synw/usepython/blob/58a3740/src/py.ts#L6) 152 | -------------------------------------------------------------------------------- /examples/esm/README.md: -------------------------------------------------------------------------------- 1 | # Use Python esm example 2 | 3 | ```bash 4 | cd examples/esm 5 | # run 6 | npm run dev 7 | ``` 8 | 9 | Open localhost:5173 -------------------------------------------------------------------------------- /examples/esm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Use Python ESM example 8 | 9 | 10 | 11 | 14 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esm", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.0.7" 13 | } 14 | } -------------------------------------------------------------------------------- /examples/umd/README.md: -------------------------------------------------------------------------------- 1 | # Use Python umd example 2 | 3 | ```bash 4 | cd examples/umd 5 | # run 6 | yarn dev 7 | ``` 8 | 9 | Open localhost:5173 -------------------------------------------------------------------------------- /examples/umd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Use Python UMD example 8 | 9 | 10 | 11 | 14 | 15 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/umd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esm", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.0.7" 13 | } 14 | } -------------------------------------------------------------------------------- /examples/vuejs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | !/doc/dist 6 | *.local 7 | yarn.lock 8 | yarn-error.log 9 | .yarnrc 10 | /dev 11 | .vscode -------------------------------------------------------------------------------- /examples/vuejs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 synw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/vuejs/README.md: -------------------------------------------------------------------------------- 1 | # Use Python vuejs example 2 | 3 | ## Install 4 | 5 | Install the dependencies: 6 | 7 | ``` 8 | yarn 9 | ``` 10 | 11 | ## Run 12 | 13 | ``` 14 | yarn dev 15 | ``` 16 | 17 | Open localhost:3000 18 | -------------------------------------------------------------------------------- /examples/vuejs/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | // biome-ignore lint: disable 6 | export {} 7 | 8 | /* prettier-ignore */ 9 | declare module 'vue' { 10 | export interface GlobalComponents { 11 | HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] 12 | 'ICil:mediaPlay': typeof import('~icons/cil/media-play')['default'] 13 | IClaritySettingsLine: typeof import('~icons/clarity/settings-line')['default'] 14 | 'IFaSolid:angleDoubleLeft': typeof import('~icons/fa-solid/angle-double-left')['default'] 15 | 'IFaSolid:angleDoubleRight': typeof import('~icons/fa-solid/angle-double-right')['default'] 16 | 'IFaSolid:moon': typeof import('~icons/fa-solid/moon')['default'] 17 | 'IFaSolid:sun': typeof import('~icons/fa-solid/sun')['default'] 18 | 'IFileIcons:configPython': typeof import('~icons/file-icons/config-python')['default'] 19 | 'IFileIcons:testPython': typeof import('~icons/file-icons/test-python')['default'] 20 | IFluentSettings32Regular: typeof import('~icons/fluent/settings32-regular')['default'] 21 | IIonArrowBackOutline: typeof import('~icons/ion/arrow-back-outline')['default'] 22 | 'IMdi:languagePython': typeof import('~icons/mdi/language-python')['default'] 23 | TheHeader: typeof import('./src/components/TheHeader.vue')['default'] 24 | TheSidebar: typeof import('./src/components/TheSidebar.vue')['default'] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/vuejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Use Python Vue example 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vuejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usepython_example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@nanostores/vue": "^0.11.0", 12 | "highlight.js": "^11.11.1", 13 | "nanostores": "^0.11.4", 14 | "usepython": "^0.1.0", 15 | "vue": "^3.5.13", 16 | "vuecodit": "^0.0.11" 17 | }, 18 | "devDependencies": { 19 | "@iconify/json": "^2.2.311", 20 | "@snowind/plugin": "0.5.0", 21 | "@tailwindcss/forms": "^0.5.10", 22 | "@vitejs/plugin-vue": "^5.2.1", 23 | "@vue/compiler-sfc": "^3.5.13", 24 | "autoprefixer": "^10.4.20", 25 | "path": "^0.12.7", 26 | "postcss": "^8.5.3", 27 | "rollup-plugin-typescript2": "^0.36.0", 28 | "sass": "^1.85.1", 29 | "tailwindcss": "^3.0.23", 30 | "tailwindcss-semantic-colors": "^0.2.0", 31 | "tslib": "^2.8.1", 32 | "typescript": "^5.7.3", 33 | "unplugin-icons": "^22.1.0", 34 | "unplugin-vue-components": "^28.4.1", 35 | "vite": "^6.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/vuejs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('autoprefixer'), 5 | ] 6 | } -------------------------------------------------------------------------------- /examples/vuejs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/usepython/a949cb7d6fe1d67425178947d1f05c6c23258c67/examples/vuejs/public/favicon.ico -------------------------------------------------------------------------------- /examples/vuejs/src/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/vuejs/src/assets/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/vuejs/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synw/usepython/a949cb7d6fe1d67425178947d1f05c6c23258c67/examples/vuejs/src/assets/logo.png -------------------------------------------------------------------------------- /examples/vuejs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /examples/vuejs/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import './assets/index.css'; 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /examples/vuejs/src/state.ts: -------------------------------------------------------------------------------- 1 | //import { usePython } from "usepython"; 2 | import { usePython } from "../../../dist/py.esm.js"; 3 | 4 | const py = usePython() 5 | 6 | export { py } -------------------------------------------------------------------------------- /examples/vuejs/src/widgets/PyCodeBlock.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 95 | 96 | -------------------------------------------------------------------------------- /examples/vuejs/src/widgets/PyStatus.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | -------------------------------------------------------------------------------- /examples/vuejs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './index.html', 4 | './src/**/*.{js,jsx,ts,tsx,vue}', 5 | './node_modules/@snowind/**/*.{vue,js,ts}', 6 | ], 7 | darkMode: 'class', 8 | plugins: [ 9 | require('@tailwindcss/forms'), 10 | require('@snowind/plugin'), 11 | require('tailwindcss-semantic-colors') 12 | ], 13 | } -------------------------------------------------------------------------------- /examples/vuejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "noImplicitAny": false, 12 | "allowJs": true, 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ], 17 | "types": [ 18 | "vite/client" 19 | ], 20 | "baseUrl": ".", 21 | "paths": { 22 | "@/*": [ 23 | "src/*" 24 | ] 25 | } 26 | }, 27 | "include": [ 28 | "src/**/*.ts", 29 | "src/**/*.d.ts", 30 | "src/**/*.tsx", 31 | "src/**/*.vue" 32 | ] 33 | } -------------------------------------------------------------------------------- /examples/vuejs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig } from 'vite' 3 | import typescript2 from "rollup-plugin-typescript2" 4 | import vue from '@vitejs/plugin-vue' 5 | import Components from 'unplugin-vue-components/vite' 6 | import Icons from 'unplugin-icons/vite' 7 | import IconsResolver from 'unplugin-icons/resolver' 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | typescript2({ 12 | check: false, 13 | tsconfig: path.resolve(__dirname, 'tsconfig.json'), 14 | clean: true 15 | }), 16 | vue(), 17 | Components({ 18 | resolvers: [ 19 | IconsResolver() 20 | ], 21 | }), 22 | Icons({ 23 | scale: 1.2, 24 | defaultClass: 'inline-block align-middle', 25 | compiler: 'vue3', 26 | }), 27 | ], 28 | // for storybook builds to github pages 29 | //base: process.env.NODE_ENV === 'production' ? '/snowind-stories/' : './', 30 | resolve: { 31 | alias: [ 32 | { find: '@/', replacement: '/src/' }, 33 | { 34 | find: 'vue', 35 | replacement: path.resolve("./node_modules/vue"), 36 | }, 37 | ] 38 | }, 39 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usepython", 3 | "version": "0.1.1", 4 | "description": "A Python scripts runner composable", 5 | "repository": "https://github.com/synw/usepython", 6 | "scripts": { 7 | "build": "rollup -c", 8 | "dev": "rollup -cw", 9 | "docs": "typedoc --plugin typedoc-plugin-markdown --entryPointStrategy expand" 10 | }, 11 | "dependencies": { 12 | "nanostores": "^0.11.4", 13 | "pyodide": "^0.27.3" 14 | }, 15 | "devDependencies": { 16 | "@qortal/rollup-plugin-web-worker-loader": "^1.6.5", 17 | "@rollup/plugin-node-resolve": "^16.0.0", 18 | "@rollup/plugin-terser": "^0.4.4", 19 | "@rollup/plugin-typescript": "^12.1.2", 20 | "@types/node": "^22.13.5", 21 | "highlight.js": "^11.11.1", 22 | "rollup": "^4.34.8", 23 | "tslib": "^2.8.1", 24 | "typedoc": "^0.27.9", 25 | "typedoc-plugin-markdown": "^4.4.2", 26 | "typedoc-plugin-rename-defaults": "^0.7.2", 27 | "typescript": "^5.7.3" 28 | }, 29 | "files": [ 30 | "dist" 31 | ], 32 | "main": "./dist/py.min.js", 33 | "module": "./dist/py.esm.js", 34 | "types": "./dist/main.d.ts", 35 | "exports": { 36 | ".": { 37 | "import": "./dist/py.esm.js" 38 | } 39 | }, 40 | "publishConfig": { 41 | "access": "public", 42 | "registry": "https://registry.npmjs.org/" 43 | }, 44 | "license": "MIT" 45 | } -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import terser from '@rollup/plugin-terser'; 4 | import webWorkerLoader from '@qortal/rollup-plugin-web-worker-loader'; 5 | 6 | const isProduction = !process.env.ROLLUP_WATCH; 7 | 8 | export default [ 9 | { 10 | input: 'src/py.ts', 11 | output: [ 12 | { 13 | sourcemap: !isProduction, 14 | file: 'dist/py.min.js', 15 | format: 'iife', 16 | name: '$py', 17 | plugins: [ 18 | isProduction && terser({ format: { comments: false } }) 19 | ] 20 | }], 21 | plugins: [ 22 | webWorkerLoader(), 23 | typescript(), 24 | resolve({ 25 | jsnext: true, 26 | main: true, 27 | browser: true, 28 | }), 29 | ], 30 | }, 31 | { 32 | input: 'src/main.ts', 33 | output: [ 34 | { 35 | sourcemap: !isProduction, 36 | file: 'dist/py.esm.js', 37 | format: 'es' 38 | }], 39 | plugins: [ 40 | webWorkerLoader(), 41 | typescript(), 42 | resolve({ 43 | jsnext: true, 44 | main: true, 45 | browser: true, 46 | }), 47 | ], 48 | }, 49 | ]; -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { MapStore, ReadableAtom } from "nanostores"; 2 | 3 | /** 4 | * Represents a log entry for a Python execution. 5 | * @interface 6 | */ 7 | interface PyLog { 8 | /** 9 | * Unique identifier for the log entry. 10 | * @type {string} 11 | */ 12 | id: string; 13 | 14 | /** 15 | * Array of standard output messages. 16 | * @type {Array} 17 | */ 18 | stdOut: Array; 19 | 20 | /** 21 | * Array of standard error messages. 22 | * @type {Array} 23 | */ 24 | stdErr: Array; 25 | 26 | /** 27 | * Exception message, if any occurred during the execution. 28 | * @type {string} 29 | */ 30 | exception: string; 31 | } 32 | 33 | /** 34 | * Represents a log entry for the installation process of Python packages. 35 | * @interface 36 | */ 37 | interface PyInstallLog { 38 | /** 39 | * The current stage of the installation process. 40 | * @type {number} 41 | */ 42 | stage: number; 43 | 44 | /** 45 | * Message related to the current stage of the installation process. 46 | * @type {string} 47 | */ 48 | msg: string; 49 | } 50 | 51 | /** 52 | * Interface for a Python runner object. 53 | * @interface 54 | */ 55 | interface PyRunner { 56 | /** 57 | * Method to load Python packages. 58 | * @param {Array} [pyoPackages] - Optional array of Python packages to load. 59 | * @param {Array} [packages] - Optional array of additional packages to load. 60 | * @param {string} [initCode] - Optional initialization code to run. 61 | * @param {string} [transformCode] - Optional transformation code to run. 62 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 63 | */ 64 | load: PyLoadMethod; 65 | 66 | /** 67 | * Method to run a Python script synchronously. 68 | * @param {string} script - The Python script to run. 69 | * @param {string} [namespace] - Optional namespace for the script. 70 | * @param {string} [id] - Optional identifier for the script execution. 71 | * @param {Record} [context] - Optional context for the script execution. 72 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 73 | */ 74 | run: PyRunMethod; 75 | 76 | /** 77 | * Method to run a Python script asynchronously. 78 | * @param {string} script - The Python script to run. 79 | * @param {string} [namespace] - Optional namespace for the script. 80 | * @param {string} [id] - Optional identifier for the script execution. 81 | * @param {Record} [context] - Optional context for the script execution. 82 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 83 | */ 84 | runAsync: PyRunAsyncMethod; 85 | 86 | /** 87 | * Method to clear a namespace. 88 | * @param {string} namespace - The namespace to clear. 89 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 90 | */ 91 | clear: PyClearMethod; 92 | 93 | /** 94 | * Store for installation logs. 95 | * @type {MapStore} 96 | */ 97 | installLog: MapStore; 98 | 99 | /** 100 | * Store for execution logs. 101 | * @type {MapStore} 102 | */ 103 | log: MapStore; 104 | 105 | /** 106 | * Atom representing whether the runner is currently executing a script. 107 | * @type {ReadableAtom} 108 | */ 109 | isExecuting: ReadableAtom; 110 | 111 | /** 112 | * Atom representing whether the runner is ready to execute scripts. 113 | * @type {ReadableAtom} 114 | */ 115 | isReady: ReadableAtom; 116 | } 117 | 118 | /** 119 | * Type for the `load` method of the PyRunner interface. 120 | * @typedef {function} PyLoadMethod 121 | * @param {Array} [pyoPackages] - Optional array of Python packages to load. 122 | * @param {Array} [packages] - Optional array of additional packages to load. 123 | * @param {string} [initCode] - Optional initialization code to run. 124 | * @param {string} [transformCode] - Optional transformation code to run. 125 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 126 | */ 127 | type PyLoadMethod = ( 128 | pyoPackages?: Array, 129 | packages?: Array, 130 | initCode?: string, 131 | transformCode?: string 132 | ) => Promise<{ results: any, error: any }>; 133 | 134 | /** 135 | * Type for the `run` method of the PyRunner interface. 136 | * @typedef {function} PyRunMethod 137 | * @param {string} script - The Python script to run. 138 | * @param {string} [namespace] - Optional namespace for the script. 139 | * @param {string} [id] - Optional identifier for the script execution. 140 | * @param {Record} [context] - Optional context for the script execution. 141 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 142 | */ 143 | type PyRunMethod = ( 144 | script: string, 145 | namespace?: string, 146 | id?: string, 147 | context?: Record 148 | ) => Promise<{ results: any, error: any }>; 149 | 150 | /** 151 | * Type for the `runAsync` method of the PyRunner interface. 152 | * @typedef {function} PyRunAsyncMethod 153 | * @param {string} script - The Python script to run. 154 | * @param {string} [namespace] - Optional namespace for the script. 155 | * @param {string} [id] - Optional identifier for the script execution. 156 | * @param {Record} [context] - Optional context for the script execution. 157 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 158 | */ 159 | type PyRunAsyncMethod = ( 160 | script: string, 161 | namespace?: string, 162 | id?: string, 163 | context?: Record 164 | ) => Promise<{ results: any, error: any }>; 165 | 166 | /** 167 | * Type for the `clear` method of the PyRunner interface. 168 | * @typedef {function} PyClearMethod 169 | * @param {string} namespace - The namespace to clear. 170 | * @returns {Promise<{ results: any, error: any }>} A promise that resolves with the results or rejects with an error. 171 | */ 172 | type PyClearMethod = (namespace: string) => Promise<{ results: any, error: any }>; 173 | 174 | /** 175 | * Export the PyRunner interface and related types. 176 | * @type {PyRunner} 177 | */ 178 | export { 179 | PyRunner, 180 | PyLog, 181 | PyInstallLog, 182 | PyRunMethod, 183 | PyRunAsyncMethod, 184 | PyClearMethod, 185 | PyLoadMethod, 186 | }; 187 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { usePython } from "./py.js"; 2 | import { 3 | PyRunner, 4 | PyLog, 5 | PyInstallLog, 6 | PyRunMethod, 7 | PyRunAsyncMethod, 8 | PyClearMethod, 9 | PyLoadMethod, 10 | } from "./interfaces.js"; 11 | 12 | export { 13 | usePython, 14 | PyRunner, 15 | PyLog, 16 | PyInstallLog, 17 | PyRunMethod, 18 | PyRunAsyncMethod, 19 | PyClearMethod, 20 | PyLoadMethod, 21 | } -------------------------------------------------------------------------------- /src/py.ts: -------------------------------------------------------------------------------- 1 | import worker from 'web-worker:./webworker.js'; 2 | import { pyLog, pyExecState, pyInstallLog, isPyExecuting, isPyReadyState, isPyReady } from "./store.js"; 3 | import { PyRunner } from './interfaces.js'; 4 | 5 | /** The main composable */ 6 | const usePython = (): PyRunner => { 7 | const _pyodideWorker = new worker(); 8 | //const _pyodideWorker = new Worker(new URL('./webworker.js', import.meta.url), { type: 'classic' }) 9 | let _callback: (value: { 10 | results: any; 11 | error: any; 12 | } | PromiseLike<{ 13 | results: any; 14 | error: any; 15 | }>) => void = (v) => null; 16 | 17 | function _dispatchEvent(id: string, data: Record) { 18 | switch (data.type) { 19 | case "end": 20 | _callback({ results: data.res, error: null }) 21 | _callback = (v) => null 22 | pyExecState.set(0); 23 | break; 24 | case "err": 25 | _callback({ results: null, error: data.msg }) 26 | _callback = (v) => null 27 | pyExecState.set(0); 28 | pyLog.setKey("exception", data.msg) 29 | break; 30 | case "installlog": 31 | pyInstallLog.setKey("stage", data.msg.stage); 32 | pyInstallLog.setKey("msg", data.msg.msg); 33 | break; 34 | case "stderr": 35 | //console.log("STDERR:", data.msg) 36 | pyLog.get().stdErr.push(data.msg); 37 | //pyLog.notify(); 38 | break; 39 | case "stdout": 40 | //console.log("STDOUT:", data.msg) 41 | pyLog.get().stdOut.push(data.msg); 42 | //pyLog.notify(); 43 | //pyLog.setKey("stdOut", [...pyLog.get().stdOut, data.msg]) 44 | break; 45 | default: 46 | pyExecState.set(0); 47 | throw new Error(`Unknown event type ${data.type}`) 48 | } 49 | } 50 | 51 | _pyodideWorker.onmessage = (event) => { 52 | const { id, ...data } = event.data; 53 | //console.log("=> msg in:", id, ":", data); 54 | _dispatchEvent(id ?? "", data) 55 | }; 56 | 57 | function _processTransformCode(code: string): string { 58 | if (code.startsWith('\n')) { 59 | code.replace('\n', '') 60 | } 61 | const li = code.split("\n"); 62 | const buf = new Array(); 63 | li.forEach((el) => { 64 | buf.push(' ' + el) 65 | }); 66 | return buf.join("\n") 67 | } 68 | 69 | /** Load the Python runtime 70 | * @param pyoPackages the list of Pyodide packages to install 71 | * @param packages the list of Pip packages to install 72 | * @param initCode the code to run before runtime initialization 73 | * @param transformCode the code to run after every script 74 | */ 75 | async function load( 76 | pyoPackages: Array = [], packages: Array = [], initCode = "", transformCode = "" 77 | ): Promise<{ results: any, error: any }> { 78 | let res: { results: any; error: any }; 79 | try { 80 | res = await run("", undefined, "_pyinstaller", { 81 | pyoPackages: pyoPackages, 82 | packages: packages, 83 | initCode: initCode, 84 | transformCode: _processTransformCode(transformCode) 85 | }); 86 | } catch (e) { 87 | throw new Error( 88 | // @ts-ignore 89 | `Error in pyodideWorker at ${e.filename}, Line: ${e.lineno}, ${e.message}` 90 | ); 91 | } 92 | isPyReadyState.set(1); 93 | return res 94 | } 95 | 96 | async function _run( 97 | script: string, 98 | isAsync: boolean, 99 | namespace?: string, 100 | id?: string, 101 | context: Record = {} 102 | ): Promise<{ results: any, error: any }> { 103 | if (pyExecState.get() === 1) { 104 | throw new Error("Only one python script can run at the time") 105 | } 106 | pyExecState.set(1); 107 | // reset logger 108 | const _id = id ?? (+ new Date()).toString(); 109 | pyLog.set({ 110 | id: _id, 111 | stdOut: [], 112 | stdErr: [], 113 | exception: "", 114 | }); 115 | // exec 116 | return new Promise((onSuccess) => { 117 | _callback = onSuccess; 118 | _pyodideWorker.postMessage({ 119 | id: _id, 120 | namespace: namespace, 121 | python: script, 122 | isAsync: isAsync, 123 | ...context, 124 | }); 125 | }); 126 | } 127 | 128 | /** Run a Python script 129 | * @param script the Python code to run 130 | * @param namespace the namemespace where the code will run 131 | * @param id the script id 132 | * @param context some context data to pass to the runtime 133 | */ 134 | async function run( 135 | script: string, 136 | namespace?: string, 137 | id?: string, 138 | context: Record = {} 139 | ): Promise<{ results: any, error: any }> { 140 | return await _run(script, false, namespace, id, context) 141 | } 142 | 143 | /** Run an async Python script 144 | * @param script the Python code to run 145 | * @param namespace the namemespace where the code will run 146 | * @param id the script id 147 | * @param context some context data to pass to the runtime 148 | */ 149 | async function runAsync( 150 | script: string, 151 | namespace?: string, 152 | id?: string, 153 | context: Record = {} 154 | ): Promise<{ results: any, error: any }> { 155 | return await _run(script, true, namespace, id, context) 156 | } 157 | 158 | /** Clear the python memory and logs for a namespace 159 | * @param namespace the namespace to cleanup 160 | */ 161 | async function clear(namespace: string): Promise<{ results: any, error: any }> { 162 | return new Promise((onSuccess) => { 163 | const _cb: (value: { 164 | results: any; 165 | error: any; 166 | } | PromiseLike<{ 167 | results: any; 168 | error: any; 169 | }>) => void = (v) => { 170 | pyLog.set({ 171 | id: "_flushns", 172 | stdOut: [], 173 | stdErr: [], 174 | exception: "", 175 | }); 176 | onSuccess(v) 177 | }; 178 | _callback = _cb; 179 | _pyodideWorker.postMessage({ 180 | id: "_flushns", 181 | namespace: namespace, 182 | }); 183 | }); 184 | } 185 | 186 | return { 187 | load, 188 | run, 189 | runAsync, 190 | clear, 191 | /** The install log store */ 192 | installLog: pyInstallLog, 193 | /** The runtime log store */ 194 | log: pyLog, 195 | /** The execution state atom */ 196 | isExecuting: isPyExecuting, 197 | /** The ready state atom */ 198 | isReady: isPyReady, 199 | } 200 | } 201 | 202 | export { usePython } -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { map, atom, computed } from 'nanostores' 2 | import { PyLog, PyInstallLog } from "./interfaces.js"; 3 | 4 | const pyLog = map({ 5 | id: "", 6 | stdOut: [], 7 | stdErr: [], 8 | exception: "", 9 | }); 10 | 11 | const pyInstallLog = map({ 12 | stage: 0, 13 | msg: "", 14 | }) 15 | 16 | const pyExecState = atom(0); 17 | 18 | const isPyReadyState = atom(0); 19 | 20 | const isPyExecuting = computed(pyExecState, all => { 21 | return all === 1 22 | }) 23 | 24 | const isPyReady = computed(isPyReadyState, all => { 25 | return all === 1 26 | }) 27 | export { pyLog, isPyExecuting, pyExecState, pyInstallLog, isPyReadyState, isPyReady } -------------------------------------------------------------------------------- /src/webworker.js: -------------------------------------------------------------------------------- 1 | importScripts("https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"); 2 | 3 | var isPyLoaded = false; 4 | var namespaces = {}; 5 | //var self.globInit = {}; 6 | 7 | function stdout(msg) { 8 | self.postMessage({ type: "stdout", msg: msg, id: null }) 9 | } 10 | 11 | function stderr(msg) { 12 | self.postMessage({ type: "stderr", msg: msg, id: null }) 13 | } 14 | 15 | function installLog(id, stage, msg) { 16 | self.postMessage({ 17 | type: "installlog", msg: { 18 | stage: stage, 19 | msg: msg 20 | }, id: id 21 | }) 22 | } 23 | 24 | function err(id, msg) { 25 | self.postMessage({ type: "err", msg: msg, id: id }) 26 | } 27 | 28 | function end(id, res) { 29 | self.postMessage({ type: "end", res: res ?? null, id: id }) 30 | } 31 | 32 | async function loadPyodideAndPackages(id, pyoPackages, packages, initCode, transformCode) { 33 | installLog(id, 1, "Loading python runtime") 34 | self.pyodide = await loadPyodide({ 35 | stdout: stdout, 36 | stderr: stderr, 37 | }); 38 | pyoPackages.unshift("micropip"); 39 | //installog(2, `Installing python packages ${packages.join(", ")}`); 40 | installLog(id, 2, `Creating python env`); 41 | await self.pyodide.loadPackage(pyoPackages); 42 | installLog(id, 3, `Installing python packages`); 43 | self.parray = packages 44 | await pyodide.runPythonAsync(` 45 | import micropip 46 | from js import parray 47 | await micropip.install(parray.to_py()) 48 | `); 49 | installLog(id, 4, `Initializing environment`); 50 | self.parray = undefined; 51 | const src = `from pyodide.code import eval_code_async, eval_code 52 | from pyodide.ffi import to_js 53 | async def async_pyeval(code, ns): 54 | _result_ = await eval_code_async(code, ns) 55 | ${transformCode} 56 | return to_js(_result_) 57 | def pyeval(code, ns): 58 | _result_ = eval_code(code, ns) 59 | ${transformCode} 60 | return to_js(_result_)` 61 | //console.log("SRC EXEC", src) 62 | await pyodide.runPythonAsync(src); 63 | if (initCode.length > 0) { 64 | await pyodide.runPython(initCode); 65 | } 66 | installLog(id, 5, "The python env is loaded") 67 | isPyLoaded = true; 68 | } 69 | 70 | async function runScript(python, id, globs, isAsync) { 71 | try { 72 | //console.log("GLOBS", globs); 73 | //console.log("Load imports") 74 | await self.pyodide.loadPackagesFromImports(python); 75 | //console.log("Run py async") 76 | //let results = await self.pyodide.runPythonAsync(python); 77 | let results = await pyodide.globals.get(isAsync ? "async_pyeval" : "pyeval")(python, globs) 78 | //console.log("End", results, _globals) 79 | end(id, results) 80 | } catch (error) { 81 | //console.log("PY RUN ERR", error) 82 | err(id, error.message) 83 | } 84 | } 85 | 86 | self.onmessage = async (event) => { 87 | const { id, namespace, python, isAsync, ...context } = event.data; 88 | if (id == "_flushns") { 89 | namespaces[namespace] = pyodide.globals.get("dict")(); 90 | } 91 | // run 92 | else if (id != "_pyinstaller") { 93 | // The worker copies the context in its own "memory" (an object mapping name to values) 94 | for (const key of Object.keys(context)) { 95 | self[key] = context[key]; 96 | } 97 | if (!isPyLoaded) { 98 | throw new Error("Python is not loaded") 99 | } 100 | let _globs = pyodide.globals; 101 | // check namespace 102 | if (namespace) { 103 | if (namespace in namespaces) { 104 | _globs = namespaces[namespace] 105 | } else { 106 | _globs = pyodide.globals.get("dict")(); 107 | namespaces[namespace] = _globs 108 | } 109 | //console.log("Running script in ns", namespace, _globs) 110 | } /*else { 111 | console.log("Running script in main ns", _globs) 112 | }*/ 113 | if (isAsync) { 114 | await runScript(python, id, _globs, true) 115 | } else { 116 | await runScript(python, id, _globs, false) 117 | } 118 | } else { 119 | await loadPyodideAndPackages(id, context.pyoPackages, context.packages, context.initCode, context.transformCode); 120 | end(id) 121 | } 122 | }; -------------------------------------------------------------------------------- /src/worker.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'web-worker:*' { 2 | const WorkerFactory: new () => Worker; 3 | export default WorkerFactory; 4 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "sourceMap": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "declaration": true, 11 | "allowSyntheticDefaultImports": true, 12 | "outDir": "./dist", 13 | "allowJs": true, 14 | "removeComments": false, 15 | "strictFunctionTypes": true, 16 | "skipLibCheck": true, 17 | "types": [ 18 | "node" 19 | ], 20 | "lib": [ 21 | "es2015", 22 | "dom" 23 | ], 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": [ 27 | "src/*" 28 | ], 29 | "*": [ 30 | "src/*", 31 | "node_modules/*" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "src/**/*.ts", 37 | "src/**/*.d.ts", 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "dist", 42 | "py.ts", 43 | "store.ts", 44 | ], 45 | "typedocOptions": { 46 | "name": "Documentation", 47 | "entryPoints": [ 48 | "src/main.ts" 49 | ], 50 | "out": "doc", 51 | } 52 | } --------------------------------------------------------------------------------