From cc4192fb21e0ccc953cfc715b520c7efe2a245d5 Mon Sep 17 00:00:00 2001 From: Ben Jeffery Date: Fri, 5 Jun 2026 23:19:04 +0100 Subject: [PATCH] Fix Pyodide sqlite bootstrap --- build.sh | 20 ++++++++++++++--- content-hash.txt | 2 +- tests/test_notebooks_pyodide.py | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index add8a83..c0a9468 100755 --- a/build.sh +++ b/build.sh @@ -37,22 +37,36 @@ kernel_extension_dir = Path( load_pyodide_pattern = re.compile( r"\(await ([A-Za-z_$][\w$]*|__webpack_require__)\(476\)\(([A-Za-z_$][\w$]*)\)\)\.loadPyodide" ) +dynamic_import_patched_files = [] patched_files = [] worker_type_patched_files = [] +sqlite_bootstrap_patched_files = [] for path in kernel_extension_dir.glob("*.js"): source = path.read_text() patched = load_pyodide_pattern.sub(r"(await import(\2)).loadPyodide", source) + if patched != source: + dynamic_import_patched_files.append(path.name) + before_worker_patch = patched patched = patched.replace("{type:void 0}", '{type:"module"}') + if "{type:void 0}" in before_worker_patch and '{type:"module"}' in patched: + worker_type_patched_files.append(path.name) + before_sqlite_patch = patched + patched = patched.replace( + '["sqlite3","ipykernel","comm","pyodide_kernel","jedi","ipython"]', + '["ipykernel","comm","pyodide_kernel","jedi","ipython"]', + ) + if '"sqlite3","ipykernel"' in before_sqlite_patch and '"sqlite3","ipykernel"' not in patched: + sqlite_bootstrap_patched_files.append(path.name) if patched != source: path.write_text(patched) patched_files.append(path.name) - if "{type:void 0}" in source and '{type:"module"}' in patched: - worker_type_patched_files.append(path.name) -if not patched_files: +if not dynamic_import_patched_files: raise SystemExit("No Pyodide dynamic import bundle entry was patched") if not worker_type_patched_files: raise SystemExit("No Pyodide worker type entry was patched") +if not sqlite_bootstrap_patched_files: + raise SystemExit("No Pyodide sqlite bootstrap entry was patched") PY python3 - <<'PY' import hashlib diff --git a/content-hash.txt b/content-hash.txt index 6eeffa0..542d8e5 100644 --- a/content-hash.txt +++ b/content-hash.txt @@ -1 +1 @@ -757a3beb0900ec3b676d915aaed054bbca25bcd7d7cb6146435ed617adf3d765 +ef4b18000bb5a070fd3ae51e556e4844a7dbaab494e555c5dfaca2b780ade7d5 diff --git a/tests/test_notebooks_pyodide.py b/tests/test_notebooks_pyodide.py index 2c4a1c0..5025201 100644 --- a/tests/test_notebooks_pyodide.py +++ b/tests/test_notebooks_pyodide.py @@ -1,8 +1,12 @@ import json import os +import shutil +import subprocess +import textwrap from pathlib import Path import nbformat +import pytest from nbclient import NotebookClient @@ -71,6 +75,41 @@ def test_pyodide_kernel_uses_module_workers(): assert "{type:void 0}" not in bundle_source +def test_pyodide_kernel_does_not_piplite_install_sqlite3(): + extension_dir = DIST / "extensions" / "@jupyterlite" / "pyodide-kernel-extension" / "static" + js_files = list(extension_dir.glob("*.js")) + assert js_files + bundle_source = "\n".join(path.read_text(encoding="utf8") for path in js_files) + assert '"sqlite3","ipykernel"' not in bundle_source + assert '["ipykernel","comm","pyodide_kernel","jedi","ipython"]' in bundle_source + + +def test_pyodide_runtime_imports_sqlite3(): + if shutil.which("node") is None: + pytest.skip("node is required to smoke-test the local Pyodide runtime") + + script = textwrap.dedent( + """ + import { loadPyodide } from './dist/static/pyodide/pyodide.mjs'; + const pyodide = await loadPyodide({ indexURL: './dist/static/pyodide/' }); + await pyodide.runPythonAsync(` + import sqlite3, _sqlite3 + assert sqlite3.sqlite_version + assert _sqlite3.sqlite_version + `); + """ + ) + result = subprocess.run( + ["node", "--input-type=module", "-e", script], + cwd=ROOT, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr + + def _execute_notebook(notebook_path: Path, *, cells: int | None = None, timeout: int = 600) -> None: """Execute a notebook using the local CPython kernel.