Spaces:
Running
on
Zero
Running
on
Zero
# ----------------------------------------------------------------------------- | |
# Copyright (c) 2012 - 2022, 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 re | |
from os.path import basename, join, relpath | |
from typing import ( | |
Callable, | |
ClassVar, | |
Dict, | |
List, | |
Tuple, | |
Union, | |
cast, | |
) | |
# External imports | |
from typing_extensions import Literal, Protocol, 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, bokehjsdir | |
from .util.token import generate_session_id | |
from .util.version import is_full_release | |
# ----------------------------------------------------------------------------- | |
# Globals and constants | |
# ----------------------------------------------------------------------------- | |
DEFAULT_SERVER_HOST = "localhost" | |
DEFAULT_SERVER_PORT = 5006 | |
DEFAULT_SERVER_HTTP_URL = f"http://{DEFAULT_SERVER_HOST}:{DEFAULT_SERVER_PORT}/" | |
BaseMode = Literal["inline", "cdn", "server", "relative", "absolute"] | |
DevMode = Literal["server-dev", "relative-dev", "absolute-dev"] | |
ResourcesMode = Union[BaseMode, DevMode] | |
# __all__ defined at the bottom on the class module | |
# ----------------------------------------------------------------------------- | |
# General API | |
# ----------------------------------------------------------------------------- | |
# ----------------------------------------------------------------------------- | |
# Dev API | |
# ----------------------------------------------------------------------------- | |
Hashes = Dict[str, str] | |
_SRI_HASHES: Dict[str, Hashes] | None = None | |
def get_all_sri_hashes() -> Dict[str, Hashes]: | |
""" Report SRI script hashes for all versions 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 version strings to sub-dictionaries that JavaScipt filenames to | |
their hashes. | |
Returns: | |
dict | |
Example: | |
The returned dict will map version strings to sub-dictionaries for each | |
version: | |
.. code-block:: python | |
{ | |
'1.4.0': { | |
'bokeh-1.4.0.js': 'vn/jmieHiN+ST+GOXzRU9AFfxsBp8gaJ/wvrzTQGpIKMsdIcyn6U1TYtvzjYztkN', | |
'bokeh-1.4.0.min.js': 'mdMpUZqu5U0cV1pLU9Ap/3jthtPth7yWSJTu1ayRgk95qqjLewIkjntQDQDQA5cZ', | |
... | |
} | |
'1.3.4': { | |
... | |
} | |
... | |
} | |
.. _Subresource Integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity | |
""" | |
global _SRI_HASHES | |
if not _SRI_HASHES: | |
with open(join(ROOT_DIR, "_sri.json")) as f: | |
_SRI_HASHES = json.load(f) | |
assert _SRI_HASHES is not None | |
return dict(_SRI_HASHES) | |
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: | |
KeyError: 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 | |
""" | |
hashes = get_all_sri_hashes() | |
return 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") | |
from glob import glob | |
paths = glob(join(bokehjsdir(), "js/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[str] = [] | |
for path in paths: | |
name, suffix = basename(path).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"] | |
class RuntimeMessage: | |
type: Literal["warn"] | |
text: str | |
# XXX: https://github.com/python/mypy/issues/5485 | |
class UrlsFn(Protocol): | |
def __call__(components: List[str], kind: Kind) -> List[str]: ... | |
class HashesFn(Protocol): | |
def __call__(components: List[str], kind: Kind) -> Hashes: ... | |
class Urls: | |
urls: UrlsFn | |
messages: List[RuntimeMessage] = field(default_factory=list) | |
hashes: HashesFn | None = None | |
ResourceAttr = Literal["__css__", "__javascript__"] | |
class BaseResources: | |
_default_root_dir = "." | |
_default_root_url = DEFAULT_SERVER_HTTP_URL | |
mode: BaseMode | |
messages: List[RuntimeMessage] | |
_log_level: LogLevel | |
_js_components: ClassVar[List[str]] | |
_css_components: ClassVar[List[str]] | |
def __init__( | |
self, | |
mode: ResourcesMode | None = None, | |
version: str | None = None, | |
root_dir: PathLike | None = None, | |
minified: bool | None = None, | |
legacy: bool | None = None, | |
log_level: LogLevel | None = None, | |
root_url: str | None = None, | |
path_versioner: PathVersioner | None = None, | |
components: List[str] | None = None, | |
base_dir: str | None = None, # TODO: PathLike | |
): | |
self._components = components | |
if hasattr(self, "_js_components"): | |
self.js_components = self._js_components | |
if hasattr(self, "_css_components"): | |
self.css_components = self._css_components | |
mode = settings.resources(mode) | |
self.dev = mode.endswith("-dev") | |
self.mode = cast(BaseMode, mode[:-4] if self.dev else mode) | |
if self.mode not in get_args(BaseMode): | |
raise ValueError( | |
"wrong value for 'mode' parameter, expected " | |
f"'inline', 'cdn', 'server(-dev)', 'relative(-dev)' or 'absolute(-dev)', got {mode}" | |
) | |
if root_dir and not self.mode.startswith("relative"): | |
raise ValueError("setting 'root_dir' makes sense only when 'mode' is set to 'relative'") | |
if version and not self.mode.startswith("cdn"): | |
raise ValueError("setting 'version' makes sense only when 'mode' is set to 'cdn'") | |
if root_url and not self.mode.startswith("server"): | |
raise ValueError("setting 'root_url' makes sense only when 'mode' is set to 'server'") | |
self.root_dir = settings.rootdir(root_dir) | |
del root_dir | |
self.version = settings.cdn_version(version) | |
del version | |
self.minified = settings.minified(minified) | |
del minified | |
self.legacy = settings.legacy(legacy) | |
del legacy | |
self.log_level = settings.log_level(log_level) | |
del log_level | |
self.path_versioner = path_versioner | |
del path_versioner | |
if root_url and not root_url.endswith("/"): | |
# root_url should end with a /, adding one | |
root_url = root_url + "/" | |
self._root_url = root_url | |
self.messages = [] | |
if self.mode == "cdn": | |
cdn = self._cdn_urls() | |
self.messages.extend(cdn.messages) | |
elif self.mode == "server": | |
server = self._server_urls() | |
self.messages.extend(server.messages) | |
self.base_dir = base_dir or bokehjsdir(self.dev) | |
# Properties -------------------------------------------------------------- | |
def log_level(self) -> LogLevel: | |
return self._log_level | |
def log_level(self, level: LogLevel) -> None: | |
valid_levels = get_args(LogLevel) | |
if not (level is None or level in valid_levels): | |
raise ValueError(f"Unknown log level '{level}', valid levels are: {valid_levels}") | |
self._log_level = level | |
def root_url(self) -> str: | |
if self._root_url is not None: | |
return self._root_url | |
else: | |
return self._default_root_url | |
# Public methods ---------------------------------------------------------- | |
def components(self, kind: Kind) -> List[str]: | |
components = self.js_components if kind == "js" else self.css_components | |
if self._components is not None: | |
components = [c for c in components if c in self._components] | |
return components | |
def _file_paths(self, kind: Kind) -> List[str]: | |
minified = ".min" if not self.dev and self.minified else "" | |
legacy = ".legacy" if self.legacy else "" | |
files = [f"{component}{legacy}{minified}.{kind}" for component in self.components(kind)] | |
paths = [join(self.base_dir, kind, file) for file in files] | |
return paths | |
def _collect_external_resources(self, resource_attr: ResourceAttr) -> List[str]: | |
""" Collect external resources set on resource_attr attribute of all models.""" | |
external_resources: List[str] = [] | |
for _, cls in sorted(Model.model_class_reverse_map.items(), key=lambda arg: arg[0]): | |
external: List[str] | str | None = getattr(cls, resource_attr, None) | |
if isinstance(external, str): | |
if external not in external_resources: | |
external_resources.append(external) | |
elif isinstance(external, list): | |
for e in external: | |
if e not in external_resources: | |
external_resources.append(e) | |
return external_resources | |
def _cdn_urls(self) -> Urls: | |
return _get_cdn_urls(self.version, self.minified, self.legacy) | |
def _server_urls(self) -> Urls: | |
return _get_server_urls(self.root_url, False if self.dev else self.minified, self.legacy, self.path_versioner) | |
def _resolve(self, kind: Kind) -> Tuple[List[str], List[str], Hashes]: | |
paths = self._file_paths(kind) | |
files, raw = [], [] | |
hashes = {} | |
if self.mode == "inline": | |
raw = [self._inline(path) for path in paths] | |
elif self.mode == "relative": | |
root_dir = self.root_dir or self._default_root_dir | |
files = [relpath(path, root_dir) for path in paths] | |
elif self.mode == "absolute": | |
files = list(paths) | |
elif self.mode == "cdn": | |
cdn = self._cdn_urls() | |
files = list(cdn.urls(self.components(kind), kind)) | |
if cdn.hashes: | |
hashes = cdn.hashes(self.components(kind), kind) | |
elif self.mode == "server": | |
server = self._server_urls() | |
files = list(server.urls(self.components(kind), kind)) | |
return (files, raw, hashes) | |
def _inline(path: str) -> str: | |
filename = basename(path) | |
begin = f"/* BEGIN {filename} */" | |
with open(path, "rb") as f: | |
middle = f.read().decode("utf-8") | |
end = f"/* END {filename} */" | |
return f"{begin}\n{middle}\n{end}" | |
class JSResources(BaseResources): | |
""" The Resources class encapsulates information relating to loading or embedding Bokeh Javascript. | |
Args: | |
mode (str) : How should Bokeh JS be included in output | |
See below for descriptions of available modes | |
version (str, optional) : what version of Bokeh JS to load | |
Only valid with the ``'cdn'`` mode | |
root_dir (str, optional) : root directory for loading Bokeh JS assets | |
Only valid with ``'relative'`` and ``'relative-dev'`` modes | |
minified (bool, optional) : whether JavaScript should be minified or not (default: True) | |
root_url (str, optional) : URL and port of Bokeh Server to load resources from (default: None) | |
If ``None``, absolute URLs based on the default server configuration will | |
be generated. | |
``root_url`` can also be the empty string, in which case relative URLs, | |
e.g., "static/js/bokeh.min.js", are generated. | |
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: | |
css_raw : any raw CSS that needs to be places inside ``<style>`` tags | |
css_files : URLs of any CSS files that need to be loaded by ``<link>`` tags | |
messages : any informational messages concerning this configuration | |
These attributes are often useful as template parameters when embedding | |
Bokeh plots. | |
""" | |
_js_components = ["bokeh", "bokeh-gl", "bokeh-widgets", "bokeh-tables", "bokeh-mathjax"] | |
# Properties -------------------------------------------------------------- | |
def js_files(self) -> List[str]: | |
files, _, _ = self._resolve("js") | |
external_resources = self._collect_external_resources("__javascript__") | |
return external_resources + files | |
def js_raw(self) -> List[str]: | |
_, raw, _ = self._resolve("js") | |
if self.log_level is not None: | |
raw.append(f'Bokeh.set_log_level("{self.log_level}");') | |
if self.dev: | |
raw.append("Bokeh.settings.dev = true") | |
return raw | |
def hashes(self) -> Hashes: | |
_, _, hashes = self._resolve("js") | |
return hashes | |
# Public methods ---------------------------------------------------------- | |
def render_js(self) -> str: | |
return JS_RESOURCES.render(js_raw=self.js_raw, js_files=self.js_files, hashes=self.hashes) | |
class CSSResources(BaseResources): | |
""" The CSSResources class encapsulates information relating to loading or embedding Bokeh client-side CSS. | |
Args: | |
mode (str) : how should Bokeh CSS be included in output | |
See below for descriptions of available modes | |
version (str, optional) : what version of Bokeh CSS to load | |
Only valid with the ``'cdn'`` mode | |
root_dir (str, optional) : root directory for loading BokehJS resources | |
Only valid with ``'relative'`` and ``'relative-dev'`` modes | |
minified (bool, optional) : whether 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 BokehJS code and CSS inline | |
* ``'cdn'`` configure to load Bokeh CSS from ``https://cdn.bokeh.org`` | |
* ``'server'`` configure to load from a Bokeh Server | |
* ``'server-dev'`` same as ``server`` but supports non-minified CSS | |
* ``'relative'`` configure to load relative to the given directory | |
* ``'relative-dev'`` same as ``relative`` but supports non-minified CSS | |
* ``'absolute'`` configure to load from the installed Bokeh library static directory | |
* ``'absolute-dev'`` same as ``absolute`` but supports non-minified CSS | |
Once configured, a Resource object exposes the following public attributes: | |
Attributes: | |
css_raw : any raw CSS that needs to be places inside ``<style>`` tags | |
css_files : URLs of any CSS files that need to be loaded by ``<link>`` tags | |
messages : any informational messages concerning this configuration | |
These attributes are often useful as template parameters when embedding Bokeh plots. | |
""" | |
_css_components = [] | |
# Properties -------------------------------------------------------------- | |
def css_files(self) -> List[str]: | |
files, _, _ = self._resolve("css") | |
external_resources = self._collect_external_resources("__css__") | |
return external_resources + files | |
def css_raw(self) -> List[str]: | |
_, raw, _ = self._resolve("css") | |
return raw | |
def css_raw_str(self) -> List[str]: | |
return [json.dumps(css) for css in self.css_raw] | |
# Public methods ---------------------------------------------------------- | |
def render_css(self) -> str: | |
return CSS_RESOURCES.render(css_raw=self.css_raw, css_files=self.css_files) | |
class Resources(JSResources, CSSResources): | |
""" 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 ``<script>`` tags | |
css_raw : any raw CSS that needs to be places inside ``<style>`` tags | |
js_files : URLs of any JS files that need to be loaded by ``<script>`` tags | |
css_files : URLs of any CSS files that need to be loaded by ``<link>`` tags | |
messages : any informational messages concerning this configuration | |
These attributes are often useful as template parameters when embedding | |
Bokeh plots. | |
""" | |
# Public methods ---------------------------------------------------------- | |
def render(self) -> str: | |
css, js = self.render_css(), self.render_js() | |
return f"{css}\n{js}" | |
class SessionCoordinates: | |
""" Internal class used to parse kwargs for server URL, app_path, and session_id.""" | |
_url: str | |
_session_id: ID | None | |
def __init__(self, *, url: str = DEFAULT_SERVER_HTTP_URL, session_id: ID | None = None) -> None: | |
self._url = url | |
if self._url == "default": | |
self._url = DEFAULT_SERVER_HTTP_URL | |
if self._url.startswith("ws"): | |
raise ValueError("url should be the http or https URL for the server, not the websocket URL") | |
self._url = self._url.rstrip("/") | |
# we lazy-generate the session_id so we can generate it server-side when appropriate | |
self._session_id = session_id | |
# Properties -------------------------------------------------------------- | |
def url(self) -> str: | |
return self._url | |
def session_id(self) -> ID: | |
""" Session ID derived from the kwargs provided.""" | |
if self._session_id is None: | |
self._session_id = generate_session_id() | |
return self._session_id | |
def session_id_allowing_none(self) -> ID | None: | |
""" Session ID provided in kwargs, keeping it None if it hasn't been generated yet. | |
The purpose of this is to preserve ``None`` as long as possible... in some cases | |
we may never generate the session ID because we generate it on the server. | |
""" | |
return self._session_id | |
# ----------------------------------------------------------------------------- | |
# Private API | |
# ----------------------------------------------------------------------------- | |
_DEV_PAT = re.compile(r"^(\d)+\.(\d)+\.(\d)+(dev|rc)") | |
def _cdn_base_url() -> str: | |
return "https://cdn.bokeh.org" | |
def _get_cdn_urls(version: str | None = None, minified: bool = True, legacy: bool = False) -> Urls: | |
if version is None: | |
docs_cdn = settings.docs_cdn() | |
version = docs_cdn if docs_cdn else __version__.split("+")[0] | |
# check if we want minified js and css | |
_minified = ".min" if minified else "" | |
_legacy = ".legacy" if legacy else "" | |
base_url = _cdn_base_url() | |
dev_container = "bokeh/dev" | |
rel_container = "bokeh/release" | |
# check the 'dev' fingerprint | |
container = dev_container if _DEV_PAT.match(version) else rel_container | |
def mk_filename(comp: str, kind: Kind) -> str: | |
return f"{comp}-{version}{_legacy}{_minified}.{kind}" | |
def mk_url(comp: str, kind: Kind) -> str: | |
return f"{base_url}/{container}/" + mk_filename(comp, kind) | |
result = Urls(urls=lambda components, kind: [mk_url(component, kind) for component in components]) | |
if len(__version__.split("+")) > 1: | |
result.messages.append(RuntimeMessage( | |
type="warn", | |
text=( | |
f"Requesting CDN BokehJS version '{version}' from Bokeh development version '{__version__}'. " | |
"This configuration is unsupported and may not work!" | |
), | |
)) | |
if is_full_release(version): # TODO: TypeGuard? | |
assert version is not None | |
sri_hashes = get_sri_hashes_for_version(version) | |
result.hashes = lambda components, kind: { | |
mk_url(component, kind): sri_hashes[mk_filename(component, kind)] for component in components | |
} | |
return result | |
def _get_server_urls(root_url: str, minified: bool = True, legacy: bool = False, | |
path_versioner: PathVersioner | None = None) -> Urls: | |
_minified = ".min" if minified else "" | |
_legacy = ".legacy" if legacy else "" | |
def mk_url(comp: str, kind: Kind) -> str: | |
path = f"{kind}/{comp}{_legacy}{_minified}.{kind}" | |
if path_versioner is not None: | |
path = path_versioner(path) | |
return f"{root_url}static/{path}" | |
return Urls(urls=lambda components, kind: [mk_url(component, kind) for component in components]) | |
def _compute_single_hash(path: str) -> str: | |
assert path.endswith(".js") | |
from subprocess import PIPE, Popen | |
digest = f"openssl dgst -sha384 -binary {path}".split() | |
p1 = Popen(digest, stdout=PIPE) | |
b64 = "openssl base64 -A".split() | |
p2 = Popen(b64, stdin=p1.stdout, stdout=PIPE) | |
out, _ = p2.communicate() | |
return out.decode("utf-8").strip() | |
# ----------------------------------------------------------------------------- | |
# Code | |
# ----------------------------------------------------------------------------- | |
ResourcesLike = Union[Resources, ResourcesMode] | |
CDN = Resources(mode="cdn") | |
INLINE = Resources(mode="inline") | |
INLINE_LEGACY = Resources(mode="inline", legacy=True) | |
__all__ = ( | |
"CDN", | |
"INLINE", | |
"INLINE_LEGACY", | |
"Resources", | |
"JSResources", | |
"CSSResources", | |
"get_all_sri_hashes", | |
"get_sri_hashes_for_version", | |
"verify_sri_hashes", | |
) | |