Executing dynamically created <pyscript>

I am trying to create a div containing a new pyscript and output section. But What do I need to call tun run the new pyscript code? Or at least a declared function within it?

let outputID = "blah"
let div = document.createElement("div");
let html = `
            <py-script output="${outputID}">
               ... python goes here ...
            </py-script>
            <div id="${outputID}">
            </div>
            `;
div.innerHTML = html;

// and yes this div gets added to the body after here

Thanks in advance

1 Like

I see that it constructs and augments the py-script tag correctly. Some processing must be happening. But no output ever shows up in the output div.

Ah never mind I figured it out. Need to call “evaluate()” on the script tag DOM object.

Can you clarify your answer please? How did you call evaluate() on the script tag DOM object?

1 Like

This is typescript. A method execute of some object executes “text” as python. The object has a member variable output which is an HTMLDivElement (DOM div element), and a member variable pyScript in which is the script tag (first element child of an artificial div we create). We also use this to no add the tag repeatedly, but remove it before next time. I guess we could also remove it right after the execution. HTH.

    execute(text: string) {
        // prepare objects exposed to Python
        (window as any).blah= ...

        // pyscript
        let div = document.createElement("div");
        let html = `
            <py-script output="${this.output.id}">
from js import blah
${text}
            </py-script>
            `;
        div.innerHTML = html;

        // if we did this before, remove the script from the body
        if (this.pyScript) {
            this.pyScript.remove();
        }
        // now remember the new script
        this.pyScript = div.firstElementChild;
        try {
             // add it to the body - this will already augment the tag in certain ways
            document.body.appendChild(this.pyScript);
            // execute the code / evaluate the expression
            (this.pyScript as any).evaluate();
        } catch (error) {
            console.error("Python error:");
            console.error(error);
        }

    }

I plain JavaScript it would look more like this:

var myPyScript; // where I remember my pyscript object
var output = document.createElement("div"); // the div object where the output goes
output.id = "myPyScriptOutput";
document.body.appendChild(output);

function execute(text) {
        // prepare objects exposed to Python
        window.myCustomObject = ... ; // purely optional

        // pyscript
        let div = document.createElement("div");
        let html = `
            <py-script output="${output.id}">
from js import myCustomObject; # this is purely optional of course
${text}
            </py-script>
            `;
        div.innerHTML = html;

        // if we did this before, remove the script from the body
        if (myPyScript) {
            myPyScript.remove();
        }
        // now remember the new script
        myPyScript = div.firstElementChild;
        try {
             // add it to the body - this will already augment the tag in certain ways
            document.body.appendChild(myPyScript);
            // execute the code / evaluate the expression
            myPyScript.evaluate();
        } catch (error) {
            console.error("Python error:");
            console.error(error);
        }

    }
1 Like

Not impossible, but also not do-able out of the box. The web components like need to participate in both the page load sequence and the Svelte Store messaging.

I just posted a video which shows how to work in the source (look for the post.) You could make a new web component which, when connectedCallback ran, would grab the child nodes of another div and execute it. Possibly passing the current node if needed.

Thanks Paul! I am back from vacation now. My code works so far, but I will look for your video to see what I am missing.

1 Like

I think this article might help you:

I do all that already, as you can see above. But thanks.

Would this work for userscripts that run in extensions like tampermonkey or the like? For instance create a pyscript tag, add pyscript or @include it, then execute it? Writing scripts here and there to accomplish things around the web would be amazing.

Yes, you can dynamically load and execute Python. At this time, each Python load goes into the same namespace, so be careful with duplicate names. IIRC Pyodide does support namespacing, but those features are not visible to PyScript.

1 Like

Hi @john.hanley, your article really helped in understanding, but I somehow did not get it running.
Especially the line py_div.evaluate(); I always run into TypeError: py_div.evaluate is not a function, although I just copied your code.

I have the same problem with the example of @gunnar. Any idea why evaluate is not defined?

Open the Chrome (browser) debugger. Reload the page. Do you see any other errors?

The TypeError was from the chrome debugger console. I mean it does execute the python correctly. It is just that the line py_div.evaluate(); does not do anything for me. If I comment it out, the python code still gets executed, but I have no error on the chrome debugger console.
If I use your code unedited, I get the following error in the chrome debugger console:

exec_py.js:36 Python error:
run_python @ exec_py.js:36
(anonymous) @ (index):26
exec_py.js:37 TypeError: py_div.evaluate is not a function
    at run_python (exec_py.js:34:24)
    at (index):26:1

These are the first messages on the console log and also the only error messages. The rest are just debug messages:

adding initializer async Ć’ mountElements() {
        console.log('Collecting nodes to be mounted into python namespace...');
        const pyodide = await pyodideReadyPromise$1;
        const matches = document.querySelectorAl…
stores.ts:34 adding initializer async Ć’ mountElements() {
        console.log('Collecting nodes to be mounted into python namespace...');
        const pyodide = await pyodideReadyPromise$1;
        const matches = document.querySelectorAl…
stores.ts:40 adding post initializer async Ć’ initHandlers() {
        console.log('Collecting nodes...');
        const pyodide = await pyodideReadyPromise$1;
        for (const pysAttribute of pysAttributeToEvent.keys()) {
            await cr…
stores.ts:42 adding post initializer async Ć’ initHandlers() {
        console.log('Collecting nodes...');
        const pyodide = await pyodideReadyPromise$1;
        for (const pysAttribute of pysAttributeToEvent.keys()) {
            await cr…
pyenv.ts:10 RUNTIME READY
pyconfig.ts:17 config set!
pyconfig.ts:22 initializers set
pyconfig.ts:27 post initializers set
pyconfig.ts:32 post initializers set
pyconfig.ts:37 post initializers set
pyconfig.ts:32 post initializers set
pyscript.ts:66 connected
pyconfig.ts:17 config set!
pyconfig.ts:112 config set {autoclose_loader: true, runtimes: Array(1)}
pyconfig.ts:124 Initializing runtimes...
interpreter.ts:5 creating pyodide runtime
pyodide.asm.js:14 Python initialization complete
interpreter.ts:15 loading micropip
load-package.ts:329 Loading micropip, pyparsing, packaging, distutils
load-package.ts:389 Loaded micropip, pyparsing, packaging, distutils
interpreter.ts:17 loading pyscript...
interpreter.ts:30 done setting up environment
pyenv.ts:10 RUNTIME READY
pyscript.ts:148 Collecting nodes to be mounted into python namespace...
base.ts:76 evaluate
pyconfig.ts:32 post initializers set
pyconfig.ts:83 ------ loader closed ------
2pyodide.asm.js:14 ----> changed out to myPyScriptOutput true
pyodide.asm.js:14 writing to myPyScriptOutput 2022-07-29 13:26:53.659000 true
pyodide.asm.js:14 APPENDING: True ==> myPyScriptOutput --> 2022-07-29 13:26:53.659000
pyodide.asm.js:14 myPyScriptOutput 2022-07-29 13:26:53.659000
pyodide.asm.js:14 writing to myPyScriptOutput 
 true
pyodide.asm.js:14 APPENDING: True ==> myPyScriptOutput --> 

pyodide.asm.js:14 myPyScriptOutput 

2pyodide.asm.js:14 ----> reverted
pyodide.asm.js:14 ----> reverted
pyscript.ts:111 Collecting nodes...

It still works for me. Do you still have this problem?