mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-21 03:05:38 -05:00
Rename runtimes with interpreter (#1082)
This commit is contained in:
@@ -2,11 +2,11 @@ import './styles/pyscript_base.css';
|
||||
|
||||
import { loadConfigFromElement } from './pyconfig';
|
||||
import type { AppConfig } from './pyconfig';
|
||||
import type { Runtime } from './runtime';
|
||||
import type { Interpreter } from './interpreter';
|
||||
import { version } from './version';
|
||||
import { PluginManager, define_custom_element } from './plugin';
|
||||
import { make_PyScript, initHandlers, mountElements } from './components/pyscript';
|
||||
import { PyodideRuntime } from './pyodide';
|
||||
import { PyodideInterpreter } from './pyodide';
|
||||
import { getLogger } from './logger';
|
||||
import { showWarning, globalExport } from './utils';
|
||||
import { calculatePaths } from './plugins/fetch';
|
||||
@@ -33,7 +33,7 @@ const logger = getLogger('pyscript/main');
|
||||
|
||||
3. (it used to be "show the splashscreen", but now it's a plugin)
|
||||
|
||||
4. loadRuntime(): start downloading the actual runtime (e.g. pyodide.js)
|
||||
4. loadInterpreter(): start downloading the actual interpreter (e.g. pyodide.js)
|
||||
|
||||
--- wait until (4) has finished ---
|
||||
|
||||
@@ -52,16 +52,16 @@ More concretely:
|
||||
|
||||
- Points 1-4 are implemented sequentially in PyScriptApp.main().
|
||||
|
||||
- PyScriptApp.loadRuntime adds a <script> tag to the document to initiate
|
||||
- PyScriptApp.loadInterpreter adds a <script> tag to the document to initiate
|
||||
the download, and then adds an event listener for the 'load' event, which
|
||||
in turns calls PyScriptApp.afterRuntimeLoad().
|
||||
in turns calls PyScriptApp.afterInterpreterLoad().
|
||||
|
||||
- PyScriptApp.afterRuntimeLoad() implements all the points >= 5.
|
||||
- PyScriptApp.afterInterpreterLoad() implements all the points >= 5.
|
||||
*/
|
||||
|
||||
export class PyScriptApp {
|
||||
config: AppConfig;
|
||||
runtime: Runtime;
|
||||
interpreter: Interpreter;
|
||||
PyScript: ReturnType<typeof make_PyScript>;
|
||||
plugins: PluginManager;
|
||||
_stdioMultiplexer: StdioMultiplexer;
|
||||
@@ -74,7 +74,7 @@ export class PyScriptApp {
|
||||
this._stdioMultiplexer = new StdioMultiplexer();
|
||||
this._stdioMultiplexer.addListener(DEFAULT_STDIO);
|
||||
|
||||
this.plugins.add(new StdioDirector(this._stdioMultiplexer))
|
||||
this.plugins.add(new StdioDirector(this._stdioMultiplexer));
|
||||
}
|
||||
|
||||
// Error handling logic: if during the execution we encounter an error
|
||||
@@ -106,7 +106,7 @@ export class PyScriptApp {
|
||||
this.loadConfig();
|
||||
this.plugins.configure(this.config);
|
||||
this.plugins.beforeLaunch(this.config);
|
||||
this.loadRuntime();
|
||||
this.loadInterpreter();
|
||||
}
|
||||
|
||||
// lifecycle (2)
|
||||
@@ -130,24 +130,25 @@ export class PyScriptApp {
|
||||
}
|
||||
|
||||
// lifecycle (4)
|
||||
loadRuntime() {
|
||||
logger.info('Initializing runtime');
|
||||
if (this.config.runtimes.length == 0) {
|
||||
throw new UserError(ErrorCode.BAD_CONFIG, 'Fatal error: config.runtimes is empty');
|
||||
loadInterpreter() {
|
||||
logger.info('Initializing interpreter');
|
||||
if (this.config.interpreters.length == 0) {
|
||||
throw new UserError(ErrorCode.BAD_CONFIG, 'Fatal error: config.interpreter is empty');
|
||||
}
|
||||
|
||||
if (this.config.runtimes.length > 1) {
|
||||
showWarning('Multiple runtimes are not supported yet.<br />Only the first will be used', 'html');
|
||||
if (this.config.interpreters.length > 1) {
|
||||
showWarning('Multiple interpreters are not supported yet.<br />Only the first will be used', 'html');
|
||||
}
|
||||
const runtime_cfg = this.config.runtimes[0];
|
||||
this.runtime = new PyodideRuntime(
|
||||
|
||||
const interpreter_cfg = this.config.interpreters[0];
|
||||
this.interpreter = new PyodideInterpreter(
|
||||
this.config,
|
||||
this._stdioMultiplexer,
|
||||
runtime_cfg.src,
|
||||
runtime_cfg.name,
|
||||
runtime_cfg.lang,
|
||||
interpreter_cfg.src,
|
||||
interpreter_cfg.name,
|
||||
interpreter_cfg.lang,
|
||||
);
|
||||
this.logStatus(`Downloading ${runtime_cfg.name}...`);
|
||||
this.logStatus(`Downloading ${interpreter_cfg.name}...`);
|
||||
|
||||
// download pyodide by using a <script> tag. Once it's ready, the
|
||||
// "load" event will be fired and the exeuction logic will continue.
|
||||
@@ -156,9 +157,9 @@ export class PyScriptApp {
|
||||
// by the try/catch inside main(): that's why we need to .catch() it
|
||||
// explicitly and call _handleUserErrorMaybe also there.
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = this.runtime.src;
|
||||
script.src = this.interpreter.src;
|
||||
script.addEventListener('load', () => {
|
||||
this.afterRuntimeLoad(this.runtime).catch(error => {
|
||||
this.afterInterpreterLoad(this.interpreter).catch(error => {
|
||||
this._handleUserErrorMaybe(error);
|
||||
});
|
||||
});
|
||||
@@ -170,61 +171,61 @@ export class PyScriptApp {
|
||||
// point (4) to point (5).
|
||||
//
|
||||
// Invariant: this.config is set and available.
|
||||
async afterRuntimeLoad(runtime: Runtime): Promise<void> {
|
||||
async afterInterpreterLoad(interpreter: Interpreter): Promise<void> {
|
||||
console.assert(this.config !== undefined);
|
||||
|
||||
this.logStatus('Python startup...');
|
||||
await runtime.loadInterpreter();
|
||||
await this.interpreter.loadInterpreter();
|
||||
this.logStatus('Python ready!');
|
||||
|
||||
this.logStatus('Setting up virtual environment...');
|
||||
await this.setupVirtualEnv(runtime);
|
||||
mountElements(runtime);
|
||||
await this.setupVirtualEnv(interpreter);
|
||||
mountElements(interpreter);
|
||||
|
||||
// lifecycle (6.5)
|
||||
this.plugins.afterSetup(runtime);
|
||||
this.plugins.afterSetup(interpreter);
|
||||
|
||||
//Refresh module cache in case plugins have modified the filesystem
|
||||
runtime.invalidate_module_path_cache();
|
||||
interpreter.invalidate_module_path_cache();
|
||||
this.logStatus('Executing <py-script> tags...');
|
||||
this.executeScripts(runtime);
|
||||
this.executeScripts(interpreter);
|
||||
|
||||
this.logStatus('Initializing web components...');
|
||||
// lifecycle (8)
|
||||
createCustomElements(runtime);
|
||||
createCustomElements(interpreter);
|
||||
|
||||
initHandlers(runtime);
|
||||
initHandlers(interpreter);
|
||||
|
||||
// NOTE: runtime message is used by integration tests to know that
|
||||
// NOTE: interpreter 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
|
||||
this.logStatus('Startup complete');
|
||||
this.plugins.afterStartup(runtime);
|
||||
this.plugins.afterStartup(interpreter);
|
||||
logger.info('PyScript page fully initialized');
|
||||
}
|
||||
|
||||
// lifecycle (6)
|
||||
async setupVirtualEnv(runtime: Runtime): Promise<void> {
|
||||
async setupVirtualEnv(interpreter: Interpreter): Promise<void> {
|
||||
// XXX: maybe the following calls could be parallelized, instead of
|
||||
// await()ing immediately. For now I'm using await to be 100%
|
||||
// compatible with the old behavior.
|
||||
logger.info('importing pyscript');
|
||||
|
||||
// Save and load pyscript.py from FS
|
||||
runtime.interpreter.FS.writeFile('pyscript.py', pyscript, { encoding: 'utf8' });
|
||||
interpreter.interface.FS.writeFile('pyscript.py', pyscript, { encoding: 'utf8' });
|
||||
//Refresh the module cache so Python consistently finds pyscript module
|
||||
runtime.invalidate_module_path_cache();
|
||||
interpreter.invalidate_module_path_cache();
|
||||
|
||||
// inject `define_custom_element` and showWarning it into the PyScript
|
||||
// module scope
|
||||
const pyscript_module = runtime.interpreter.pyimport('pyscript');
|
||||
const pyscript_module = interpreter.interface.pyimport('pyscript');
|
||||
pyscript_module.define_custom_element = define_custom_element;
|
||||
pyscript_module.showWarning = showWarning;
|
||||
pyscript_module._set_version_info(version);
|
||||
pyscript_module.destroy();
|
||||
|
||||
// import some carefully selected names into the global namespace
|
||||
await runtime.run(`
|
||||
await interpreter.run(`
|
||||
import js
|
||||
import pyscript
|
||||
from pyscript import Element, display, HTML
|
||||
@@ -233,20 +234,20 @@ export class PyScriptApp {
|
||||
|
||||
if (this.config.packages) {
|
||||
logger.info('Packages to install: ', this.config.packages);
|
||||
await runtime.installPackage(this.config.packages);
|
||||
await interpreter.installPackage(this.config.packages);
|
||||
}
|
||||
await this.fetchPaths(runtime);
|
||||
await this.fetchPaths(interpreter);
|
||||
|
||||
//This may be unnecessary - only useful if plugins try to import files fetch'd in fetchPaths()
|
||||
runtime.invalidate_module_path_cache();
|
||||
interpreter.invalidate_module_path_cache();
|
||||
// Finally load plugins
|
||||
await this.fetchUserPlugins(runtime);
|
||||
await this.fetchUserPlugins(interpreter);
|
||||
}
|
||||
|
||||
async fetchPaths(runtime: Runtime) {
|
||||
async fetchPaths(interpreter: Interpreter) {
|
||||
// XXX this can be VASTLY improved: for each path we need to fetch a
|
||||
// URL and write to the virtual filesystem: pyodide.loadFromFile does
|
||||
// it in Python, which means we need to have the runtime
|
||||
// it in Python, which means we need to have the interpreter
|
||||
// initialized. But we could easily do it in JS in parallel with the
|
||||
// download/startup of pyodide.
|
||||
const [paths, fetchPaths] = calculatePaths(this.config.fetch);
|
||||
@@ -255,7 +256,7 @@ export class PyScriptApp {
|
||||
logger.info(` fetching path: ${fetchPaths[i]}`);
|
||||
|
||||
// Exceptions raised from here will create an alert banner
|
||||
await runtime.loadFromFile(paths[i], fetchPaths[i]);
|
||||
await interpreter.loadFromFile(paths[i], fetchPaths[i]);
|
||||
}
|
||||
logger.info('All paths fetched');
|
||||
}
|
||||
@@ -265,15 +266,15 @@ export class PyScriptApp {
|
||||
* be loaded by the PluginManager. Currently, we are just looking
|
||||
* for .py and .js files and calling the appropriate methods.
|
||||
*
|
||||
* @param runtime - runtime that will be used to execute the plugins that need it.
|
||||
* @param interpreter - the interpreter that will be used to execute the plugins that need it.
|
||||
*/
|
||||
async fetchUserPlugins(runtime: Runtime) {
|
||||
async fetchUserPlugins(interpreter: Interpreter) {
|
||||
const plugins = this.config.plugins;
|
||||
logger.info('Plugins to fetch: ', plugins);
|
||||
for (const singleFile of plugins) {
|
||||
logger.info(` fetching plugins: ${singleFile}`);
|
||||
if (singleFile.endsWith('.py')) {
|
||||
await this.fetchPythonPlugin(runtime, singleFile);
|
||||
await this.fetchPythonPlugin(interpreter, singleFile);
|
||||
} else if (singleFile.endsWith('.js')) {
|
||||
await this.fetchJSPlugin(singleFile);
|
||||
} else {
|
||||
@@ -327,26 +328,26 @@ export class PyScriptApp {
|
||||
* Fetch python plugins from a filePath, saves it on the FS and import
|
||||
* it as a module, executing any plugin define the module scope.
|
||||
*
|
||||
* @param runtime - runtime that will execute the plugins
|
||||
* @param interpreter - the interpreter that will execute the plugins
|
||||
* @param filePath - path to the python file to fetch
|
||||
*/
|
||||
async fetchPythonPlugin(runtime: Runtime, filePath: string) {
|
||||
async fetchPythonPlugin(interpreter: Interpreter, filePath: string) {
|
||||
const pathArr = filePath.split('/');
|
||||
const filename = pathArr.pop();
|
||||
// TODO: Would be probably be better to store plugins somewhere like /plugins/python/ or similar
|
||||
const destPath = `./${filename}`;
|
||||
await runtime.loadFromFile(destPath, filePath);
|
||||
await interpreter.loadFromFile(destPath, filePath);
|
||||
|
||||
//refresh module cache before trying to import module files into runtime
|
||||
runtime.invalidate_module_path_cache();
|
||||
//refresh module cache before trying to import module files into interpreter
|
||||
interpreter.invalidate_module_path_cache();
|
||||
|
||||
const modulename = filePath.replace(/^.*[\\/]/, '').replace('.py', '');
|
||||
|
||||
console.log(`importing ${modulename}`);
|
||||
// TODO: This is very specific to Pyodide API and will not work for other interpreters,
|
||||
// when we add support for other interpreters we will need to move this to the
|
||||
// runtime (interpreter) API level and allow each one to implement it in its own way
|
||||
const module = runtime.interpreter.pyimport(modulename);
|
||||
// interpreter API level and allow each one to implement it in its own way
|
||||
const module = interpreter.interface.pyimport(modulename);
|
||||
if (typeof module.plugin !== 'undefined') {
|
||||
const py_plugin = module.plugin;
|
||||
py_plugin.init(this);
|
||||
@@ -358,9 +359,9 @@ modules must contain a "plugin" attribute. For more information check the plugin
|
||||
}
|
||||
|
||||
// lifecycle (7)
|
||||
executeScripts(runtime: Runtime) {
|
||||
// make_PyScript takes a runtime and a PyScriptApp as arguments
|
||||
this.PyScript = make_PyScript(runtime, this);
|
||||
executeScripts(interpreter: Interpreter) {
|
||||
// make_PyScript takes an interpreter and a PyScriptApp as arguments
|
||||
this.PyScript = make_PyScript(interpreter, this);
|
||||
customElements.define('py-script', this.PyScript);
|
||||
}
|
||||
|
||||
@@ -387,4 +388,7 @@ const globalApp = new PyScriptApp();
|
||||
globalApp.main();
|
||||
|
||||
export { version };
|
||||
export const runtime = globalApp.runtime;
|
||||
export const interpreter = globalApp.interpreter;
|
||||
// TODO: This is for backwards compatibility, it should be removed
|
||||
// when we finish the deprecation cycle of `runtime`
|
||||
export const runtime = globalApp.interpreter;
|
||||
|
||||
Reference in New Issue
Block a user