🕹️ Try the demo
jupyterlite-pyodide-lock#
Create pre-solved environments for jupyterlite-pyodide-kernel with pyodide-lock.
Installing#
This package is not yet released. See
CONTRIBUTING.md
for development.pip install jupyterlite-pyodide-lockor mamba/conda:
mamba install -c conda-forge jupyterlite-pyodide-lock
Why Use This?#
in the browser
fetches
pyodide-kernel
, its dependencies, and configured packages in parallel whilepyodide
is starting, skppingmicropip.install
and its requests to the PyPI APIdoesn’t require
%pip install
for locked packages and their dependenciesnotebooks and scripts still need to be well-formed, e.g.
import my_package
once shipped, package versions loaded in the browser won’t change over time
during a build
doesn’t require rebuilding a full custom
pyodide
distributionbut will patch an custom deployed
pyodide
all downloaded wheels can be optionally shipped along with the application
optionally clamp PyPI packages to a known timestamp to ensure newer packages aren’t found during a future solve
supports multiple sources of custom wheels and dependencies
Feature Comparison#
A number of approaches are available for getting reproducible JupyterLite runtime
python environments, either with jupyterlite-pyodide-kernel
or other kernels.
Choosing one requires some trades of simplicity, reproducibility, flexibility,
and performance.
Note
Each tool is evolving, so the tables below should be verified against the different tools when making a decision.
A visitor to a JupyterLite site's needs may be the top priority...
feature |
|
|
||
---|---|---|---|---|
needs separate |
no (for locked packages) |
yes ( |
no |
no |
allows install by PyPI name |
yes |
yes |
no |
yes |
allows install from URL |
yes |
yes |
no |
yes |
blocks interaction per package |
run cell |
run cell |
start kernel |
run cell |
caches in the browser |
per package |
per package |
whole environment |
per package |
An author of a JupyterLite site may have additional needs...
feature |
|
|
||
---|---|---|---|---|
requires “heavy” build dependencies |
real browser (and/or |
no |
minimal, see repo |
many, see repo |
ships local wheels |
yes |
yes |
maybe? |
yes |
ships noarch PyPI wheels |
yes |
yes |
yes |
yes |
ships pyodide emscripten wheels |
yes |
yes |
no |
yes |
ships arbitrary pyodide zip C libs |
no |
yes |
no |
yes |
locks multiple versions of same package |
no |
yes |
no |
no |
optionally clamp to a timestamp |
yes |
no |
no |
no |
Usage#
Configure#
Requirements#
A number of ways to add requirements to the lock file are supported:
adding wheels in
{lite_dir}/static/pyodide-lock
configuring
specs
as a list of PEP508 dependency specsconfiguring
packages
as a list ofURLs to remote wheels that will be downloaded and cached
local paths relative to
lite_dir
of.whl
files (or folders of wheels)
# examples/jupyter_lite_config.json
{ "PyodideLockAddon": { "enabled": true, "specs": [
# pep508 spec
"ipywidgets >=8.1,<8.2",
], "packages": [
# a wheel
"../dist/ipywidgets-8.1.2-py3-none-any.whl",
# a folder of wheels
"../dist",
] } }
Lockers#
The Locker is responsible for starting a browser, executing micopip.install
and micropip.freeze
to try to get a viable lock file solution.
{ "PyodideLockAddon": {
"enabled": true,
# the default locker: uses naive a `subprocess.Popen` approach
"locker": "browser",
}, "BrowserLocker": {
# requires `firefox` or `firefox.exe` on PATH
"browser": "firefox",
"headless": true,
"private_mode": true,
"temp_profile": true,
} }
A convenience CLI options will show some information about detected browsers:
jupyter pyodide-lock browsers
Reproducible Locks#
By configuring the lock date to a UNIX epoch timestamp, artifacts from a PyPI index newer than that date will be filtered out before a lock is attempted.
Combined with a fixed pyodide_url
archive, this should prevent known packages
and their dependencies from “drifting.”
{
"PyodideAddon":
{
"pyodide_url": f"https://github.com/pyodide/pyodide/releases/download/0.25.0/pyodide-core-0.25.0.tar.bz2",
},
"PyodideLockAddon": { "enabled": true, "lock_date_epoch": 1712980201 },
}
Alternately, this can be provided by environemnt variable:
JLPL_LOCK_DATE_EPOCH=$(date -u +%s) jupyter lite build
Getting a lock_date_epoch
As shown in the example above, date
can provide this:
date -u +%s
Or python
:
>>> from datetime import datetime, timezone
>>> int(datetime.now(tz=timezone.utc).timestamp())
…or git
, for the last commit time of a file:
git log -1 --format=%ct requirements.txt
The latter approch, using version control metadata, is recommended, as it shifts the burden of bookkeeping to a verifiable source.