diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 21d1b0441..f4b23c6aa 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -74,6 +74,18 @@ substitutions: the method is called on. {pr}`3130` +- {{ Breaking }} The messageCallback and errorCallback argument to + {any}`loadPackage ` and + {any}`loadPackagesFromImports ` + is now passed as named arguments. + The old usage still works with a deprecation warning. + {pr}`3149` + +- {{ Enhancement }} {any}`loadPackage ` and + {any}`loadPackagesFromImports ` now accepts + a new option `checkIntegrity`. If set to False, integrity check for Python Packages + will be disabled. + - {{ Fix }} Shared libraries with version suffix are now handled correctly. {pr}`3154` diff --git a/docs/project/deprecation-timeline.md b/docs/project/deprecation-timeline.md index 16676b6a5..ccd1b69f3 100644 --- a/docs/project/deprecation-timeline.md +++ b/docs/project/deprecation-timeline.md @@ -10,6 +10,11 @@ state, this list is subject to change and some features can be removed without deprecation warnings. More details about each item can often be found in the {ref}`changelog`. +## 0.24.0 + +- The `messageCallback` and `errorCallback` argument to `loadPackage` and `loadPackagesFromImports` will be passed as a + named argument only. + ## 0.23.0 Names that used to be in the root `pyodide` module and were moved to submodules diff --git a/src/js/api.ts b/src/js/api.ts index 74b4b3fc4..d723deeb4 100644 --- a/src/js/api.ts +++ b/src/js/api.ts @@ -71,6 +71,7 @@ export function runPython( } API.runPython = runPython; +let loadPackagesFromImportsPositionalCallbackDeprecationWarned = false; /** * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to install * any known packages that the code chunk imports. Uses the Python API @@ -86,17 +87,39 @@ API.runPython = runPython; * ``pyodide.loadPackage(['numpy'])``. * * @param code The code to inspect. - * @param messageCallback The ``messageCallback`` argument of - * :any:`pyodide.loadPackage` (optional). - * @param errorCallback The ``errorCallback`` argument of - * :any:`pyodide.loadPackage` (optional). + * @param options Options passed to :any:`pyodide.loadPackage`. + * @param options.messageCallback A callback, called with progress messages + * (optional) + * @param options.errorCallback A callback, called with error/warning messages + * (optional) + * @param options.checkIntegrity If true, check the integrity of the downloaded + * packages (default: true) * @async */ export async function loadPackagesFromImports( code: string, - messageCallback?: (msg: string) => void, - errorCallback?: (err: string) => void, + options: { + messageCallback?: (message: string) => void; + errorCallback?: (message: string) => void; + checkIntegrity?: boolean; + } = { + checkIntegrity: true, + }, + errorCallbackDeprecated?: (message: string) => void, ) { + if (typeof options === "function") { + if (!loadPackagesFromImportsPositionalCallbackDeprecationWarned) { + console.warn( + "Passing a messageCallback or errorCallback as the second or third argument to loadPackagesFromImports is deprecated and will be removed in v0.24. Instead use { messageCallback : callbackFunc }", + ); + options = { + messageCallback: options, + errorCallback: errorCallbackDeprecated, + }; + loadPackagesFromImportsPositionalCallbackDeprecationWarned = true; + } + } + let pyimports = API.pyodide_code.find_imports(code); let imports; try { @@ -116,7 +139,7 @@ export async function loadPackagesFromImports( } } if (packages.size) { - await loadPackage(Array.from(packages), messageCallback, errorCallback); + await loadPackage(Array.from(packages), options); } } diff --git a/src/js/load-package.ts b/src/js/load-package.ts index 372409540..fc4746016 100644 --- a/src/js/load-package.ts +++ b/src/js/load-package.ts @@ -191,12 +191,15 @@ function recursiveDependencies( * @param name The name of the package * @param channel Either `DEFAULT_CHANNEL` or the absolute URL to the * wheel or the path to the wheel relative to indexURL. + * @param checkIntegrity Whether to check the integrity of the downloaded + * package. * @returns The binary data for the package * @private */ async function downloadPackage( name: string, channel: string, + checkIntegrity: boolean = true, ): Promise { let file_name, uri, file_sub_resource_hash; if (channel === DEFAULT_CHANNEL) { @@ -212,6 +215,10 @@ async function downloadPackage( uri = channel; file_sub_resource_hash = undefined; } + + if (!checkIntegrity) { + file_sub_resource_hash = undefined; + } try { return await loadBinaryFile(uri, file_sub_resource_hash); } catch (e) { @@ -274,6 +281,8 @@ async function installPackage( * @param toLoad The map of package names to PackageLoadMetadata * @param loaded The set of loaded package names, this will be updated by this function. * @param failed The map of , this will be updated by this function. + * @param checkIntegrity Whether to check the integrity of the downloaded + * package. * @private */ async function downloadAndInstall( @@ -281,6 +290,7 @@ async function downloadAndInstall( toLoad: Map, loaded: Set, failed: Map, + checkIntegrity: boolean = true, ) { if (loadedPackages[name] !== undefined) { return; @@ -289,7 +299,7 @@ async function downloadAndInstall( const pkg = toLoad.get(name)!; try { - const buffer = await downloadPackage(pkg.name, pkg.channel); + const buffer = await downloadPackage(pkg.name, pkg.channel, checkIntegrity); const installPromisDependencies = pkg.depends.map((dependency) => { return toLoad.has(dependency) ? toLoad.get(dependency)!.done @@ -422,6 +432,7 @@ API.loadDynlib = loadDynlib; const acquirePackageLock = createLock(); +let loadPackagePositionalCallbackDeprecationWarned = false; /** * Load a package or a list of packages over the network. This installs the * package in the virtual filesystem. The package needs to be imported from @@ -433,19 +444,41 @@ const acquirePackageLock = createLock(); * ``.data`` in the same directory. The argument can be a * ``PyProxy`` of a list, in which case the list will be converted to JavaScript * and the ``PyProxy`` will be destroyed. - * @param messageCallback A callback, called with progress messages + * @param options + * @param options.messageCallback A callback, called with progress messages * (optional) - * @param errorCallback A callback, called with error/warning messages + * @param options.errorCallback A callback, called with error/warning messages * (optional) + * @param options.checkIntegrity If true, check the integrity of the downloaded + * packages (default: true) * @async */ export async function loadPackage( names: string | PyProxy | Array, - messageCallback?: (msg: string) => void, - errorCallback?: (msg: string) => void, + options: { + messageCallback?: (message: string) => void; + errorCallback?: (message: string) => void; + checkIntegrity?: boolean; + } = { + checkIntegrity: true, + }, + errorCallbackDeprecated?: (message: string) => void, ) { - messageCallback = messageCallback || console.log; - errorCallback = errorCallback || console.error; + if (typeof options === "function") { + if (!loadPackagePositionalCallbackDeprecationWarned) { + console.warn( + "Passing a messageCallback or errorCallback as the second or third argument to loadPackage is deprecated and will be removed in v0.24. Instead use { messageCallback : callbackFunc }", + ); + options = { + messageCallback: options, + errorCallback: errorCallbackDeprecated, + }; + loadPackagePositionalCallbackDeprecationWarned = true; + } + } + + const messageCallback = options.messageCallback || console.log; + const errorCallback = options.errorCallback || console.error; if (isPyProxy(names)) { names = names.toJs(); } @@ -503,6 +536,7 @@ export async function loadPackage( toLoad, loaded, failed, + options.checkIntegrity, ); } diff --git a/src/test-js/index.test-d.ts b/src/test-js/index.test-d.ts index ff21e2905..978761e78 100644 --- a/src/test-js/index.test-d.ts +++ b/src/test-js/index.test-d.ts @@ -49,29 +49,29 @@ async function main() { expectType>(pyodide.loadPackagesFromImports("import some_pkg")); expectType>( - pyodide.loadPackagesFromImports("import some_pkg", (x: any) => - console.log(x), - ), + pyodide.loadPackagesFromImports("import some_pkg", { + messageCallback: (x: any) => console.log(x), + }), ); expectType>( - pyodide.loadPackagesFromImports( - "import some_pkg", - (x: any) => console.log(x), - (x: any) => console.warn(x), - ), + pyodide.loadPackagesFromImports("import some_pkg", { + messageCallback: (x: any) => console.log(x), + errorCallback: (x: any) => console.warn(x), + }), ); expectType>(pyodide.loadPackage("blah")); expectType>(pyodide.loadPackage(["blah", "blah2"])); expectType>( - pyodide.loadPackage("blah", (x: any) => console.log(x)), + pyodide.loadPackage("blah", { + messageCallback: (x: any) => console.log(x), + }), ); expectType>( - pyodide.loadPackage( - ["blah", "blah2"], - (x: any) => console.log(x), - (x: any) => console.warn(x), - ), + pyodide.loadPackage(["blah", "blah2"], { + messageCallback: (x: any) => console.log(x), + errorCallback: (x: any) => console.warn(x), + }), ); expectType>(pyodide.loadPackage(px)); diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py index 02bd9118a..a5c090138 100644 --- a/src/tests/test_pyodide.py +++ b/src/tests/test_pyodide.py @@ -1248,6 +1248,18 @@ def test_raises_jsexception(selenium): raise_jsexception(selenium) +def test_deprecations(selenium_standalone): + selenium = selenium_standalone + selenium.run_js( + """ + pyodide.loadPackage("micropip", (x) => x); + pyodide.loadPackagesFromImports("import micropip", (x) => x); + """ + ) + dep_msg = "Passing a messageCallback or errorCallback as the second or third argument to loadPackage is deprecated and will be removed in v0.24. Instead use { messageCallback : callbackFunc }" + assert selenium.logs.count(dep_msg) == 1 + + @run_in_pyodide(packages=["pytest"]) def test_moved_deprecation_warnings(selenium_standalone): import pytest