├── .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 | [](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 | [](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 |
2 |
8 |
11 |
14 |
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 |
2 |
3 |
4 |
8 |
13 |
16 |
19 |
20 |
21 |
95 |
96 |
--------------------------------------------------------------------------------
/examples/vuejs/src/widgets/PyStatus.vue:
--------------------------------------------------------------------------------
1 |
2 | ]
3 |
4 |
5 |
6 |
{{ pyInstallLog.msg }}
7 |
8 |
9 |
10 |
11 |
12 |
{{ pyInstallLog.msg }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | }
--------------------------------------------------------------------------------