mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
This PR is the first step to improve and rationalize the life-cycle of a pyscript app along the lines of what I described in #763 . It is not a complete solution, more PRs will follow. Highlights: - py-config is no longer a web component: the old code relied on PyConfig.connectedCallback to do some logic, but then if no <py-config> tag was present, we had to introduce a dummy one with the sole goal of activating the callback. Now the logic is much more linear. - the new pyconfig.ts only contains the code which is needed to parse the config; I also moved some relevant code from utils.ts because it didn't really belong to it - the old PyConfig class did much more than dealing with the config: in particular, it contained the code to initialize the env and the runtime. Now this logic has been moved directly into main.ts, inside the new PyScriptApp class. I plan to refactor the initialization code in further PRs - the current code relies too much on global state and global variables, they are everywhere. This PR is a first step to solve the problem by introducing a PyScriptApp class, which will hold all the mutable state of the page. Currently only config is stored there, but eventually I will migrate more state to it, until we will have only one global singleton, globalApp - thanks to what I described above, I could kill the appConfig svelte store: one less store to kill :).
173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
import type { AppConfig } from './pyconfig';
|
|
import type { PyodideInterface } from 'pyodide';
|
|
import type { PyLoader } from './components/pyloader';
|
|
import {
|
|
runtimeLoaded,
|
|
loadedEnvironments,
|
|
globalLoader,
|
|
initializers,
|
|
postInitializers,
|
|
Initializer,
|
|
scriptsQueue,
|
|
} from './stores';
|
|
import { createCustomElements } from './components/elements';
|
|
import type { PyScript } from './components/pyscript';
|
|
import { getLogger } from './logger';
|
|
|
|
const logger = getLogger('pyscript/runtime');
|
|
|
|
export const version = "<<VERSION>>";
|
|
export type RuntimeInterpreter = PyodideInterface | null;
|
|
|
|
|
|
let loader: PyLoader | undefined;
|
|
globalLoader.subscribe(value => {
|
|
loader = value;
|
|
});
|
|
|
|
let initializers_: Initializer[];
|
|
initializers.subscribe((value: Initializer[]) => {
|
|
initializers_ = value;
|
|
});
|
|
|
|
let postInitializers_: Initializer[];
|
|
postInitializers.subscribe((value: Initializer[]) => {
|
|
postInitializers_ = value;
|
|
});
|
|
|
|
let scriptsQueue_: PyScript[];
|
|
scriptsQueue.subscribe((value: PyScript[]) => {
|
|
scriptsQueue_ = value;
|
|
});
|
|
|
|
|
|
/*
|
|
Runtime class is a super class that all different runtimes must respect
|
|
and adhere to.
|
|
|
|
Currently, the only runtime available is Pyodide as indicated by the
|
|
`RuntimeInterpreter` type above. This serves as a Union of types of
|
|
different runtimes/interpreters which will be added in near future.
|
|
|
|
The class has abstract methods available which each runtime is supposed
|
|
to implement.
|
|
|
|
Methods available handle loading of the interpreter, initialization,
|
|
running code, loading and installation of packages, loading from files etc.
|
|
|
|
For an example implementation, refer to the `PyodideRuntime` class
|
|
in `pyodide.ts`
|
|
*/
|
|
export abstract class Runtime extends Object {
|
|
config: AppConfig;
|
|
abstract src: string;
|
|
abstract name?: string;
|
|
abstract lang?: string;
|
|
abstract interpreter: RuntimeInterpreter;
|
|
/**
|
|
* global symbols table for the underlying interpreter.
|
|
* */
|
|
abstract globals: any;
|
|
|
|
constructor(config: AppConfig) {
|
|
super();
|
|
this.config = config;
|
|
}
|
|
|
|
/**
|
|
* loads the interpreter for the runtime and saves an instance of it
|
|
* in the `this.interpreter` property along with calling of other
|
|
* additional convenience functions.
|
|
* */
|
|
abstract loadInterpreter(): Promise<void>;
|
|
|
|
/**
|
|
* delegates the code to be run to the underlying interpreter
|
|
* (asynchronously) which can call its own API behind the scenes.
|
|
* */
|
|
abstract run(code: string): Promise<any>;
|
|
|
|
/**
|
|
* delegates the setting of JS objects to
|
|
* the underlying interpreter.
|
|
* */
|
|
abstract registerJsModule(name: string, module: object): void;
|
|
|
|
/**
|
|
* delegates the loading of packages to
|
|
* the underlying interpreter.
|
|
* */
|
|
abstract loadPackage(names: string | string[]): Promise<void>;
|
|
|
|
/**
|
|
* delegates the installation of packages
|
|
* (using a package manager, which can be specific to
|
|
* the runtime) to the underlying interpreter.
|
|
*
|
|
* For Pyodide, we use `micropip`
|
|
* */
|
|
abstract installPackage(package_name: string | string[]): Promise<void>;
|
|
|
|
/**
|
|
* delegates the loading of files to the
|
|
* underlying interpreter.
|
|
* */
|
|
abstract loadFromFile(path: string): Promise<void>;
|
|
|
|
/**
|
|
* initializes the page which involves loading of runtime,
|
|
* as well as evaluating all the code inside <py-script> tags
|
|
* along with initializers and postInitializers
|
|
* */
|
|
async initialize(): Promise<void> {
|
|
loader?.log('Loading runtime...');
|
|
await this.loadInterpreter();
|
|
const newEnv = {
|
|
id: 'default',
|
|
runtime: this,
|
|
state: 'loading',
|
|
};
|
|
runtimeLoaded.set(this);
|
|
|
|
// Inject the loader into the runtime namespace
|
|
// eslint-disable-next-line
|
|
this.globals.set('pyscript_loader', loader);
|
|
|
|
loader?.log('Runtime created...');
|
|
loadedEnvironments.update(environments => ({
|
|
...environments,
|
|
[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();
|
|
}
|
|
|
|
loader?.log('Initializing scripts...');
|
|
for (const script of scriptsQueue_) {
|
|
void script.evaluate();
|
|
}
|
|
scriptsQueue.set([]);
|
|
|
|
// now we call all post initializers AFTER we actually executed all page scripts
|
|
loader?.log('Running post initializers...');
|
|
|
|
// Finally create the custom elements for pyscript such as pybutton
|
|
createCustomElements();
|
|
|
|
if (this.config.autoclose_loader) {
|
|
loader?.close();
|
|
}
|
|
|
|
for (const initializer of postInitializers_) {
|
|
await initializer();
|
|
}
|
|
// NOTE: this message is used by integration tests to know that
|
|
// pyscript initialization has complete. If you change it, you need to
|
|
// change it also in tests/integration/support.py
|
|
logger.info('PyScript page fully initialized');
|
|
}
|
|
}
|