diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 22d5ba5b9..effae95b3 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -67,6 +67,10 @@ substitutions: {pr}`3015` included in 0.21.2) and if there is a variable in top level scope called `__dirname` we use that for the `indexURL`. +- {{ Fix }} `loadPyodide` will now raise error when the version of + JavaScript and Python Pyodide package does not match. + {pr}`3074` + ### Build System / Package Loading - New packages: pycryptodomex {pr}`2966`, pycryptodome {pr}`2965` diff --git a/src/core/pre.js b/src/core/pre.js index 9a8b6e21a..9139b15c8 100644 --- a/src/core/pre.js +++ b/src/core/pre.js @@ -2,4 +2,5 @@ const API = Module.API; const Hiwire = {}; const Tests = {}; API.tests = Tests; +API.version = "0.22.0.dev0"; Module.hiwire = Hiwire; diff --git a/src/js/api.ts b/src/js/api.ts index 6137544a9..3a529d84e 100644 --- a/src/js/api.ts +++ b/src/js/api.ts @@ -7,6 +7,7 @@ import { loadPackage, loadedPackages } from "./load-package"; import { isPyProxy, PyBuffer, PyProxy, TypedArray } from "./pyproxy.gen"; import { PythonError } from "./error_handling.gen"; import { loadBinaryFile } from "./compat"; +import version from "./version"; export { loadPackage, loadedPackages, isPyProxy }; import "./error_handling.gen.js"; @@ -29,16 +30,6 @@ export let pyodide_py: PyProxy; // actually defined in loadPyodide (see pyodide. */ export let globals: PyProxy; // actually defined in loadPyodide (see pyodide.js) -/** - * - * The Pyodide version. - * - * It can be either the exact release version (e.g. ``0.1.0``), or - * the latest release version followed by the number of commits since, and - * the git hash of the current commit (e.g. ``0.1.0-1-bd84646``). - */ -export let version: string = ""; // actually defined in loadPyodide (see pyodide.js) - /** * Just like `runPython` except uses a different globals dict and gets * `eval_code` from `_pyodide` so that it can work before `pyodide` is imported. diff --git a/src/js/pyodide.ts b/src/js/pyodide.ts index 91eef6705..6bb291a94 100644 --- a/src/js/pyodide.ts +++ b/src/js/pyodide.ts @@ -11,6 +11,7 @@ import { } from "./compat"; import { createModule, setStandardStreams, setHomeDirectory } from "./module"; +import version from "./version"; import type { PyodideInterface } from "./api.js"; import type { PyProxy, PyProxyDict } from "./pyproxy.gen"; @@ -34,6 +35,8 @@ export type { export type Py2JsResult = any; +export { version }; + /** * A proxy around globals that falls back to checking for a builtin if has or * get fails to find a global with the given key. Note that this proxy is @@ -144,11 +147,9 @@ function finalizeBootstrap(API: any, config: ConfigType) { API.pyodide_code = import_module("pyodide.code"); API.pyodide_ffi = import_module("pyodide.ffi"); API.package_loader = import_module("pyodide._package_loader"); - API.version = API.pyodide_py.__version__; // copy some last constants onto public API. pyodide.pyodide_py = API.pyodide_py; - pyodide.version = API.version; pyodide.globals = API.globals; return pyodide; } @@ -327,6 +328,15 @@ export async function loadPyodide( throw Module.exited.toThrow; } + if (API.version !== version) { + throw new Error( + `\ +Pyodide version does not match: '${version}' <==> '${API.version}'. \ +If you updated the Pyodide version, make sure you also updated the 'indexURL' parameter passed to loadPyodide.\ +` + ); + } + // Disable further loading of Emscripten file_packager stuff. Module.locateFile = (path: string) => { throw new Error("Didn't expect to load any more file_packager files!"); @@ -337,6 +347,7 @@ export async function loadPyodide( Module._pyodide_init(); const pyodide = finalizeBootstrap(API, config); + // API.runPython works starting here. if (!pyodide.version.includes("dev")) { // Currently only used in Node to download packages the first time they are @@ -344,7 +355,7 @@ export async function loadPyodide( API.setCdnUrl(`https://cdn.jsdelivr.net/pyodide/v${pyodide.version}/full/`); } await API.packageIndexReady; - if (API.repodata_info.version !== pyodide.version) { + if (API.repodata_info.version !== version) { throw new Error("Lock file version doesn't match Pyodide version"); } API.package_loader.init_loaded_packages(); diff --git a/src/js/pyodide.umd.ts b/src/js/pyodide.umd.ts index a7bfb8040..eb4963116 100644 --- a/src/js/pyodide.umd.ts +++ b/src/js/pyodide.umd.ts @@ -1,3 +1,3 @@ -import { loadPyodide } from "./pyodide"; -export { loadPyodide }; +import { loadPyodide, version } from "./pyodide"; +export { loadPyodide, version }; (globalThis as any).loadPyodide = loadPyodide; diff --git a/src/js/version.ts b/src/js/version.ts new file mode 100644 index 000000000..fb540bcbf --- /dev/null +++ b/src/js/version.ts @@ -0,0 +1,10 @@ +/** + * + * The Pyodide version. + * + * The version here follows PEP440 which is different from the one in package.json, + * as we want to compare this with the version of Pyodide Python package without conversion. + */ +const version: string = "0.22.0.dev0"; + +export default version; diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py index 029691218..c8b1178ea 100644 --- a/src/tests/test_pyodide.py +++ b/src/tests/test_pyodide.py @@ -1126,6 +1126,24 @@ def test_home_directory(selenium_standalone_noload): ) +def test_version_variable(selenium): + js_version = selenium.run_js( + """ + return pyodide.version + """ + ) + + core_version = selenium.run_js( + """ + return pyodide._api.version + """ + ) + + from pyodide import __version__ as py_version + + assert js_version == py_version == core_version + + def test_sys_path0(selenium): selenium.run_js( """ diff --git a/tools/bump_version.py b/tools/bump_version.py index fbab4253c..f51b5771e 100755 --- a/tools/bump_version.py +++ b/tools/bump_version.py @@ -64,6 +64,16 @@ PYTHON_TARGETS = [ build_version_pattern(r"version\s*=\s*{{{python_version}}}"), prerelease=False, ), + Target( + ROOT / "src/js/version.ts", + build_version_pattern('version: string = "{python_version}"'), + prerelease=True, + ), + Target( + ROOT / "src/core/pre.js", + build_version_pattern('API.version = "{python_version}"'), + prerelease=True, + ), ] JS_TARGETS = [