Skip to content

Update to pyodide 0.28.1, micropip 0.10 #202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: 0.6.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

| status | `jupyterlite-pyodide-kernel` | `jupyterlite-core` | `jupyterlab` | `notebook` | `retrolab` |
| :----: | :--------------------------: | :----------------: | :------------: | :------------: | :----------: |
| alpha | `0.7.*` | `>=0.6,<0.7` | `>=4.4.5,<4.5` | `>=7.4.5,<7.5` | - |
| stable | `0.6.*` | `>=0.6,<0.7` | `>=4.4.3,<4.5` | `>=7.4.3,<7.5` | - |
| stable | `0.5.*` | `>=0.5,<0.6` | `>=4.3.0,<4.4` | `>=7.3.0,<7.4` | - |
| stable | `0.4.*` | `>=0.4,<0.5` | `>=4.2.0,<4.3` | `>=7.2.0,<7.3` | - |
Expand All @@ -46,6 +47,7 @@ yet work in a full, `jupyter_server`-hosted client such as JupyterLab or Noteboo
| `>=0.4.7,<=0.5.0` | `0.27.*` | `3.12.*` | `3.1.58` |
| `>=0.5.0,<=0.6.0` | `0.27.*` | `3.12.*` | `3.1.58` |
| `>=0.6.0,<=0.7.0` | `0.27.*` | `3.12.*` | `3.1.58` |
| `>=0.7.0,<=0.8.0` | `0.28.*` | `3.13.*` | `4.0.9` |

Note that the Emscripten version is strict down to the bugfix version.

Expand Down
2 changes: 1 addition & 1 deletion examples/jupyter-lite.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@jupyterlite/pyodide-kernel-extension:kernel": {
"loadPyodideOptions": {
"packages": ["matplotlib", "micropip", "numpy", "sqlite3", "ssl"],
"lockFileURL": "https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide-lock.json?from-lite-config=1"
"lockFileURL": "https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide-lock.json?from-lite-config=1"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion jupyterlite_pyodide_kernel/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
PYODIDE_URL_ENV_VAR = "JUPYTERLITE_PYODIDE_URL"

#: probably only compatible with this version of pyodide
PYODIDE_VERSION = "0.27.6"
PYODIDE_VERSION = "0.28.1"

#: the only kind of noarch wheel piplite understands
NOARCH_WHL = "py3-none-any.whl"
Expand Down
16 changes: 8 additions & 8 deletions packages/pyodide-kernel-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyterlab/application": "^4.4.0",
"@jupyterlab/coreutils": "^6.4.0",
"@jupyterlab/logconsole": "^4.4.0",
"@jupyterlite/contents": "^0.6.0",
"@jupyterlite/kernel": "^0.6.0",
"@jupyterlab/application": "^4.4.4",
"@jupyterlab/coreutils": "^6.4.4",
"@jupyterlab/logconsole": "^4.4.4",
"@jupyterlite/contents": "^0.6.3",
"@jupyterlite/kernel": "^0.6.3",
"@jupyterlite/pyodide-kernel": "^0.6.1",
"@jupyterlite/server": "^0.6.0"
"@jupyterlite/server": "^0.6.3"
},
"devDependencies": {
"@jupyterlab/builder": "~4.4.0",
"rimraf": "^5.0.1",
"@jupyterlab/builder": "~4.4.4",
"rimraf": "^6.0.1",
"typescript": "~5.2.2"
},
"publishConfig": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"pyodideUrl": {
"description": "The path to the main pyodide.js entry point",
"type": "string",
"default": "https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js",
"default": "https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide.js",
"format": "uri"
},
"disablePyPIFallback": {
Expand Down
2 changes: 1 addition & 1 deletion packages/pyodide-kernel-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const KERNEL_ICON_URL = `data:image/svg+xml;base64,${btoa(KERNEL_ICON_SVG_STR)}`
/**
* The default CDN fallback for Pyodide
*/
const PYODIDE_CDN_URL = 'https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js';
const PYODIDE_CDN_URL = 'https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide.js';

/**
* The id for the extension, and key in the litePlugins.
Expand Down
12 changes: 6 additions & 6 deletions packages/pyodide-kernel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab/coreutils": "^6.4.0",
"@jupyterlab/logconsole": "^4.4.0",
"@jupyterlite/contents": "^0.6.0",
"@jupyterlite/kernel": "^0.6.0",
"@jupyterlab/coreutils": "^6.4.4",
"@jupyterlab/logconsole": "^4.4.4",
"@jupyterlite/contents": "^0.6.3",
"@jupyterlite/kernel": "^0.6.3",
"coincident": "^1.2.3",
"comlink": "^4.4.2"
},
"devDependencies": {
"@babel/core": "^7.22.17",
"esbuild": "^0.19.2",
"pyodide": "0.27.6",
"rimraf": "^5.0.1",
"pyodide": "0.28.1",
"rimraf": "^6.0.1",
"typescript": "~5.2.2"
},
"publishConfig": {
Expand Down
133 changes: 82 additions & 51 deletions packages/pyodide-kernel/py/piplite/piplite/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,46 @@

As of the upstream:

https://github.com/pyodide/micropip/blob/v0.2.0/micropip/_micropip.py#L468
https://github.com/pyodide/micropip/blob/0.10.0/micropip/package_manager.py#L43-L55

.. code:

async def install(
self,
requirements: str | list[str], # -r and [packages]
requirements: str | list[str], # -r and [PACKAGES]
keep_going: bool = False, # --verbose
deps: bool = True, # --no-deps
credentials: str | None = None, # no CLI alias
pre: bool = False, # --pre
index_urls: list[str] | str | None = None, # no CLI alias
*,
verbose: bool | int | None = None,
):
```
constraints: list[str] | None = None, # --constraints
reinstall: bool = False, # no CLI alias
verbose: bool | int | None = None, # --verbose
) -> None:

As this is _not_ really a CLI, it doesn't bother with accurate return codes, and
failures should not block execution.
"""

from __future__ import annotations

import re
import sys
import typing
from typing import Any, TYPE_CHECKING
from argparse import ArgumentParser
from pathlib import Path

REQ_FILE_PREFIX = r"^(-r|--requirements)\s*=?\s*(.*)\s*"
if TYPE_CHECKING:
from collections.abc import AsyncIterator

REQ_FILE_SPEC = r"^(?P<flag>-r|--requirements)\s*=?\s*(?P<path_ref>.+)$"

__all__ = ["get_transformed_code"]


def warn(msg):
def warn(msg: str) -> None:
"""Print a warning to stderr."""
print(msg, file=sys.stderr, flush=True)


Expand All @@ -44,7 +51,7 @@ def _get_parser() -> ArgumentParser:
"piplite",
exit_on_error=False,
allow_abbrev=False,
description="a pip-like wrapper for `piplite` and `micropip`",
description="a ``pip``-like wrapper for ``piplite`` and ``micropip``",
)
parser.add_argument(
"--verbose",
Expand All @@ -64,7 +71,19 @@ def _get_parser() -> ArgumentParser:
"--requirements",
"-r",
nargs="*",
help="paths to requirements files",
help=(
"path to a requirements file; each line should be a PEP 508 spec"
" or -r to a relative path"
),
)
parser.add_argument(
"--constraints",
"-c",
nargs="*",
help=(
"path to a constraints file; each line should be a PEP 508 spec"
" or -r to a relative path"
),
)
parser.add_argument(
"--no-deps",
Expand All @@ -76,6 +95,11 @@ def _get_parser() -> ArgumentParser:
action="store_true",
help="whether pre-release packages should be considered",
)
parser.add_argument(
"--force-reinstall",
action="store_true",
help="reinstall all packages even if they are already installed",
)
parser.add_argument(
"packages",
nargs="*",
Expand All @@ -87,21 +111,25 @@ def _get_parser() -> ArgumentParser:
return parser


async def get_transformed_code(argv: list[str]) -> typing.Optional[str]:
async def get_transformed_code(argv: list[str]) -> str | None:
"""Return a string of code for use in in-kernel execution."""
action, kwargs = await get_action_kwargs(argv)
code_str: str = "\n"

if action == "help":
pass

if action == "install":
if kwargs["requirements"]:
return f"""await __import__("piplite").install(**{kwargs})\n"""
code_str = f"""await __import__("piplite").install(**{kwargs})\n"""
else:
warn("piplite needs at least one package to install")

return code_str

async def get_action_kwargs(argv: list[str]) -> tuple[typing.Optional[str], dict]:
"""Get the arguments to `piplite` subcommands from CLI-like tokens."""

async def get_action_kwargs(argv: list[str]) -> tuple[str | None, dict[str, Any]]:
"""Get the arguments to ``piplite`` subcommands from CLI-like tokens."""

parser = _get_parser()

Expand All @@ -126,51 +154,54 @@ async def get_action_kwargs(argv: list[str]) -> tuple[typing.Optional[str], dict
if args.verbose:
kwargs["keep_going"] = True

if args.reinstall:
kwargs["reinstall"] = True

for req_file in args.requirements or []:
kwargs["requirements"] += await _packages_from_requirements_file(
Path(req_file)
)
async for spec in _specs_from_requirements_file(Path(req_file)):
kwargs["requirements"] += [spec]

return action, kwargs
for const_file in args.constraints or []:
async for spec in _specs_from_requirements_file(Path(const_file)):
kwargs["constraints"] += [spec]

return action, kwargs

async def _packages_from_requirements_file(req_path: Path) -> list[str]:
"""Extract (potentially nested) package requirements from a requirements file."""
if not req_path.exists():
warn(f"piplite could not find requirements file {req_path}")
return []

requirements = []
async def _specs_from_requirements_file(spec_path: Path) -> AsyncIterator[str]:
"""Extract package specs from a ``requirements.txt``-style file."""
if not spec_path.exists():
warn(f"piplite could not find requirements file {spec_path}")
return

for line_no, line in enumerate(req_path.read_text(encoding="utf").splitlines()):
requirements += await _packages_from_requirements_line(
req_path, line_no + 1, line
)
for line_no, line in enumerate(spec_path.read_text(encoding="utf").splitlines()):
async for spec in _specs_from_requirements_line(spec_path, line_no + 1, line):
yield spec

return requirements

async def _specs_from_requirements_line(
spec_path: Path, line_no: int, line: str
) -> AsyncIterator[str]:
"""Get package specs from a line of a ``requirements.txt``-style file.

async def _packages_from_requirements_line(
req_path: Path, line_no: int, line: str
) -> list[str]:
"""Extract (potentially nested) package requirements from line of a
requirements file.
``micropip`` has a sufficient pep508 implementation to handle most cases.

`micropip` has a sufficient pep508 implementation to handle most cases
References to other, local files with ``-r`` are supported.
"""
req = line.strip().split("#")[0].strip()
# is it another requirement file?
req_file_match = re.match(REQ_FILE_PREFIX, req)
if req_file_match:
if req_file_match[2].startswith("/"):
sub_req = Path(req)
else:
sub_req = req_path.parent / req_file_match[2]
return await _packages_from_requirements_file(sub_req)

if req.startswith("-"):
warn(f"{req_path}:{line_no}: unrecognized requirement: {req}")
req = None
if not req:
return []
return [req]
raw = line.strip().split("#")[0].strip()
# is it another spec file?
file_match = re.match(REQ_FILE_SPEC, raw)

if file_match:
ref = file_match.groupdict()["path_ref"]
ref_path = Path(ref if ref.startswith("/") else spec_path.parent / ref)
async for sub_spec in _specs_from_requirements_file(ref_path):
yield sub_spec
elif raw.startswith("-"):
warn(f"{spec_path}:{line_no}: unrecognized spec: {raw}")
return
else:
spec = raw

if spec:
yield spec
Loading
Loading