# ----------------------------------------------------------------------------- # Copyright (c) Anaconda, Inc., and Bokeh Contributors. # All rights reserved. # # The full license is in the file LICENSE.txt, distributed with this software. # ----------------------------------------------------------------------------- """ The resources module provides the Resources class for easily configuring how BokehJS code and CSS resources should be located, loaded, and embedded in Bokeh documents. Additionally, functions for retrieving `Subresource Integrity`_ hashes for Bokeh JavaScript files are provided here. Some pre-configured Resources objects are made available as attributes. Attributes: CDN : load minified BokehJS from CDN INLINE : provide minified BokehJS from library static directory .. _Subresource Integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity """ # ----------------------------------------------------------------------------- # Boilerplate # ----------------------------------------------------------------------------- from __future__ import annotations import logging # isort:skip log = logging.getLogger(__name__) # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- # Standard library imports import json import os import re from os.path import relpath from pathlib import Path from typing import ( TYPE_CHECKING, Callable, ClassVar, Literal, Protocol, TypedDict, Union, cast, get_args, ) # Bokeh imports from . import __version__ from .core.templates import CSS_RESOURCES, JS_RESOURCES from .core.types import ID, PathLike from .model import Model from .settings import LogLevel, settings from .util.dataclasses import dataclass, field from .util.paths import ROOT_DIR from .util.token import generate_session_id from .util.version import is_full_release if TYPE_CHECKING: from typing_extensions import TypeAlias # ----------------------------------------------------------------------------- # Globals and constants # ----------------------------------------------------------------------------- DEFAULT_SERVER_HOST = settings.default_server_host() DEFAULT_SERVER_PORT = settings.default_server_port() def server_url(host: str | None = None, port: int | None = None, ssl: bool = False) -> str: protocol = "https" if ssl else "http" return f"{protocol}://{host or DEFAULT_SERVER_HOST}:{port or DEFAULT_SERVER_PORT}/" DEFAULT_SERVER_HTTP_URL = server_url() BaseMode: TypeAlias = Literal["inline", "cdn", "server", "relative", "absolute"] DevMode: TypeAlias = Literal["server-dev", "relative-dev", "absolute-dev"] ResourcesMode: TypeAlias = Union[BaseMode, DevMode] Component = Literal["bokeh", "bokeh-gl", "bokeh-widgets", "bokeh-tables", "bokeh-mathjax", "bokeh-api"] class ComponentDefs(TypedDict): js: list[Component] css: list[Component] # __all__ defined at the bottom on the class module # ----------------------------------------------------------------------------- # General API # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # Dev API # ----------------------------------------------------------------------------- Hashes: TypeAlias = dict[str, str] _ALL_SRI_HASHES: dict[str, Hashes] = {} def get_all_sri_versions() -> tuple[str, ...]: """ Report all versions that have SRI hashes. Returns: tuple """ files = (ROOT_DIR / "_sri").glob("*.json") return set(file.stem for file in files) def get_sri_hashes_for_version(version: str) -> Hashes: """ Report SRI script hashes for a specific version of BokehJS. Bokeh provides `Subresource Integrity`_ hashes for all JavaScript files that are published to CDN for full releases. This function returns a dictionary that maps JavaScript filenames to their hashes, for a single version of Bokeh. Args: version (str) : The Bokeh version to return SRI hashes for. Hashes are only provided for full releases, e.g "1.4.0", and not for "dev" builds or release candidates. Returns: dict Raises: ValueError: if the specified version does not exist Example: The returned dict for a single version will map filenames for that version to their SRI hashes: .. code-block:: python { 'bokeh-1.4.0.js': 'vn/jmieHiN+ST+GOXzRU9AFfxsBp8gaJ/wvrzTQGpIKMsdIcyn6U1TYtvzjYztkN', 'bokeh-1.4.0.min.js': 'mdMpUZqu5U0cV1pLU9Ap/3jthtPth7yWSJTu1ayRgk95qqjLewIkjntQDQDQA5cZ', 'bokeh-api-1.4.0.js': 'Y3kNQHt7YjwAfKNIzkiQukIOeEGKzUU3mbSrraUl1KVfrlwQ3ZAMI1Xrw5o3Yg5V', 'bokeh-api-1.4.0.min.js': '4oAJrx+zOFjxu9XLFp84gefY8oIEr75nyVh2/SLnyzzg9wR+mXXEi+xyy/HzfBLM', 'bokeh-tables-1.4.0.js': 'I2iTMWMyfU/rzKXWJ2RHNGYfsXnyKQ3YjqQV2RvoJUJCyaGBrp0rZcWiTAwTc9t6', 'bokeh-tables-1.4.0.min.js': 'pj14Cq5ZSxsyqBh+pnL2wlBS3UX25Yz1gVxqWkFMCExcnkN3fl4mbOF8ZUKyh7yl', 'bokeh-widgets-1.4.0.js': 'scpWAebHEUz99AtveN4uJmVTHOKDmKWnzyYKdIhpXjrlvOwhIwEWUrvbIHqA0ke5', 'bokeh-widgets-1.4.0.min.js': 'xR3dSxvH5hoa9txuPVrD63jB1LpXhzFoo0ho62qWRSYZVdyZHGOchrJX57RwZz8l' } .. _Subresource Integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity """ if version not in _ALL_SRI_HASHES: try: with open(ROOT_DIR / "_sri" / f"{version}.json") as f: _ALL_SRI_HASHES[version] = json.load(f) except Exception as e: raise ValueError(f"Missing SRI hash for version {version}") from e return _ALL_SRI_HASHES[version] def verify_sri_hashes() -> None: """ Verify the SRI hashes in a full release package. This function compares the computed SRI hashes for the BokehJS files in a full release package to the values in the SRI manifest file. Returns None if all hashes match, otherwise an exception will be raised. .. note:: This function can only be called on full release (e.g "1.2.3") packages. Returns: None Raises: ValueError If called outside a full release package RuntimeError If there are missing, extra, or mismatched files """ if not is_full_release(): raise ValueError("verify_sri_hashes() can only be used with full releases") paths = list((settings.bokehjs_path() / "js").glob("bokeh*.js")) hashes = get_sri_hashes_for_version(__version__) if len(hashes) < len(paths): raise RuntimeError("There are unexpected 'bokeh*.js' files in the package") if len(hashes) > len(paths): raise RuntimeError("There are 'bokeh*.js' files missing in the package") bad: list[Path] = [] for path in paths: name, suffix = str(path.name).split(".", 1) filename = f"{name}-{__version__}.{suffix}" sri_hash = _compute_single_hash(path) if hashes[filename] != sri_hash: bad.append(path) if bad: raise RuntimeError(f"SRI Hash mismatches in the package: {bad!r}") PathVersioner = Callable[[str], str] Kind = Literal["css", "js"] @dataclass class RuntimeMessage: type: Literal["warn"] text: str # XXX: https://github.com/python/mypy/issues/5485 class UrlsFn(Protocol): @staticmethod def __call__(components: list[str], kind: Kind) -> list[str]: ... class HashesFn(Protocol): @staticmethod def __call__(components: list[str], kind: Kind) -> Hashes: ... @dataclass class Urls: urls: UrlsFn messages: list[RuntimeMessage] = field(default_factory=list) hashes: HashesFn | None = None ResourceAttr = Literal["__css__", "__javascript__"] class Resources: """ The Resources class encapsulates information relating to loading or embedding Bokeh Javascript and CSS. Args: mode (str) : how should Bokeh JS and CSS be included in output See below for descriptions of available modes version (str, optional) : what version of Bokeh JS and CSS to load Only valid with the ``'cdn'`` mode root_dir (str, optional) : root directory for loading Bokeh JS and CSS assets Only valid with ``'relative'`` and ``'relative-dev'`` modes minified (bool, optional) : whether JavaScript and CSS should be minified or not (default: True) root_url (str, optional) : URL and port of Bokeh Server to load resources from Only valid with ``'server'`` and ``'server-dev'`` modes The following **mode** values are available for configuring a Resource object: * ``'inline'`` configure to provide entire Bokeh JS and CSS inline * ``'cdn'`` configure to load Bokeh JS and CSS from ``https://cdn.bokeh.org`` * ``'server'`` configure to load from a Bokeh Server * ``'server-dev'`` same as ``server`` but supports non-minified assets * ``'relative'`` configure to load relative to the given directory * ``'relative-dev'`` same as ``relative`` but supports non-minified assets * ``'absolute'`` configure to load from the installed Bokeh library static directory * ``'absolute-dev'`` same as ``absolute`` but supports non-minified assets Once configured, a Resource object exposes the following public attributes: Attributes: js_raw : any raw JS that needs to be placed inside ``