Source code for jupyterlite_pyodide_lock.addons._base

"""JupyterLite addon base for ``pyodide-lock.json``."""
# Copyright (c) jupyterlite-pyodide-lock contributors.
# Distributed under the terms of the BSD-3-Clause License.

from __future__ import annotations

from hashlib import sha256
from logging import Logger
from pathlib import Path
from typing import TYPE_CHECKING, Any

from jupyterlite_pyodide_kernel.addons._base import _BaseAddon  # noqa: PLC2701
from jupyterlite_pyodide_kernel.constants import PYODIDE_JS, PYODIDE_LOCK
from jupyterlite_pyodide_kernel.constants import PYODIDE_URL as OPTION_PYODIDE_URL
from traitlets import Bool

from jupyterlite_pyodide_lock.constants import (
    LOAD_PYODIDE_OPTIONS,
    OPTION_LOCK_FILE_URL,
    OPTION_PACKAGES,
    PYODIDE_ADDON,
    PYODIDE_LOCK_ADDON,
    PYODIDE_LOCK_OFFLINE_ADDON,
    PYODIDE_LOCK_STEM,
)

if TYPE_CHECKING:
    from collections.abc import Generator
    from logging import Logger

    from jupyterlite_pyodide_kernel.addons.pyodide import PyodideAddon

    TTaskGenerator = Generator[dict[str, Any], None, None]


[docs] class BaseAddon(_BaseAddon): # type: ignore[misc] """A base for ``jupyterlite-pyodide-lock`` addons.""" log: Logger # traits enabled: bool = Bool( default_value=False, help="whether ``pyodide-lock`` integration is enabled", ).tag(config=True) # type: ignore[assignment] # properties @property def addons(self) -> dict[str, _BaseAddon]: addons: dict[str, _BaseAddon] = self.manager._addons # noqa: SLF001 return addons @property def pyodide_addon(self) -> PyodideAddon: """The manager's pyodide addon, which will be reconfigured if needed.""" return self.addons[PYODIDE_ADDON] @property def pyodide_lock_addon(self) -> BaseAddon: """The manager's pyodide-lock addon.""" addon: BaseAddon = self.addons[PYODIDE_LOCK_ADDON] return addon @property def pyodide_lock_offline_addon(self) -> BaseAddon: """The manager's pyodide-lock offline addon.""" addon: BaseAddon = self.addons[PYODIDE_LOCK_OFFLINE_ADDON] return addon @property def output_dir(self) -> Path: """Provide ``jupyterlite_core.addons.base.LiteBuildConfig.output_dir``.""" return Path(self.manager.output_dir) @property def lite_dir(self) -> Path: """Provide ``jupyterlite_core.addons.base.LiteBuildConfig.lite_dir``.""" return Path(self.manager.lite_dir) @property def cache_dir(self) -> Path: """Provide ``jupyterlite_core.addons.base.LiteBuildConfig.cache_dir``.""" return Path(self.manager.cache_dir) @property def lock_output_dir(self) -> Path: """The folder where the ``pyodide-lock.json`` and packages will be stored.""" return self.output_dir / "static" / f"{PYODIDE_LOCK_STEM}" @property def lockfile(self) -> Path: """The ``pyodide-lock.json`` file in the ``{output_dir}``.""" return self.lock_output_dir / f"{PYODIDE_LOCK}" @property def package_cache(self) -> Path: """The root of the ``pyodide-lock`` cache.""" return self.cache_dir / f"{PYODIDE_LOCK_STEM}"
[docs] def patch_config(self, jupyterlite_json: Path, lockfile: Path) -> None: """Update the runtime ``jupyter-lite-config.json``.""" self.log.debug("[lock] patching %s for pyodide-lock", jupyterlite_json) settings = self.get_pyodide_settings(jupyterlite_json) output_js = self.pyodide_addon.output_pyodide / PYODIDE_JS url = f"./{output_js.relative_to(self.output_dir).as_posix()}" settings[OPTION_PYODIDE_URL] = url rel = lockfile.relative_to(self.output_dir).as_posix() lock_hash = sha256(lockfile.read_bytes()).hexdigest() load_pyodide_options = settings.setdefault(LOAD_PYODIDE_OPTIONS, {}) lock_addon = self.pyodide_lock_addon if TYPE_CHECKING: from jupyterlite_pyodide_lock.addons.lock import PyodideLockAddon assert isinstance(lock_addon, PyodideLockAddon) preload = [ *load_pyodide_options.get(OPTION_PACKAGES, []), *lock_addon.preload_packages, *lock_addon.extra_preload_packages, ] load_pyodide_options.update( { OPTION_LOCK_FILE_URL: f"./{rel}?sha256={lock_hash}", OPTION_PACKAGES: sorted(set(preload)), }, ) self.set_pyodide_settings(jupyterlite_json, settings) self.log.info("[lock] patched %s for %s", jupyterlite_json, lockfile.name)