Skip to content

support different pyodide versions #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 11, 2022
10 changes: 10 additions & 0 deletions pyscriptjs/examples/simple_clock.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
- paths:
- ./utils.py
</py-env>
<py-config>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like this pattern

- autoclose_loader: false
- runtimes:
-
src: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
name: pyodide-0.20
lang: python
</py-config>
</head>

<body>
Expand Down Expand Up @@ -43,6 +51,8 @@
else:
out3.clear()

# close the global PyScript pyscript_loader
pyscript_loader.close()
pyscript.run_until_complete(foo())
</py-script>
</body>
Expand Down
75 changes: 0 additions & 75 deletions pyscriptjs/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,76 +1,5 @@
<script lang="ts">
import Tailwind from './Tailwind.svelte';
import { loadInterpreter } from './interpreter';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this moved? So it's easier to use with the new array of runtimes that should be on the pyconfig? Or just in general this is the wrong place for this kind of config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it because the whole logic was moved to pyconfig.ts and not used here anymore, if needed, other parts can import directly from ./interpreter

The reason I moved to pyconfig was that it helps isolate the whole logic around declaring and initializing runtimes somewhere more contained than just the App. Not sure if pyconfig is the best place for it to be, but it's better than before.

I think there's less and less need for us to have the svelte app... and this also helps us understand how much we depend on it. Atm, afaict, we are only relying on its stores.

import type { AppConfig } from './components/pyconfig';
import { initializers, loadedEnvironments, mode, postInitializers, pyodideLoaded, scriptsQueue, globalLoader, appConfig } from './stores';

let pyodideReadyPromise;

let loader;
let appConfig_: AppConfig = {
autoclose_loader: true,
};

globalLoader.subscribe(value => {
loader = value;
});

appConfig.subscribe( (value:AppConfig) => {
if (value){
appConfig_ = value;
}
console.log("config set!")
});

const initializePyodide = async () => {
loader.log("Loading runtime...")
pyodideReadyPromise = loadInterpreter();
const pyodide = await pyodideReadyPromise;
const newEnv = {
id: 'a',
promise: pyodideReadyPromise,
runtime: pyodide,
state: 'loading',
};
pyodideLoaded.set(pyodide);

// Inject the loader into the runtime namespace
pyodide.globals.set("pyscript_loader", loader);

loader.log("Runtime created...")
loadedEnvironments.update((value: any): any => {
value[newEnv['id']] = newEnv;
});

// now we call all initializers before we actually executed all page scripts
loader.log("Initializing components...")
for (let initializer of $initializers) {
await initializer();
}

// now we can actually execute the page scripts if we are in play mode
loader.log("Initializing scripts...")
if ($mode == 'play') {
for (let script of $scriptsQueue) {
await script.evaluate();
}
scriptsQueue.set([]);
}

// now we call all post initializers AFTER we actually executed all page scripts
loader.log("Running post initializers...");

if (appConfig_ && appConfig_.autoclose_loader) {
loader.close();
console.log("------ loader closed ------");
}

setTimeout(async () => {
for (let initializer of $postInitializers) {
await initializer();
}
}, 3000);
};
</script>

<style global>
Expand Down Expand Up @@ -105,8 +34,4 @@
}
</style>

<svelte:head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js" on:load={initializePyodide}></script>
</svelte:head>

<Tailwind />
151 changes: 143 additions & 8 deletions pyscriptjs/src/components/pyconfig.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,133 @@
import * as jsyaml from 'js-yaml';
import { BaseEvalElement } from './base';
import { appConfig } from '../stores';
import { initializers, loadedEnvironments, mode, postInitializers, pyodideLoaded, scriptsQueue, globalLoader, appConfig, Initializer } from '../stores';
import { loadInterpreter } from '../interpreter';
import type { PyScript } from './pyscript';

let appConfig_;

appConfig.subscribe(value => {
appConfig_ = value;
});
const DEFAULT_RUNTIME = {
src: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js",
name: "pyodide-default",
lang: "python"
}

export type Runtime = {
src: string;
name?: string;
lang?: string;
};

export type AppConfig = {
autoclose_loader: boolean;
name?: string;
version?: string;
};
runtimes?: Array<Runtime>;
};

let appConfig_: AppConfig = {
autoclose_loader: true,
};

appConfig.subscribe( (value:AppConfig) => {
if (value){
appConfig_ = value;
}
console.log("config set!")
});

let initializers_: Initializer[];
initializers.subscribe( (value:Initializer[]) => {
initializers_ = value;
console.log("initializers set")
});

let postInitializers_: Initializer[];
postInitializers.subscribe( (value:Initializer[]) => {
postInitializers_ = value;
console.log("post initializers set")
});

let scriptsQueue_: PyScript[];
scriptsQueue.subscribe( (value: PyScript[]) => {
scriptsQueue_ = value;
console.log("post initializers set")
});

let mode_: string;
mode.subscribe( (value:string) => {
mode_ = value;
console.log("post initializers set")
});


let pyodideReadyPromise;
let loader;


globalLoader.subscribe(value => {
loader = value;
});


export class PyodideRuntime extends Object{
src: string;

constructor(url:string) {
super();
this.src = url;
}

async initialize(){
loader.log("Loading runtime...")
pyodideReadyPromise = loadInterpreter(this.src);
const pyodide = await pyodideReadyPromise;
const newEnv = {
id: 'a',
promise: pyodideReadyPromise,
runtime: pyodide,
state: 'loading',
};
pyodideLoaded.set(pyodide);

// Inject the loader into the runtime namespace
pyodide.globals.set("pyscript_loader", loader);

loader.log("Runtime created...")
loadedEnvironments.update((value: any): any => {
value[newEnv['id']] = newEnv;
});

// now we call all initializers before we actually executed all page scripts
loader.log("Initializing components...")
for (const initializer of initializers_) {
await initializer();
}

// now we can actually execute the page scripts if we are in play mode
loader.log("Initializing scripts...")
if (mode_ == 'play') {
for (const script of scriptsQueue_) {
script.evaluate();
}
scriptsQueue.set([]);
}

// now we call all post initializers AFTER we actually executed all page scripts
loader.log("Running post initializers...");

if (appConfig_ && appConfig_.autoclose_loader) {
loader.close();
console.log("------ loader closed ------");
}

setTimeout(() => {
for (const initializer of postInitializers_) {
initializer();
}
}, 3000);
}
}


export class PyConfig extends BaseEvalElement {
shadow: ShadowRoot;
Expand All @@ -33,14 +148,21 @@ export class PyConfig extends BaseEvalElement {
this.code = this.innerHTML;
this.innerHTML = '';

this.values = jsyaml.load(this.code);
if (this.values === undefined){
const loadedValues = jsyaml.load(this.code);
if (loadedValues === undefined){
this.values = {
autoclose_loader: true,
};
}else{
this.values = Object.assign({}, ...loadedValues);
}
if (this.values.runtimes === undefined){
this.values.runtimes = [DEFAULT_RUNTIME];
}
appConfig.set(this.values);
console.log("config set", this.values);

this.loadRuntimes();
}

log(msg: string){
Expand All @@ -52,4 +174,17 @@ export class PyConfig extends BaseEvalElement {
close() {
this.remove();
}

loadRuntimes(){
console.log("Initializing runtimes...")
for (const runtime of this.values.runtimes) {
const script = document.createElement("script"); // create a script DOM node
const runtimeSpec = new PyodideRuntime(runtime.src);
script.src = runtime.src; // set its src to the provided URL
script.onload = () => {
runtimeSpec.initialize();
}
document.head.appendChild(script);
}
}
}
3 changes: 2 additions & 1 deletion pyscriptjs/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { getLastPath } from './utils';
let pyodideReadyPromise;
let pyodide;

const loadInterpreter = async function (): Promise<any> {
const loadInterpreter = async function (indexUrl:string): Promise<any> {
console.log('creating pyodide runtime');
// eslint-disable-next-line
// @ts-ignore
pyodide = await loadPyodide({
// indexURL: indexUrl,
stdout: console.log,
stderr: console.log,
fullStdLib: false
Expand Down
3 changes: 1 addition & 2 deletions pyscriptjs/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ const xPyWidget = customElements.define('py-register-widget', PyWidget);
const xPyLoader = customElements.define('py-loader', PyLoader);
const xPyConfig = customElements.define('py-config', PyConfig);


// As first thing, loop for application configs
const config = document.querySelector('py-config');
const config: PyConfig = document.querySelector('py-config');
if (!config){
const loader = document.createElement('py-config');
document.body.append(loader);
Expand Down
2 changes: 1 addition & 1 deletion pyscriptjs/src/stores.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { writable } from 'svelte/store';
import type { PyScript } from './components/pyscript';

type Initializer = () => Promise<void>;
export type Initializer = () => Promise<void>;

export const pyodideLoaded = writable({
loaded: false,
Expand Down
1 change: 0 additions & 1 deletion pyscriptjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"sourceMap": true,
/** Requests the runtime types from the svelte modules by default. Needed for TS files or else you get errors. */
"types": ["svelte"],

"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
Expand Down