Add options to disable integrity check in pyodide.loadPackage (#3149)

This PR adds an option to disable integrity check in `pyodide.loadPackage` when it is loading packages registered in repodata.json.
This commit is contained in:
Gyeongjae Choi 2022-10-05 08:32:11 +09:00 committed by GitHub
parent d2fce73291
commit 825a01e3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 28 deletions

View File

@ -74,6 +74,18 @@ substitutions:
the method is called on.
{pr}`3130`
- {{ Breaking }} The messageCallback and errorCallback argument to
{any}`loadPackage <pyodide.loadPackage>` and
{any}`loadPackagesFromImports <pyodide.loadPackagesFromImports>`
is now passed as named arguments.
The old usage still works with a deprecation warning.
{pr}`3149`
- {{ Enhancement }} {any}`loadPackage <pyodide.loadPackage>` and
{any}`loadPackagesFromImports <pyodide.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`

View File

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

View File

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

View File

@ -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<Uint8Array> {
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 <failed package name, error message>, 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<string, PackageLoadMetadata>,
loaded: Set<string>,
failed: Map<string, Error>,
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();
* ``<package-name>.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<string>,
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,
);
}

View File

@ -49,29 +49,29 @@ async function main() {
expectType<Promise<void>>(pyodide.loadPackagesFromImports("import some_pkg"));
expectType<Promise<void>>(
pyodide.loadPackagesFromImports("import some_pkg", (x: any) =>
console.log(x),
),
pyodide.loadPackagesFromImports("import some_pkg", {
messageCallback: (x: any) => console.log(x),
}),
);
expectType<Promise<void>>(
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<Promise<void>>(pyodide.loadPackage("blah"));
expectType<Promise<void>>(pyodide.loadPackage(["blah", "blah2"]));
expectType<Promise<void>>(
pyodide.loadPackage("blah", (x: any) => console.log(x)),
pyodide.loadPackage("blah", {
messageCallback: (x: any) => console.log(x),
}),
);
expectType<Promise<void>>(
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<Promise<void>>(pyodide.loadPackage(px));

View File

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