mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Export Runtime from PyScript Module (#868)
* export 'pyscript' JS module with runtime attribute, to allow accessing (Pyodide) runtime and globals from JS. * add docs to explain the js module * add integration tests for the js module Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -34,9 +34,46 @@ We can use the syntax `from js import ...` to import JavaScript objects directly
|
||||
|
||||
## PyScript to JavaScript
|
||||
|
||||
Since [PyScript doesn't export its instance of Pyodide](https://github.com/pyscript/pyscript/issues/494) and only one instance of Pyodide can be running in a browser window at a time, there isn't currently a way for Javascript to access Objects defined inside PyScript tags "directly".
|
||||
### Using Pyodide's globals access
|
||||
|
||||
We can work around this limitation using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval). First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
||||
The [PyScript JavaScript module](../reference/pyscript-module.md) exposes its underlying Pyodide runtime as `PyScript.runtime`, and maintains a reference to the [globals()](https://docs.python.org/3/library/functions.html#globals) dictionary of the Python namespace. Thus, any global variables in python are accessible in JavaScript at `PyScript.runtime.globals.get('my_variable_name')`
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${PyScript.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Since [everything is an object](https://docs.python.org/3/reference/datamodel.html) in Python, this applies not only to user created variables, but also to classes, functions, built-ins, etc. If we want, we can even apply Python functions to JavaScript data and variables:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<!-- Click this button to log 'Apple', 'Banana', 'Candy', 'Donut' by sorting in Python-->
|
||||
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
|
||||
<script>
|
||||
function sortInPython(data){
|
||||
js_sorted = PyScript.runtime.globals.get('sorted') //grab python's 'sorted' function
|
||||
const sorted_data = js_sorted(data) //apply the function to the 'data' argument
|
||||
for (const item of sorted_data){
|
||||
console.log(item)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Using JavaScript's eval()
|
||||
|
||||
There may be some situations where it isn't possible or ideal to use `PyScript.runtime.globals.get()` to retrieve a variable from the Pyodide global dictionary. For example, some JavaScript frameworks may take a function/Callable as an html attribute in a context where code execution isn't allowed (i.e. `get()` fails). In these cases, you can create JavaScript proxies of Python objects more or less "manually" using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval).
|
||||
|
||||
First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
||||
|
||||
Include the following script tag anywhere in your html document:
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ You already know the basics and want to learn specifics!
|
||||
|
||||
[Frequently asked questions](reference/faq.md)
|
||||
|
||||
[The PyScript JS Module](reference/pyscript-module.md)
|
||||
|
||||
:::{toctree}
|
||||
:maxdepth: 1
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ maxdepth: 1
|
||||
glob:
|
||||
---
|
||||
faq
|
||||
pyscript-module
|
||||
|
||||
66
docs/reference/pyscript-module.md
Normal file
66
docs/reference/pyscript-module.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# The PyScript Module
|
||||
|
||||
The code underlying PyScript is a TypeScript/JavaScript module, which is loaded and executed by the browser. This is what loads when you include, for example, `<script defer src="https://pyscript.net/latest/pyscript.js">` in your HTML.
|
||||
|
||||
The module is exported to the browser as `pyscript`. The exports from this module are:
|
||||
|
||||
## pyscript.runtime
|
||||
|
||||
The RunTime object which is responsible for executing Python code in the Browser. Currently, all runtimes are assumed to be Pyodide runtimes, but there is flexibility to expand this to other web-based Python runtimes in future versions.
|
||||
|
||||
The RunTime object has the following attributes
|
||||
|
||||
| attribute | type | description |
|
||||
|---------------------|---------------------|-----------------------------------------------------------------------------|
|
||||
| **src** | string | The URL from which the current runtime was fetched |
|
||||
| **interpreter** | RuntimeInterpretter | A reference to the runtime object itself |
|
||||
| **globals** | any | The globals dictionary of the runtime, if applicable/accessible |
|
||||
| **name (optional)** | string | A user-designated name for the runtime |
|
||||
| **lang (optional)** | string | A user-designation for the language the runtime runs ('Python', 'C++', etc) |
|
||||
|
||||
### pyscript.runtime.src
|
||||
|
||||
The URL from which the current runtime was fetched.
|
||||
|
||||
### pyscript.runtime.interpreter
|
||||
|
||||
A reference to the Runtime wrapper that PyScript uses to execute code. object itself. This allows other frameworks, modules etc to interact with the same [(Pyodide) runtime instance](https://pyodide.org/en/stable/usage/api/js-api.html) that PyScript uses.
|
||||
|
||||
For example, assuming we've loaded Pyodide, we can access the methods of the Pyodide runtime as follows:
|
||||
|
||||
```html
|
||||
<button onclick="logFromPython()">Click Me to Run Some Python</button>
|
||||
<script>
|
||||
function logFromPython(){
|
||||
pyscript.runtime.interpreter.runPython(`
|
||||
animal = "Python"
|
||||
sound = "sss"
|
||||
console.warn(f"{animal}s go " + sound * 5)
|
||||
`)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### pyscript.runtime.globals
|
||||
|
||||
A proxy for the runtime's `globals()` dictionary. For example:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${PyScript.runtime.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
### pyscript.runtime.name
|
||||
|
||||
A user-supplied string for the runtime given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.
|
||||
|
||||
### PyScript.runtime.lang
|
||||
|
||||
A user-supplied string for the language the runtime uses given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
sourcemap: true,
|
||||
format: "iife",
|
||||
inlineDynamicImports: true,
|
||||
name: "app",
|
||||
name: "pyscript",
|
||||
file: "build/pyscript.js",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ class PyScriptApp {
|
||||
|
||||
config: AppConfig;
|
||||
loader: PyLoader;
|
||||
runtime: Runtime;
|
||||
|
||||
// lifecycle (1)
|
||||
main() {
|
||||
@@ -111,13 +112,13 @@ class PyScriptApp {
|
||||
"Only the first will be used");
|
||||
}
|
||||
const runtime_cfg = this.config.runtimes[0];
|
||||
const runtime: Runtime = new PyodideRuntime(this.config, runtime_cfg.src,
|
||||
this.runtime = new PyodideRuntime(this.config, runtime_cfg.src,
|
||||
runtime_cfg.name, runtime_cfg.lang);
|
||||
this.loader.log(`Downloading ${runtime_cfg.name}...`);
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = runtime.src;
|
||||
script.src = this.runtime.src;
|
||||
script.addEventListener('load', () => {
|
||||
void this.afterRuntimeLoad(runtime);
|
||||
void this.afterRuntimeLoad(this.runtime);
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
@@ -209,3 +210,5 @@ globalExport('pyscript_get_config', pyscript_get_config);
|
||||
// main entry point of execution
|
||||
const globalApp = new PyScriptApp();
|
||||
globalApp.main();
|
||||
|
||||
export const runtime = globalApp.runtime
|
||||
|
||||
42
pyscriptjs/tests/integration/test_04_runtime.py
Normal file
42
pyscriptjs/tests/integration/test_04_runtime.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from .support import PyScriptTest
|
||||
|
||||
|
||||
class TestRuntimeAccess(PyScriptTest):
|
||||
"""Test accessing Python objects from JS via pyscript.runtime"""
|
||||
|
||||
def test_runtime_python_access(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
x = 1
|
||||
def py_func():
|
||||
return 2
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
|
||||
self.page.add_script_tag(
|
||||
content="""
|
||||
console.log(`x is ${pyscript.runtime.globals.get('x')}`);
|
||||
console.log(`py_func() returns ${pyscript.runtime.globals.get('py_func')()}`);
|
||||
"""
|
||||
)
|
||||
|
||||
assert self.console.log.lines == [
|
||||
self.PY_COMPLETE,
|
||||
"x is 1",
|
||||
"py_func() returns 2",
|
||||
]
|
||||
|
||||
def test_runtime_script_execution(self):
|
||||
"""Test running Python code from js via pyscript.runtime"""
|
||||
self.pyscript_run("")
|
||||
|
||||
self.page.add_script_tag(
|
||||
content="""
|
||||
const interpreter = pyscript.runtime.interpreter;
|
||||
interpreter.runPython('console.log("Interpreter Ran This")');
|
||||
"""
|
||||
)
|
||||
|
||||
assert self.console.log.lines == [self.PY_COMPLETE, "Interpreter Ran This"]
|
||||
Reference in New Issue
Block a user