mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Fix many ESlint errors (#1265)
* Unvendor toml package * Fix many ESlint errors For mysterious reasons, these errors appear on my branch #1262 even though they are not related to changes there. The eslint config seems a bit unstable. Anyways this fixes them. * Put back Record * Fix typescript compilation * Fix lints * Try @iarna/toml instead * Fix import * Use @ltd/j-toml * Update test * Use toml-j0.4 * Some changes * Fix toml import * Try adding eslint gha job * Add forgotten checkout action * Force CI to run * Blah * Fix * Revert changes to github workflow * Fix lints * wget toml-j0.4 type definitions * Add toml-j types workaround to eslint workflow * Apply formatter * Use @hoodmane/toml-j0.4 * Import from @hoodmane/toml-j0.4
This commit is contained in:
@@ -18,16 +18,16 @@ module.exports = {
|
||||
plugins: ['@typescript-eslint'],
|
||||
ignorePatterns: ['node_modules'],
|
||||
rules: {
|
||||
'no-prototype-builtins': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/restrict-plus-operands': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'no-prototype-builtins': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { args: 'none' }],
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-argument': 'error',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'error',
|
||||
'@typescript-eslint/no-unsafe-call': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/restrict-plus-operands': 'error',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
24
pyscriptjs/package-lock.json
generated
24
pyscriptjs/package-lock.json
generated
@@ -14,8 +14,8 @@
|
||||
"@codemirror/state": "6.1.2",
|
||||
"@codemirror/theme-one-dark": "6.1.0",
|
||||
"@codemirror/view": "6.3.0",
|
||||
"codemirror": "6.0.1",
|
||||
"toml-j0.4": "^1.1.1"
|
||||
"@hoodmane/toml-j0.4": "^1.1.2",
|
||||
"codemirror": "6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.1.2",
|
||||
@@ -711,6 +711,11 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@hoodmane/toml-j0.4": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@hoodmane/toml-j0.4/-/toml-j0.4-1.1.2.tgz",
|
||||
"integrity": "sha512-vQvFpjssFt39GcYwsFLDts6eReKSWeDwFt3AufXnM62ACIG2MgvOVomNRo4kQUYV0G3+zpu4LezUWw5JCFgF0Q=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.10.7",
|
||||
"dev": true,
|
||||
@@ -5309,11 +5314,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toml-j0.4": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml-j0.4/-/toml-j0.4-1.1.1.tgz",
|
||||
"integrity": "sha512-lYK5otg0+cto8YmsWcPEfeiTiC/VU6P6HA6ooaYI9K/KYT24Jg0BrYtRZK1K3cwakSMyh6nttfJL9RmQH0gyCg=="
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.2",
|
||||
"dev": true,
|
||||
@@ -6166,6 +6166,11 @@
|
||||
"strip-json-comments": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"@hoodmane/toml-j0.4": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@hoodmane/toml-j0.4/-/toml-j0.4-1.1.2.tgz",
|
||||
"integrity": "sha512-vQvFpjssFt39GcYwsFLDts6eReKSWeDwFt3AufXnM62ACIG2MgvOVomNRo4kQUYV0G3+zpu4LezUWw5JCFgF0Q=="
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.10.7",
|
||||
"dev": true,
|
||||
@@ -9148,11 +9153,6 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"toml-j0.4": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml-j0.4/-/toml-j0.4-1.1.1.tgz",
|
||||
"integrity": "sha512-lYK5otg0+cto8YmsWcPEfeiTiC/VU6P6HA6ooaYI9K/KYT24Jg0BrYtRZK1K3cwakSMyh6nttfJL9RmQH0gyCg=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "4.1.2",
|
||||
"dev": true,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@codemirror/state": "6.1.2",
|
||||
"@codemirror/theme-one-dark": "6.1.0",
|
||||
"@codemirror/view": "6.3.0",
|
||||
"codemirror": "6.0.1",
|
||||
"toml-j0.4": "^1.1.1"
|
||||
"@hoodmane/toml-j0.4": "^1.1.2",
|
||||
"codemirror": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { basicSetup, EditorView } from 'codemirror';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { Compartment } from '@codemirror/state';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { keymap, Command } from '@codemirror/view';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { oneDarkTheme } from '@codemirror/theme-one-dark';
|
||||
|
||||
@@ -68,8 +68,8 @@ export function make_PyRepl(interpreter: InterpreterClient) {
|
||||
languageConf.of(python()),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
{ key: 'Ctrl-Enter', run: this.execute.bind(this), preventDefault: true },
|
||||
{ key: 'Shift-Enter', run: this.execute.bind(this), preventDefault: true },
|
||||
{ key: 'Ctrl-Enter', run: this.execute.bind(this) as Command, preventDefault: true },
|
||||
{ key: 'Shift-Enter', run: this.execute.bind(this) as Command, preventDefault: true },
|
||||
]),
|
||||
];
|
||||
|
||||
@@ -134,7 +134,7 @@ export function make_PyRepl(interpreter: InterpreterClient) {
|
||||
runButton.id = 'runButton';
|
||||
runButton.className = 'absolute py-repl-run-button';
|
||||
runButton.innerHTML = RUNBUTTON;
|
||||
runButton.addEventListener('click', this.execute.bind(this));
|
||||
runButton.addEventListener('click', this.execute.bind(this) as (e: MouseEvent) => void);
|
||||
return runButton;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ export function make_PyRepl(interpreter: InterpreterClient) {
|
||||
outEl.innerHTML = '';
|
||||
|
||||
// execute the python code
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const pyResult = (await pyExec(interpreter, pySrc, outEl)).result;
|
||||
|
||||
// display the value of the last evaluated expression (REPL-style)
|
||||
|
||||
@@ -23,7 +23,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
|
||||
*
|
||||
* Concurrent access to the multiple py-script tags is thus avoided.
|
||||
*/
|
||||
let releaseLock: any;
|
||||
let releaseLock: () => void;
|
||||
try {
|
||||
releaseLock = await app.tagExecutionLock();
|
||||
ensureUniqueId(this);
|
||||
@@ -35,6 +35,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
|
||||
this.innerHTML = '';
|
||||
|
||||
app.plugins.beforePyScriptExec({ interpreter: interpreter, src: pySrc, pyScriptTag: this });
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
const result = (await pyExec(interpreter, pySrc, this)).result;
|
||||
app.plugins.afterPyScriptExec({
|
||||
interpreter: interpreter,
|
||||
@@ -42,6 +43,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
|
||||
pyScriptTag: this,
|
||||
result: result,
|
||||
});
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
||||
} finally {
|
||||
releaseLock();
|
||||
}
|
||||
@@ -53,7 +55,8 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
|
||||
try {
|
||||
const response = await robustFetch(url);
|
||||
return await response.text();
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
_createAlertBanner(e.message);
|
||||
this.innerHTML = '';
|
||||
throw e;
|
||||
@@ -163,15 +166,15 @@ const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
|
||||
]);
|
||||
|
||||
/** Initialize all elements with py-* handlers attributes */
|
||||
export function initHandlers(interpreter: InterpreterClient) {
|
||||
export async function initHandlers(interpreter: InterpreterClient) {
|
||||
logger.debug('Initializing py-* event handlers...');
|
||||
for (const pyAttribute of pyAttributeToEvent.keys()) {
|
||||
createElementsWithEventListeners(interpreter, pyAttribute);
|
||||
await createElementsWithEventListeners(interpreter, pyAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes an element with the given py-on* attribute and its handler */
|
||||
function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttribute: string) {
|
||||
async function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttribute: string) {
|
||||
const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pyAttribute}]`);
|
||||
for (const el of matches) {
|
||||
// If the element doesn't have an id, let's add one automatically!
|
||||
@@ -195,7 +198,7 @@ function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttr
|
||||
// the source code may contain a syntax error, which will cause
|
||||
// the splashscreen to not be removed.
|
||||
try {
|
||||
interpreter.run(source);
|
||||
await interpreter.run(source);
|
||||
} catch (e) {
|
||||
logger.error((e as Error).message);
|
||||
}
|
||||
@@ -204,7 +207,8 @@ function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttr
|
||||
void (async () => {
|
||||
try {
|
||||
await interpreter.run(handlerCode);
|
||||
} catch (err) {
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
displayPyException(err, el.parentElement);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PyProxy } from 'pyodide';
|
||||
import type { PyProxy, PyProxyCallable } from 'pyodide';
|
||||
import { getLogger } from '../logger';
|
||||
import { robustFetch } from '../fetch';
|
||||
import { InterpreterClient } from '../interpreter_client';
|
||||
@@ -13,8 +13,8 @@ function createWidget(interpreter: InterpreterClient, name: string, code: string
|
||||
name: string = name;
|
||||
klass: string = klass;
|
||||
code: string = code;
|
||||
proxy: PyProxy;
|
||||
proxyClass: any;
|
||||
proxy: PyProxy & { connect(): void };
|
||||
proxyClass: PyProxyCallable;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -28,8 +28,8 @@ function createWidget(interpreter: InterpreterClient, name: string, code: string
|
||||
|
||||
async connectedCallback() {
|
||||
await interpreter.runButDontRaise(this.code);
|
||||
this.proxyClass = interpreter.globals.get(this.klass);
|
||||
this.proxy = this.proxyClass(this);
|
||||
this.proxyClass = interpreter.globals.get(this.klass) as PyProxyCallable;
|
||||
this.proxy = this.proxyClass(this) as PyProxy & { connect(): void };
|
||||
this.proxy.connect();
|
||||
this.registerWidget();
|
||||
}
|
||||
@@ -39,9 +39,8 @@ function createWidget(interpreter: InterpreterClient, name: string, code: string
|
||||
interpreter.globals.set(this.id, this.proxy);
|
||||
}
|
||||
}
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const xPyWidget = customElements.define(name, CustomWidget);
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
}
|
||||
|
||||
export function make_PyWidget(interpreter: InterpreterClient) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AppConfig } from './pyconfig';
|
||||
import { RemoteInterpreter } from './remote_interpreter';
|
||||
import type { PyProxy } from 'pyodide';
|
||||
import type { PyProxyDict } from 'pyodide';
|
||||
import { getLogger } from './logger';
|
||||
import type { Stdio } from './stdio';
|
||||
|
||||
@@ -16,7 +16,7 @@ export class InterpreterClient extends Object {
|
||||
/**
|
||||
* global symbols table for the underlying interface.
|
||||
* */
|
||||
globals: PyProxy;
|
||||
globals: PyProxyDict;
|
||||
stdio: Stdio;
|
||||
|
||||
constructor(config: AppConfig, stdio: Stdio) {
|
||||
@@ -32,7 +32,7 @@ export class InterpreterClient extends Object {
|
||||
* */
|
||||
async initializeRemote(): Promise<void> {
|
||||
await this._remote.loadInterpreter(this.config, this.stdio);
|
||||
this.globals = this._remote.globals;
|
||||
this.globals = this._remote.globals as PyProxyDict;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,6 +40,7 @@ export class InterpreterClient extends Object {
|
||||
* the remote interpreter.
|
||||
* Python exceptions are turned into JS exceptions.
|
||||
* */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async run(code: string): Promise<{ result: any }> {
|
||||
return await this._remote.run(code);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ interface Logger {
|
||||
debug(message: string, ...args: unknown[]): void;
|
||||
info(message: string, ...args: unknown[]): void;
|
||||
warn(message: string, ...args: unknown[]): void;
|
||||
error(message: string, ...args: unknown[]): void;
|
||||
error(message: string | Error, ...args: unknown[]): void;
|
||||
}
|
||||
|
||||
const _cache = new Map<string, Logger>();
|
||||
@@ -44,8 +44,8 @@ function getLogger(prefix: string): Logger {
|
||||
function _makeLogger(prefix: string): Logger {
|
||||
prefix = `[${prefix}] `;
|
||||
|
||||
function make(level: string) {
|
||||
const out_fn = console[level].bind(console);
|
||||
function make(level: 'info' | 'debug' | 'warn' | 'error') {
|
||||
const out_fn = console[level].bind(console) as typeof console.log;
|
||||
function fn(fmt: string, ...args: unknown[]) {
|
||||
out_fn(prefix + fmt, ...args);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { loadConfigFromElement } from './pyconfig';
|
||||
import type { AppConfig } from './pyconfig';
|
||||
import { InterpreterClient } from './interpreter_client';
|
||||
import { version } from './version';
|
||||
import { PluginManager, define_custom_element } from './plugin';
|
||||
import { PluginManager, define_custom_element, Plugin } from './plugin';
|
||||
import { make_PyScript, initHandlers, mountElements } from './components/pyscript';
|
||||
import { getLogger } from './logger';
|
||||
import { showWarning, globalExport, createLock } from './utils';
|
||||
@@ -16,6 +16,7 @@ import { PyTerminalPlugin } from './plugins/pyterminal';
|
||||
import { SplashscreenPlugin } from './plugins/splashscreen';
|
||||
import { ImportmapPlugin } from './plugins/importmap';
|
||||
import { StdioDirector as StdioDirector } from './plugins/stdiodirector';
|
||||
import type { PyProxy } from 'pyodide';
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
import pyscript from './python/pyscript/__init__.py';
|
||||
@@ -190,7 +191,7 @@ export class PyScriptApp {
|
||||
// lifecycle (8)
|
||||
createCustomElements(interpreter);
|
||||
|
||||
initHandlers(interpreter);
|
||||
await initHandlers(interpreter);
|
||||
|
||||
// NOTE: interpreter message is used by integration tests to know that
|
||||
// pyscript initialization has complete. If you change it, you need to
|
||||
@@ -208,8 +209,8 @@ export class PyScriptApp {
|
||||
logger.info('importing pyscript');
|
||||
|
||||
// Save and load pyscript.py from FS
|
||||
interpreter._remote.interface.FS.mkdirTree('/home/pyodide/pyscript');
|
||||
interpreter._remote.interface.FS.writeFile('pyscript/__init__.py', pyscript);
|
||||
interpreter._remote.FS.mkdirTree('/home/pyodide/pyscript');
|
||||
interpreter._remote.FS.writeFile('pyscript/__init__.py', pyscript as string);
|
||||
//Refresh the module cache so Python consistently finds pyscript module
|
||||
interpreter._remote.invalidate_module_path_cache();
|
||||
|
||||
@@ -218,6 +219,7 @@ export class PyScriptApp {
|
||||
const pyscript_module = interpreter._remote.interface.pyimport('pyscript');
|
||||
pyscript_module.define_custom_element = define_custom_element;
|
||||
pyscript_module.showWarning = showWarning;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
pyscript_module._set_version_info(version);
|
||||
pyscript_module.destroy();
|
||||
|
||||
@@ -303,7 +305,7 @@ export class PyScriptApp {
|
||||
const blobFile = new File([pluginBlob], 'plugin.js', { type: 'text/javascript' });
|
||||
const fileUrl = URL.createObjectURL(blobFile);
|
||||
|
||||
const module = await import(fileUrl);
|
||||
const module = (await import(fileUrl)) as { default: { new (): Plugin } };
|
||||
// Note: We have to put module.default in a variable
|
||||
// because we have seen weird behaviour when doing
|
||||
// new module.default() directly.
|
||||
@@ -346,7 +348,7 @@ export class PyScriptApp {
|
||||
// interpreter API level and allow each one to implement it in its own way
|
||||
const module = interpreter._remote.interface.pyimport(modulename);
|
||||
if (typeof module.plugin !== 'undefined') {
|
||||
const py_plugin = module.plugin;
|
||||
const py_plugin = module.plugin as PyProxy & { init(app: PyScriptApp): void };
|
||||
py_plugin.init(this);
|
||||
this.plugins.addPythonPlugin(py_plugin);
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
|
||||
@typescript-eslint/no-unsafe-call,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import type { AppConfig } from './pyconfig';
|
||||
import type { UserError } from './exceptions';
|
||||
import { getLogger } from './logger';
|
||||
@@ -20,7 +25,9 @@ export class Plugin {
|
||||
* This hook should **NOT** contain expensive operations, else it delays
|
||||
* the download of the python interpreter which is initiated later.
|
||||
*/
|
||||
configure(config: AppConfig) {}
|
||||
configure(_config: AppConfig) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** The preliminary initialization phase is complete and we are about to
|
||||
* download and launch the Python interpreter.
|
||||
@@ -32,14 +39,18 @@ export class Plugin {
|
||||
* This hook should **NOT** contain expensive operations, else it delays
|
||||
* the download of the python interpreter which is initiated later.
|
||||
*/
|
||||
beforeLaunch(config: AppConfig) {}
|
||||
beforeLaunch(_config: AppConfig) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** The Python interpreter has been launched, the virtualenv has been
|
||||
* installed and we are ready to execute user code.
|
||||
*
|
||||
* The <py-script> tags will be executed after this hook.
|
||||
*/
|
||||
afterSetup(interpreter: InterpreterClient) {}
|
||||
afterSetup(_interpreter: InterpreterClient) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** The source of a <py-script>> tag has been fetched, and we're about
|
||||
* to evaluate that source using the provided interpreter.
|
||||
@@ -48,7 +59,9 @@ export class Plugin {
|
||||
* @param options.src {string} The Python source code to be evaluated
|
||||
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
|
||||
*/
|
||||
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {}
|
||||
beforePyScriptExec(_options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** The Python in a <py-script> has just been evaluated, but control
|
||||
* has not been ceded back to the JavaScript event loop yet
|
||||
@@ -58,22 +71,28 @@ export class Plugin {
|
||||
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
|
||||
* @param options.result The returned result of evaluating the Python (if any)
|
||||
*/
|
||||
afterPyScriptExec(options: {
|
||||
afterPyScriptExec(_options: {
|
||||
interpreter: InterpreterClient;
|
||||
src: string;
|
||||
pyScriptTag: PyScriptTag;
|
||||
result: any;
|
||||
}) {}
|
||||
}) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** Startup complete. The interpreter is initialized and ready, user
|
||||
* scripts have been executed: the main initialization logic ends here and
|
||||
* the page is ready to accept user interactions.
|
||||
*/
|
||||
afterStartup(interpreter: InterpreterClient) {}
|
||||
afterStartup(_interpreter: InterpreterClient) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
/** Called when an UserError is raised
|
||||
*/
|
||||
onUserError(error: UserError) {}
|
||||
onUserError(_error: UserError) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
export class PluginManager {
|
||||
|
||||
@@ -25,7 +25,8 @@ export class ImportmapPlugin extends Plugin {
|
||||
const importmap: ImportMapType = (() => {
|
||||
try {
|
||||
return JSON.parse(node.textContent) as ImportMapType;
|
||||
} catch (error) {
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
showWarning('Failed to parse import map: ' + error.message);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -16,7 +16,7 @@ export class PyTerminalPlugin extends Plugin {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
configure(config: AppConfig) {
|
||||
configure(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
||||
// validate the terminal config and handle default values
|
||||
const t = config.terminal;
|
||||
if (t !== undefined && t !== true && t !== false && t !== 'auto') {
|
||||
@@ -32,7 +32,7 @@ export class PyTerminalPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
beforeLaunch(config: AppConfig) {
|
||||
beforeLaunch(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
||||
// if config.terminal is "yes" or "auto", let's add a <py-terminal> to
|
||||
// the document, unless it's already present.
|
||||
const t = config.terminal;
|
||||
@@ -46,7 +46,7 @@ export class PyTerminalPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
afterSetup(interpreter: InterpreterClient) {
|
||||
afterSetup(_interpreter: InterpreterClient) {
|
||||
// the Python interpreter has been initialized and we are ready to
|
||||
// execute user code:
|
||||
//
|
||||
|
||||
@@ -22,7 +22,9 @@ export class SplashscreenPlugin extends Plugin {
|
||||
autoclose: boolean;
|
||||
enabled: boolean;
|
||||
|
||||
configure(config: AppConfig) {
|
||||
configure(
|
||||
config: AppConfig & { splashscreen?: { autoclose?: boolean; enabled?: boolean }; autoclose_loader?: boolean },
|
||||
) {
|
||||
// the officially supported setting is config.splashscreen.autoclose,
|
||||
// but we still also support the old config.autoclose_loader (with a
|
||||
// deprecation warning)
|
||||
@@ -40,7 +42,7 @@ export class SplashscreenPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
beforeLaunch(config: AppConfig) {
|
||||
beforeLaunch(_config: AppConfig) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -50,18 +52,18 @@ export class SplashscreenPlugin extends Plugin {
|
||||
this.elem = <PySplashscreen>document.createElement('py-splashscreen');
|
||||
document.body.append(this.elem);
|
||||
document.addEventListener('py-status-message', (e: CustomEvent) => {
|
||||
const msg = e.detail;
|
||||
const msg = e.detail as string;
|
||||
this.elem.log(msg);
|
||||
});
|
||||
}
|
||||
|
||||
afterStartup(interpreter: InterpreterClient) {
|
||||
afterStartup(_interpreter: InterpreterClient) {
|
||||
if (this.autoclose && this.enabled) {
|
||||
this.elem.close();
|
||||
}
|
||||
}
|
||||
|
||||
onUserError(error: UserError) {
|
||||
onUserError(_error: UserError) {
|
||||
if (this.elem !== undefined && this.enabled) {
|
||||
// Remove the splashscreen so users can see the banner better
|
||||
this.elem.close();
|
||||
|
||||
@@ -47,7 +47,7 @@ export class StdioDirector extends Plugin {
|
||||
interpreter: InterpreterClient;
|
||||
src: string;
|
||||
pyScriptTag: PyScriptTag;
|
||||
result: any;
|
||||
result: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}): void {
|
||||
if (options.pyScriptTag.stdout_manager != null) {
|
||||
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import toml from 'toml-j0.4';
|
||||
import toml from '@hoodmane/toml-j0.4';
|
||||
import { getLogger } from './logger';
|
||||
import { version } from './version';
|
||||
import { getAttribute, readTextFromPath, htmlDecode, createDeprecationWarning } from './utils';
|
||||
@@ -6,6 +6,7 @@ import { UserError, ErrorCode } from './exceptions';
|
||||
|
||||
const logger = getLogger('py-config');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface AppConfig extends Record<string, any> {
|
||||
name?: string;
|
||||
description?: string;
|
||||
@@ -42,11 +43,11 @@ export type PyScriptMetadata = {
|
||||
time?: string;
|
||||
};
|
||||
|
||||
const allKeys = {
|
||||
const allKeys = Object.entries({
|
||||
string: ['name', 'description', 'version', 'type', 'author_name', 'author_email', 'license'],
|
||||
number: ['schema_version'],
|
||||
array: ['runtimes', 'interpreters', 'packages', 'fetch', 'plugins'],
|
||||
};
|
||||
});
|
||||
|
||||
export const defaultConfig: AppConfig = {
|
||||
schema_version: 1,
|
||||
@@ -106,6 +107,7 @@ function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfi
|
||||
for (const key in inputConfig) {
|
||||
// fill in all extra keys ignored by the validator
|
||||
if (!(key in defaultConfig)) {
|
||||
// eslint-disable-next-line
|
||||
resultConfig[key] = inputConfig[key];
|
||||
}
|
||||
}
|
||||
@@ -122,13 +124,14 @@ function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppCon
|
||||
} else {
|
||||
let merged: AppConfig = {};
|
||||
|
||||
for (const keyType in allKeys) {
|
||||
const keys: string[] = allKeys[keyType];
|
||||
for (const [keyType, keys] of allKeys) {
|
||||
keys.forEach(function (item: string) {
|
||||
if (keyType === 'boolean') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
merged[item] =
|
||||
typeof inlineConfig[item] !== 'undefined' ? inlineConfig[item] : externalConfig[item];
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
merged[item] = inlineConfig[item] || externalConfig[item];
|
||||
}
|
||||
});
|
||||
@@ -143,7 +146,7 @@ function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppCon
|
||||
}
|
||||
}
|
||||
|
||||
function parseConfig(configText: string, configType = 'toml') {
|
||||
function parseConfig(configText: string, configType = 'toml'): AppConfig {
|
||||
if (configType === 'toml') {
|
||||
// TOML parser is soft and can parse even JSON strings, this additional check prevents it.
|
||||
if (configText.trim()[0] === '{') {
|
||||
@@ -153,8 +156,9 @@ function parseConfig(configText: string, configType = 'toml') {
|
||||
);
|
||||
}
|
||||
try {
|
||||
return toml.parse(configText);
|
||||
} catch (err) {
|
||||
return toml.parse(configText) as AppConfig;
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
const errMessage: string = err.toString();
|
||||
|
||||
throw new UserError(
|
||||
@@ -164,8 +168,9 @@ function parseConfig(configText: string, configType = 'toml') {
|
||||
}
|
||||
} else if (configType === 'json') {
|
||||
try {
|
||||
return JSON.parse(configText);
|
||||
} catch (err) {
|
||||
return JSON.parse(configText) as AppConfig;
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
const errMessage: string = err.toString();
|
||||
throw new UserError(
|
||||
ErrorCode.BAD_CONFIG,
|
||||
@@ -185,17 +190,17 @@ function validateConfig(configText: string, configType = 'toml') {
|
||||
|
||||
const finalConfig: AppConfig = {};
|
||||
|
||||
for (const keyType in allKeys) {
|
||||
const keys: string[] = allKeys[keyType];
|
||||
for (const [keyType, keys] of allKeys) {
|
||||
keys.forEach(function (item: string) {
|
||||
if (validateParamInConfig(item, keyType, config)) {
|
||||
if (item === 'interpreters') {
|
||||
finalConfig[item] = [];
|
||||
const interpreters = config[item] as InterpreterConfig[];
|
||||
const interpreters = config[item];
|
||||
interpreters.forEach(function (eachInterpreter: InterpreterConfig) {
|
||||
const interpreterConfig: InterpreterConfig = {};
|
||||
for (const eachInterpreterParam in eachInterpreter) {
|
||||
if (validateParamInConfig(eachInterpreterParam, 'string', eachInterpreter)) {
|
||||
// eslint-disable-next-line
|
||||
interpreterConfig[eachInterpreterParam] = eachInterpreter[eachInterpreterParam];
|
||||
}
|
||||
}
|
||||
@@ -214,11 +219,12 @@ function validateConfig(configText: string, configType = 'toml') {
|
||||
'',
|
||||
);
|
||||
finalConfig['interpreters'] = [];
|
||||
const interpreters = config[item] as InterpreterConfig[];
|
||||
const interpreters = config[item];
|
||||
interpreters.forEach(function (eachInterpreter: InterpreterConfig) {
|
||||
const interpreterConfig: InterpreterConfig = {};
|
||||
for (const eachInterpreterParam in eachInterpreter) {
|
||||
if (validateParamInConfig(eachInterpreterParam, 'string', eachInterpreter)) {
|
||||
// eslint-disable-next-line
|
||||
interpreterConfig[eachInterpreterParam] = eachInterpreter[eachInterpreterParam];
|
||||
}
|
||||
}
|
||||
@@ -226,18 +232,20 @@ function validateConfig(configText: string, configType = 'toml') {
|
||||
});
|
||||
} else if (item === 'fetch') {
|
||||
finalConfig[item] = [];
|
||||
const fetchList = config[item] as FetchConfig[];
|
||||
const fetchList = config[item];
|
||||
fetchList.forEach(function (eachFetch: FetchConfig) {
|
||||
const eachFetchConfig: FetchConfig = {};
|
||||
for (const eachFetchConfigParam in eachFetch) {
|
||||
const targetType = eachFetchConfigParam === 'files' ? 'array' : 'string';
|
||||
if (validateParamInConfig(eachFetchConfigParam, targetType, eachFetch)) {
|
||||
// eslint-disable-next-line
|
||||
eachFetchConfig[eachFetchConfigParam] = eachFetch[eachFetchConfigParam];
|
||||
}
|
||||
}
|
||||
finalConfig[item].push(eachFetchConfig);
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
finalConfig[item] = config[item];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,20 @@ import { getLogger } from './logger';
|
||||
import { ensureUniqueId } from './utils';
|
||||
import { UserError, ErrorCode } from './exceptions';
|
||||
import { InterpreterClient } from './interpreter_client';
|
||||
import type { PyProxy, PyProxyCallable } from 'pyodide';
|
||||
|
||||
const logger = getLogger('pyexec');
|
||||
|
||||
export async function pyExec(interpreter: InterpreterClient, pysrc: string, outElem: HTMLElement) {
|
||||
//This is pyscript.py
|
||||
const pyscript_py = interpreter._remote.interface.pyimport('pyscript');
|
||||
export async function pyExec(
|
||||
interpreter: InterpreterClient,
|
||||
pysrc: string,
|
||||
outElem: HTMLElement,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<{ result: any }> {
|
||||
const pyscript_py = interpreter._remote.interface.pyimport('pyscript') as PyProxy & {
|
||||
set_current_display_target(id: string): void;
|
||||
uses_top_level_await(code: string): boolean;
|
||||
};
|
||||
ensureUniqueId(outElem);
|
||||
pyscript_py.set_current_display_target(outElem.id);
|
||||
try {
|
||||
@@ -23,7 +31,8 @@ export async function pyExec(interpreter: InterpreterClient, pysrc: string, outE
|
||||
);
|
||||
}
|
||||
return await interpreter.run(pysrc);
|
||||
} catch (err) {
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
// XXX: currently we display exceptions in the same position as
|
||||
// the output. But we probably need a better way to do that,
|
||||
// e.g. allowing plugins to intercept exceptions and display them
|
||||
@@ -44,15 +53,17 @@ export async function pyExec(interpreter: InterpreterClient, pysrc: string, outE
|
||||
* pyDisplay(interpreter, obj);
|
||||
* pyDisplay(interpreter, obj, { target: targetID });
|
||||
*/
|
||||
export function pyDisplay(interpreter: InterpreterClient, obj: any, kwargs: object) {
|
||||
const display = interpreter.globals.get('display');
|
||||
if (kwargs === undefined) display(obj);
|
||||
else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function pyDisplay(interpreter: InterpreterClient, obj: any, kwargs: { [k: string]: any } = {}) {
|
||||
const display = interpreter.globals.get('display') as PyProxyCallable;
|
||||
try {
|
||||
display.callKwargs(obj, kwargs);
|
||||
} finally {
|
||||
display.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export function displayPyException(err: any, errElem: HTMLElement) {
|
||||
export function displayPyException(err: Error, errElem: HTMLElement) {
|
||||
//addClasses(errElem, ['py-error'])
|
||||
const pre = document.createElement('pre');
|
||||
pre.className = 'py-error';
|
||||
@@ -65,8 +76,8 @@ export function displayPyException(err: any, errElem: HTMLElement) {
|
||||
} else {
|
||||
// this is very likely a normal JS exception. The best we can do is to
|
||||
// display it as is.
|
||||
logger.error('Non-python exception:\n' + err);
|
||||
pre.innerText = err;
|
||||
logger.error('Non-python exception:\n' + err.toString());
|
||||
pre.innerText = err.toString();
|
||||
}
|
||||
errElem.appendChild(pre);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,23 @@ const logger = getLogger('pyscript/pyodide');
|
||||
export type InterpreterInterface = PyodideInterface | null;
|
||||
|
||||
interface Micropip extends PyProxy {
|
||||
install: (packageName: string | string[]) => Promise<void>;
|
||||
destroy: () => void;
|
||||
install(packageName: string | string[]): Promise<void>;
|
||||
}
|
||||
|
||||
type FSInterface = {
|
||||
writeFile(path: string, data: Uint8Array | string, options?: { canOwn: boolean }): void;
|
||||
mkdirTree(path: string): void;
|
||||
mkdir(path: string): void;
|
||||
};
|
||||
|
||||
type PATHFSInterface = {
|
||||
resolve(path: string): string;
|
||||
};
|
||||
|
||||
type PATHInterface = {
|
||||
dirname(path: string): string;
|
||||
};
|
||||
|
||||
/*
|
||||
RemoteInterpreter class is responsible to process requests from the
|
||||
`InterpreterClient` class -- these can be requests for installation of
|
||||
@@ -33,6 +46,10 @@ such as MicroPython.
|
||||
export class RemoteInterpreter extends Object {
|
||||
src: string;
|
||||
interface: InterpreterInterface;
|
||||
FS: FSInterface;
|
||||
PATH: PATHInterface;
|
||||
PATH_FS: PATHFSInterface;
|
||||
|
||||
globals: PyProxy;
|
||||
// TODO: Remove this once `runtimes` is removed!
|
||||
interpreter: InterpreterInterface;
|
||||
@@ -74,6 +91,12 @@ export class RemoteInterpreter extends Object {
|
||||
},
|
||||
fullStdLib: false,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.FS = this.interface.FS;
|
||||
// eslint-disable-next-line
|
||||
this.PATH = (this.interface as any)._module.PATH;
|
||||
// eslint-disable-next-line
|
||||
this.PATH_FS = (this.interface as any)._module.PATH_FS;
|
||||
|
||||
// TODO: Remove this once `runtimes` is removed!
|
||||
this.interpreter = this.interface;
|
||||
@@ -131,14 +154,15 @@ export class RemoteInterpreter extends Object {
|
||||
// but one of our tests tries to use a locally downloaded older version of pyodide
|
||||
// for which the signature of `loadPackage` accepts the above params as args i.e.
|
||||
// the call uses `logger.info.bind(logger), logger.info.bind(logger)`.
|
||||
const pyodide_version = (await this.run("import sys; sys.modules['pyodide'].__version__")).result.toString();
|
||||
if (pyodide_version.startsWith('0.22')) {
|
||||
const messageCallback = logger.info.bind(logger) as typeof logger.info;
|
||||
if (this.interpreter.version.startsWith('0.22')) {
|
||||
await this.interface.loadPackage(names, {
|
||||
messageCallback: logger.info.bind(logger),
|
||||
errorCallback: logger.info.bind(logger),
|
||||
messageCallback,
|
||||
errorCallback: messageCallback,
|
||||
});
|
||||
} else {
|
||||
await this.interface.loadPackage(names, logger.info.bind(logger), logger.info.bind(logger));
|
||||
// @ts-expect-error Types don't include this deprecated call signature
|
||||
await this.interface.loadPackage(names, messageCallback, messageCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,8 +181,15 @@ export class RemoteInterpreter extends Object {
|
||||
try {
|
||||
await micropip.install(package_name);
|
||||
micropip.destroy();
|
||||
} catch (e) {
|
||||
let exceptionMessage = `Unable to install package(s) '` + package_name + `'.`;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
let fmt_names: string;
|
||||
if (Array.isArray(package_name)) {
|
||||
fmt_names = package_name.join(', ');
|
||||
} else {
|
||||
fmt_names = package_name;
|
||||
}
|
||||
let exceptionMessage = `Unable to install package(s) '${fmt_names}'.`;
|
||||
|
||||
// If we can't fetch `package_name` micropip.install throws a huge
|
||||
// Python traceback in `e.message` this logic is to handle the
|
||||
@@ -166,9 +197,8 @@ export class RemoteInterpreter extends Object {
|
||||
// huge traceback.
|
||||
if (e.message.includes("Can't find a pure Python 3 wheel")) {
|
||||
exceptionMessage +=
|
||||
` Reason: Can't find a pure Python 3 Wheel for package(s) '` +
|
||||
package_name +
|
||||
`'. See: https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel ` +
|
||||
` Reason: Can't find a pure Python 3 Wheel for package(s) '${fmt_names}'.` +
|
||||
`See: https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel ` +
|
||||
`for more information.`;
|
||||
} else if (e.message.includes("Can't fetch metadata")) {
|
||||
exceptionMessage +=
|
||||
@@ -176,7 +206,7 @@ export class RemoteInterpreter extends Object {
|
||||
'Please make sure you have entered a correct package name.';
|
||||
} else {
|
||||
exceptionMessage +=
|
||||
` Reason: ${e.message as string}. Please open an issue at ` +
|
||||
` Reason: ${e.message}. Please open an issue at ` +
|
||||
`https://github.com/pyscript/pyscript/issues/new if you require help or ` +
|
||||
`you think it's a bug.`;
|
||||
}
|
||||
@@ -212,20 +242,16 @@ export class RemoteInterpreter extends Object {
|
||||
* and `/a/b.py` will be placed into `/a/b.py`.
|
||||
*/
|
||||
async loadFromFile(path: string, fetch_path: string): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path = this.interface._module.PATH_FS.resolve(path);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const dir = this.interface._module.PATH.dirname(path);
|
||||
this.interface.FS.mkdirTree(dir);
|
||||
path = this.PATH_FS.resolve(path);
|
||||
const dir: string = this.PATH.dirname(path);
|
||||
this.FS.mkdirTree(dir);
|
||||
|
||||
// `robustFetch` checks for failures in getting a response
|
||||
const response = await robustFetch(fetch_path);
|
||||
const buffer = await response.arrayBuffer();
|
||||
const data = new Uint8Array(buffer);
|
||||
|
||||
this.interface.FS.writeFile(path, data, { canOwn: true });
|
||||
this.FS.writeFile(path, data, { canOwn: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,7 +259,7 @@ export class RemoteInterpreter extends Object {
|
||||
* caches to the underlying interface
|
||||
*/
|
||||
invalidate_module_path_cache(): void {
|
||||
const importlib = this.interface.pyimport('importlib');
|
||||
const importlib = this.interface.pyimport('importlib') as PyProxy & { invalidate_caches(): void };
|
||||
importlib.invalidate_caches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export function createSingularWarning(msg: string, sentinelText: string | null =
|
||||
* @returns A new asynchronous lock
|
||||
* @private
|
||||
*/
|
||||
export function createLock() {
|
||||
export function createLock(): () => Promise<() => void> {
|
||||
// This is a promise that is resolved when the lock is open, not resolved when lock is held.
|
||||
let _lock = Promise.resolve();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user