diff --git a/docs/project/changelog.md b/docs/project/changelog.md index cc529abbc..94d3c3272 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -54,18 +54,18 @@ substitutions: `pyodide.globals.get('key')`. [#1367](https://github.com/pyodide/pyodide/pull/1367) - {{ API }} Added {any}`PyProxy.getBuffer` API to allow direct access to Python buffers as Javascript TypedArrays. - [1215](https://github.com/pyodide/pyodide/pull/1215) + [#1215](https://github.com/pyodide/pyodide/pull/1215) - {{ API }} The innermost level of a buffer converted to Javascript used to be a TypedArray if the buffer was contiguous and otherwise an Array. Now the innermost level will be a TypedArray unless the buffer format code is a '?' in which case it will be an Array of booleans, or if the format code is a "s" in which case the innermost level will be converted to a string. - [1376](https://github.com/pyodide/pyodide/pull/1376) + [#1376](https://github.com/pyodide/pyodide/pull/1376) - {{ Enhancement }} Javascript `BigInt`s are converted into Python `int` and Python `int`s larger than 2^53 are converted into `BigInt`. - [1407](https://github.com/pyodide/pyodide/pull/1407) + [#1407](https://github.com/pyodide/pyodide/pull/1407) - {{ API }} Added {any}`pyodide.isPyProxy` to test if an object is a `PyProxy`. - [1456](https://github.com/pyodide/pyodide/pull/1456) + [#1456](https://github.com/pyodide/pyodide/pull/1456) ### Fixed - {{ Fix }} getattr and dir on JsProxy now report consistent results and include all @@ -136,13 +136,16 @@ substitutions: - Changed the loading method: added an async function `loadPyodide` to load Pyodide to use instead of `languagePluginURL` and `languagePluginLoader`. The change is currently backwards compatible, but the old approach is deprecated. - [1363](https://github.com/pyodide/pyodide/pull/1363) + [#1363](https://github.com/pyodide/pyodide/pull/1363) ### micropip - {{ Feature }} `micropip` now supports installing wheels from relative URLs. [#872](https://github.com/pyodide/pyodide/pull/872) - {{ API }} `micropip.install` now returns a Python `Future` instead of a Javascript `Promise`. + [#1324](https://github.com/pyodide/pyodide/pull/1324/) +- {{ FIX }} {any}`micropip.install` now interacts correctly with {any}`pyodide.loadPackage`. + [#1457](https://github.com/pyodide/pyodide/pull/1457/) ### Build system diff --git a/packages/micropip/micropip/micropip.py b/packages/micropip/micropip/micropip.py index 919514e36..5ac3d2235 100644 --- a/packages/micropip/micropip/micropip.py +++ b/packages/micropip/micropip/micropip.py @@ -57,6 +57,14 @@ else: return result +if IN_BROWSER: + from pyodide_js import loadedPackages +else: + + class loadedPackages: # type: ignore + pass + + async def _get_pypi_json(pkgname): url = f"https://pypi.org/pypi/{pkgname}/json" fd = await _get_url(url) @@ -112,6 +120,7 @@ async def _install_wheel(name, fileinfo): wheel = await _get_url(url) _validate_wheel(wheel, fileinfo) _extract_wheel(wheel) + setattr(loadedPackages, name, url) class _PackageManager: @@ -155,14 +164,15 @@ class _PackageManager: # Note: branch never happens in out-of-browser testing because we # report that all dependencies are empty. self.installed_packages.update(dict((k, None) for k in pyodide_packages)) - wheel_promises.append(pyodide_js.loadPackage(list(pyodide_packages))) + wheel_promises.append( + asyncio.ensure_future(pyodide_js.loadPackage(list(pyodide_packages))) + ) # Now install PyPI packages for name, wheel, ver in transaction["wheels"]: wheel_promises.append(_install_wheel(name, wheel)) self.installed_packages[name] = ver await gather(*wheel_promises) - return f'Installed {", ".join(self.installed_packages.keys())}' async def add_requirement(self, requirement: str, ctx, transaction): if requirement.endswith(".whl"): @@ -245,8 +255,8 @@ def install(requirements: Union[str, List[str]]): ---------- requirements : ``str | List[str]`` - A requirement or list of requirements to install. Each requirement is a string, which should be either - a package name or URL to a wheel: + A requirement or list of requirements to install. Each requirement is a + string, which should be either a package name or URL to a wheel: - If the requirement ends in ``.whl`` it will be interpreted as a URL. The file must be a wheel named in compliance with the @@ -260,8 +270,8 @@ def install(requirements: Union[str, List[str]]): ------- ``Future`` - A ``Future`` that resolves to ``None`` when all packages have - been downloaded and installed. + A ``Future`` that resolves to ``None`` when all packages have been + downloaded and installed. """ importlib.invalidate_caches() return asyncio.ensure_future(PACKAGE_MANAGER.install(requirements)) diff --git a/packages/micropip/test_micropip.py b/packages/micropip/test_micropip.py index c774ea87a..1e0c8215a 100644 --- a/packages/micropip/test_micropip.py +++ b/packages/micropip/test_micropip.py @@ -170,3 +170,25 @@ def test_last_version_from_pypi(): wheel, ver = micropip.PACKAGE_MANAGER.find_wheel(metadata, requirement) assert ver == "0.15.5" + + +def test_install_different_version(selenium_standalone_micropip): + selenium = selenium_standalone_micropip + selenium.run_js( + """ + await pyodide.runPythonAsync(` + import micropip + await micropip.install( + "https://files.pythonhosted.org/packages/89/06/2c2d3034b4d6bf22f2a4ae546d16925898658a33b4400cfb7e2c1e2871a3/pytz-2020.5-py2.py3-none-any.whl" + ); + `); + """ + ) + selenium.run_js( + """ + pyodide.runPython(` + import pytz + assert pytz.__version__ == "2020.5" + `); + """ + )