|
5 | 5 |
|
6 | 6 | https://github.com/jupyterlite/jupyterlite/issues/996
|
7 | 7 | """
|
8 |
| -import json |
9 | 8 | from pathlib import Path
|
10 |
| -from typing import Generator, Dict, Any |
| 9 | +import json |
| 10 | +from hashlib import sha256 |
| 11 | +from typing import Dict, Any, List, Optional |
11 | 12 | from jupyterlite_core.addons.base import BaseAddon
|
12 |
| -from jupyterlite_core.constants import ( |
13 |
| - JUPYTERLITE_IPYNB, |
14 |
| - JUPYTERLITE_JSON, |
15 |
| - UTF8, |
16 |
| - JUPYTERLITE_METADATA, |
17 |
| - JUPYTER_CONFIG_DATA, |
18 |
| - LITE_PLUGIN_SETTINGS, |
19 |
| - JSON_FMT, |
| 13 | +from jupyterlite_core.constants import LAB_EXTENSIONS, UTF8 |
| 14 | + |
| 15 | +from ..constants import ( |
| 16 | + PKG_JSON_PYODIDE_KERNEL, |
| 17 | + PKG_JSON_WHEELDIR, |
| 18 | + PYODIDE_KERNEL_PLUGIN_ID, |
| 19 | + PYODIDE_KERNEL_NPM_NAME, |
| 20 | + PYPI_WHEELS, |
20 | 21 | )
|
21 | 22 |
|
22 |
| -from ..constants import PYODIDE_KERNEL_PLUGIN_ID |
23 |
| - |
24 | 23 | __all__ = ["_BaseAddon"]
|
25 | 24 |
|
26 | 25 |
|
27 | 26 | class _BaseAddon(BaseAddon):
|
28 |
| - def get_pyodide_settings(self, config_path: Path): |
29 |
| - """Get the settings for the client-side Pyodide kernel.""" |
30 |
| - return self.get_lite_plugin_settings(config_path, PYODIDE_KERNEL_PLUGIN_ID) |
| 27 | + @property |
| 28 | + def output_extensions(self) -> Path: |
| 29 | + """where labextensions will go in the output folder |
31 | 30 |
|
32 |
| - def set_pyodide_settings(self, config_path: Path, settings: Dict[str, Any]) -> None: |
33 |
| - """Update the settings for the client-side Pyodide kernel.""" |
34 |
| - return self.set_lite_plugin_settings( |
35 |
| - config_path, PYODIDE_KERNEL_PLUGIN_ID, settings |
36 |
| - ) |
| 31 | + Candidate for hoisting to ``jupyterlite_core`` |
| 32 | + """ |
| 33 | + return self.manager.output_dir / LAB_EXTENSIONS |
37 | 34 |
|
38 |
| - def get_output_config_paths(self) -> Generator[Path, None, None]: |
39 |
| - """Yield an iterator of all config paths that _might_ exist in the |
40 |
| - ``output_dir``. |
| 35 | + def get_output_labextension_packages(self) -> List[Path]: |
| 36 | + """All ``package.json`` files for labextensions in ``output_dir``. |
41 | 37 |
|
42 |
| - This will likely move upstream. |
| 38 | + Candidate for hoisting to ``jupyterlite_core`` |
43 | 39 | """
|
44 |
| - for app in [None, *self.manager.apps]: |
45 |
| - app_dir = self.manager.output_dir / app if app else self.manager.output_dir |
46 |
| - for path_name in [JUPYTERLITE_JSON, JUPYTERLITE_IPYNB]: |
47 |
| - config_path = app_dir / path_name |
48 |
| - yield config_path |
| 40 | + return sorted( |
| 41 | + [ |
| 42 | + *self.output_extensions.glob("*/package.json"), |
| 43 | + *self.output_extensions.glob("@*/*/package.json"), |
| 44 | + ] |
| 45 | + ) |
49 | 46 |
|
50 |
| - def get_lite_plugin_settings( |
51 |
| - self, config_path: Path, plugin_id: str |
52 |
| - ) -> Dict[str, Any]: |
53 |
| - """Get the plugin settings from a config path. |
| 47 | + def check_index_urls(self, raw_urls: List[str], schema: Path): |
| 48 | + """Validate URLs against a schema.""" |
| 49 | + for raw_url in raw_urls: |
| 50 | + if not raw_url.startswith("./"): |
| 51 | + continue |
54 | 52 |
|
55 |
| - The keys follow the JupyterLab settings naming convention, of module and |
56 |
| - identifier e.g. |
| 53 | + index_url = raw_url.split("?")[0].split("#")[0] |
57 | 54 |
|
58 |
| - @jupyterlite/contents:plugin |
| 55 | + index_path = self.manager.output_dir / index_url |
59 | 56 |
|
60 |
| - This will likely move upstream. |
61 |
| - """ |
62 |
| - if not config_path.exists(): |
63 |
| - return {} |
| 57 | + if not index_path.exists(): |
| 58 | + continue |
64 | 59 |
|
65 |
| - config = json.loads(config_path.read_text(**UTF8)) |
| 60 | + yield self.task( |
| 61 | + name=f"validate:{index_url}", |
| 62 | + doc=f"validate {index_url} against {schema}", |
| 63 | + file_dep=[index_path], |
| 64 | + actions=[(self.validate_one_json_file, [schema, index_path])], |
| 65 | + ) |
66 | 66 |
|
67 |
| - # if a notebook, look in the top-level metadata (which must exist) |
68 |
| - if config_path.name == JUPYTERLITE_IPYNB: |
69 |
| - config = config["metadata"].get(JUPYTERLITE_METADATA, {}) |
| 67 | + @property |
| 68 | + def output_kernel_extension(self) -> Path: |
| 69 | + """the location of the Pyodide kernel labextension static assets""" |
| 70 | + return self.output_extensions / PYODIDE_KERNEL_NPM_NAME |
70 | 71 |
|
71 |
| - return ( |
72 |
| - config.get(JUPYTER_CONFIG_DATA, {}) |
73 |
| - .get(LITE_PLUGIN_SETTINGS, {}) |
74 |
| - .get(plugin_id, {}) |
75 |
| - ) |
| 72 | + @property |
| 73 | + def schemas(self) -> Path: |
| 74 | + """the path to the as-deployed schema in the labextension""" |
| 75 | + return self.output_kernel_extension / "static/schema" |
76 | 76 |
|
77 |
| - def set_lite_plugin_settings( |
78 |
| - self, config_path: Path, plugin_id: str, settings: Dict[str, Any] |
79 |
| - ) -> None: |
80 |
| - """Overwrite the plugin settings for a single plugin in a config path. |
| 77 | + @property |
| 78 | + def output_wheels(self) -> Path: |
| 79 | + """where wheels will go in the output folder""" |
| 80 | + return self.manager.output_dir / PYPI_WHEELS |
81 | 81 |
|
82 |
| - This will likely move upstream. |
83 |
| - """ |
84 |
| - whole_file = config = json.loads(config_path.read_text(**UTF8)) |
85 |
| - if config_path.name == JUPYTERLITE_IPYNB: |
86 |
| - config = whole_file["metadata"][JUPYTERLITE_METADATA] |
| 82 | + @property |
| 83 | + def wheel_cache(self) -> Path: |
| 84 | + """where wheels will go in the cache folder""" |
| 85 | + return self.manager.cache_dir / "wheels" |
| 86 | + |
| 87 | + def get_pyodide_settings(self, config_path: Path): |
| 88 | + """Get the settings for the client-side Pyodide kernel.""" |
| 89 | + return self.get_lite_plugin_settings(config_path, PYODIDE_KERNEL_PLUGIN_ID) |
87 | 90 |
|
88 |
| - config.setdefault(JUPYTER_CONFIG_DATA, {}).setdefault( |
89 |
| - LITE_PLUGIN_SETTINGS, {} |
90 |
| - ).update({plugin_id: settings}) |
| 91 | + def set_pyodide_settings(self, config_path: Path, settings: Dict[str, Any]) -> None: |
| 92 | + """Update the settings for the client-side Pyodide kernel.""" |
| 93 | + return self.set_lite_plugin_settings( |
| 94 | + config_path, PYODIDE_KERNEL_PLUGIN_ID, settings |
| 95 | + ) |
91 | 96 |
|
92 |
| - config_path.write_text(json.dumps(whole_file, **JSON_FMT), **UTF8) |
93 |
| - self.log.debug("%s wrote settings in %s: %s", plugin_id, config_path, settings) |
94 |
| - self.maybe_timestamp(config_path) |
| 97 | + def get_index_urls(self, index_path: Path): |
| 98 | + """Get output_dir relative URLs for an index file.""" |
| 99 | + index_sha256 = sha256(index_path.read_bytes()).hexdigest() |
| 100 | + index_url = f"./{index_path.relative_to(self.manager.output_dir).as_posix()}" |
| 101 | + index_url_with_sha = f"{index_url}?sha256={index_sha256}" |
| 102 | + return index_url, index_url_with_sha |
| 103 | + |
| 104 | + def get_package_wheel_index_url( |
| 105 | + self, pkg_json: Path, index_name: str |
| 106 | + ) -> Optional[Path]: |
| 107 | + pkg_data = json.loads(pkg_json.read_text(**UTF8)) |
| 108 | + wheel_dir = pkg_data.get(PKG_JSON_PYODIDE_KERNEL, {}).get(PKG_JSON_WHEELDIR) |
| 109 | + if wheel_dir: |
| 110 | + pkg_whl_index = pkg_json.parent / wheel_dir / index_name |
| 111 | + if pkg_whl_index.exists(): |
| 112 | + return self.get_index_urls(pkg_whl_index)[1] |
| 113 | + return None |
0 commit comments