Skip to content

Commit

Permalink
Support loadPyodide options (#103)
Browse files Browse the repository at this point in the history
* add loadPyodideOptions

* bump upstream

* resolve yarn

* fix loadPyodideOptions in examples

* linting

---------

Co-authored-by: Jeremy Tuloup <[email protected]>
  • Loading branch information
bollwyvl and jtpio authored Mar 26, 2024
1 parent 6c57f7a commit d9b41b8
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 23 deletions.
8 changes: 6 additions & 2 deletions examples/jupyter-lite.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
"jupyter-lite-schema-version": 0,
"jupyter-config-data": {
"appName": "JupyterLite Pyodide Kernel",
"disabledExtensions": {
"@jupyterlite/javascript-kernel-extension": true
"litePluginSettings": {
"@jupyterlite/pyodide-kernel-extension:kernel": {
"loadPyodideOptions": {
"packages": ["matplotlib", "micropip", "numpy", "sqlite3", "ssl"]
}
}
}
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jupyterlite/pyodide-kernel-root",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"workspaces": {
"packages": [
Expand Down Expand Up @@ -33,7 +33,7 @@
"lint:py": "jlpm lint:py:ruff:fix",
"lint:py:check": "jlpm lint:py:pip && jlpm lint:py:ruff:check",
"lint:py:pip": "python -m pip check",
"lint:py:ruff:fix": "ruff format && ruff --fix-only",
"lint:py:ruff:fix": "ruff format && ruff check --fix-only",
"lint:py:ruff:check": "ruff format --check",
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml,.yaml}\"",
"prettier:check": "jlpm prettier:base --check",
Expand Down
13 changes: 13 additions & 0 deletions packages/pyodide-kernel-extension/schema/kernel.v0.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@
},
"default": [],
"format": "uri"
},
"loadPyodideOptions": {
"type": "object",
"description": "additional options to provide to `loadPyodide`, see https://pyodide.org/en/stable/usage/api/js-api.html#globalThis.loadPyodide",
"default": {},
"properties": {
"packages": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
9 changes: 9 additions & 0 deletions packages/pyodide-kernel-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ const kernel: JupyterLiteServerPlugin<void> = {
const config =
JSON.parse(PageConfig.getOption('litePluginSettings') || '{}')[PLUGIN_ID] || {};
const url = config.pyodideUrl || PYODIDE_CDN_URL;

const pyodideUrl = URLExt.parse(url).href;
const pipliteWheelUrl = config.pipliteWheelUrl
? URLExt.parse(config.pipliteWheelUrl).href
: undefined;
const rawPipUrls = config.pipliteUrls || [];
const pipliteUrls = rawPipUrls.map((pipUrl: string) => URLExt.parse(pipUrl).href);
const disablePyPIFallback = !!config.disablePyPIFallback;
const loadPyodideOptions = config.loadPyodideOptions || {};

for (const [key, value] of Object.entries(loadPyodideOptions)) {
if (key.endsWith('URL') && typeof value === 'string') {
loadPyodideOptions[key] = URLExt.parse(key).href;
}
}

kernelspecs.register({
spec: {
Expand Down Expand Up @@ -81,6 +89,7 @@ const kernel: JupyterLiteServerPlugin<void> = {
pipliteUrls,
disablePyPIFallback,
mountDrive,
loadPyodideOptions,
});
},
});
Expand Down
11 changes: 11 additions & 0 deletions packages/pyodide-kernel/src/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class PyodideKernel extends BaseKernel implements IKernel {
const { pyodideUrl } = options;
const indexUrl = pyodideUrl.slice(0, pyodideUrl.lastIndexOf('/') + 1);
const baseUrl = PageConfig.getBaseUrl();

const pipliteUrls = [...(options.pipliteUrls || []), allJSONUrl.default];

const disablePyPIFallback = !!options.disablePyPIFallback;
Expand All @@ -67,6 +68,7 @@ export class PyodideKernel extends BaseKernel implements IKernel {
disablePyPIFallback,
location: this.location,
mountDrive: options.mountDrive,
loadPyodideOptions: options.loadPyodideOptions || {},
};
}

Expand Down Expand Up @@ -323,5 +325,14 @@ export namespace PyodideKernel {
* Whether or not to mount the Emscripten drive
*/
mountDrive: boolean;

/**
* additional options to provide to `loadPyodide`
* @see https://pyodide.org/en/stable/usage/api/js-api.html#globalThis.loadPyodide
*/
loadPyodideOptions: Record<string, any> & {
pyodideLockURL: string;
packages: string[];
};
}
}
9 changes: 9 additions & 0 deletions packages/pyodide-kernel/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,14 @@ export namespace IPyodideWorkerKernel {
* Whether or not to mount the Emscripten drive
*/
mountDrive: boolean;

/**
* additional options to provide to `loadPyodide`
* @see https://pyodide.org/en/stable/usage/api/js-api.html#globalThis.loadPyodide
*/
loadPyodideOptions: Record<string, any> & {
pyodideLockURL: string;
packages: string[];
};
}
}
57 changes: 38 additions & 19 deletions packages/pyodide-kernel/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export class PyodideRemoteKernel {
importScripts(pyodideUrl);
loadPyodide = (self as any).loadPyodide;
}
this._pyodide = await loadPyodide({ indexURL: indexUrl });
this._pyodide = await loadPyodide({
indexURL: indexUrl,
...options.loadPyodideOptions,
});
}

protected async initPackageManager(
Expand All @@ -60,38 +63,54 @@ export class PyodideRemoteKernel {
throw new Error('Uninitialized');
}

const { pipliteWheelUrl, disablePyPIFallback, pipliteUrls } = this._options;
const { pipliteWheelUrl, disablePyPIFallback, pipliteUrls, loadPyodideOptions } =
this._options;

await this._pyodide.loadPackage(['micropip']);
const preloaded = (loadPyodideOptions || {}).packages || [];

// get piplite early enough to impact pyodide dependencies
await this._pyodide.runPythonAsync(`
if (!preloaded.includes('micropip')) {
await this._pyodide.loadPackage(['micropip']);
}

if (!preloaded.includes('piplite')) {
await this._pyodide.runPythonAsync(`
import micropip
await micropip.install('${pipliteWheelUrl}', keep_going=True)
`);
}

// get piplite early enough to impact pyodide-kernel dependencies
await this._pyodide.runPythonAsync(`
import piplite.piplite
piplite.piplite._PIPLITE_DISABLE_PYPI = ${disablePyPIFallback ? 'True' : 'False'}
piplite.piplite._PIPLITE_URLS = ${JSON.stringify(pipliteUrls)}
`);
}

protected async initKernel(options: IPyodideWorkerKernel.IOptions): Promise<void> {
// from this point forward, only use piplite (but not %pip)
await this._pyodide.runPythonAsync(`
await piplite.install(['ssl'], keep_going=True);
await piplite.install(['sqlite3'], keep_going=True);
await piplite.install(['ipykernel'], keep_going=True);
await piplite.install(['comm'], keep_going=True);
await piplite.install(['pyodide_kernel'], keep_going=True);
await piplite.install(['ipython'], keep_going=True);
import pyodide_kernel
`);
const preloaded = (options.loadPyodideOptions || {}).packages || [];

const toLoad = ['ssl', 'sqlite3', 'ipykernel', 'comm', 'pyodide_kernel', 'ipython'];

const scriptLines: string[] = [];

// use piplite for packages that weren't pre-loaded
for (const pkgName of toLoad) {
if (!preloaded.includes(pkgName)) {
scriptLines.push(`await piplite.install('${pkgName}', keep_going=True)`);
}
}

// import the kernel
scriptLines.push('import pyodide_kernel');

// cd to the kernel location
if (options.mountDrive && this._localPath) {
await this._pyodide.runPythonAsync(`
import os;
os.chdir("${this._localPath}");
`);
scriptLines.push('import os', `os.chdir("${this._localPath}")`);
}

// from this point forward, only use piplite (but not %pip)
await this._pyodide.runPythonAsync(scriptLines.join('\n'));
}

protected async initGlobals(options: IPyodideWorkerKernel.IOptions): Promise<void> {
Expand Down

0 comments on commit d9b41b8

Please sign in to comment.