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:
Hood Chatham
2023-03-13 15:51:28 +01:00
committed by GitHub
parent 653e2c9be4
commit 37c9db09c6
18 changed files with 196 additions and 122 deletions

View File

@@ -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',
},
};

View File

@@ -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,

View File

@@ -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"
}
}

View File

@@ -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)

View File

@@ -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);
}
})();

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);
}
})();

View File

@@ -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:
//

View File

@@ -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();

View File

@@ -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);

View File

@@ -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];
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();