# -----------------------------------------------------------------------------# 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 configuringhow BokehJS code and CSS resources should be located, loaded, and embedded inBokeh documents.Additionally, functions for retrieving `Subresource Integrity`_ hashes forBokeh 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__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)# -----------------------------------------------------------------------------# Imports# -----------------------------------------------------------------------------# Standard library importsimportjsonimportrefromos.pathimportbasename,join,relpathfromtypingimport(Callable,ClassVar,Dict,List,Tuple,Union,cast,)# External importsfromtyping_extensionsimportLiteral,Protocol,get_args# Bokeh importsfrom.import__version__from.core.templatesimportCSS_RESOURCES,JS_RESOURCESfrom.core.typesimportID,PathLikefrom.modelimportModelfrom.settingsimportLogLevel,settingsfrom.util.dataclassesimportdataclass,fieldfrom.util.pathsimportROOT_DIR,bokehjsdirfrom.util.tokenimportgenerate_session_idfrom.util.versionimportis_full_release# -----------------------------------------------------------------------------# Globals and constants# -----------------------------------------------------------------------------DEFAULT_SERVER_HOST="localhost"DEFAULT_SERVER_PORT=5006DEFAULT_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
[docs]defget_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_HASHESifnot_SRI_HASHES:withopen(join(ROOT_DIR,"_sri.json"))asf:_SRI_HASHES=json.load(f)assert_SRI_HASHESisnotNonereturndict(_SRI_HASHES)
[docs]defget_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()returnhashes[version]
[docs]defverify_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 """ifnotis_full_release():raiseValueError("verify_sri_hashes() can only be used with full releases")fromglobimportglobpaths=glob(join(bokehjsdir(),"js/bokeh*.js"))hashes=get_sri_hashes_for_version(__version__)iflen(hashes)<len(paths):raiseRuntimeError("There are unexpected 'bokeh*.js' files in the package")iflen(hashes)>len(paths):raiseRuntimeError("There are 'bokeh*.js' files missing in the package")bad:List[str]=[]forpathinpaths:name,suffix=basename(path).split(".",1)filename=f"{name}-{__version__}.{suffix}"sri_hash=_compute_single_hash(path)ifhashes[filename]!=sri_hash:bad.append(path)ifbad:raiseRuntimeError(f"SRI Hash mismatches in the package: {bad!r}")
PathVersioner=Callable[[str],str]Kind=Literal["css","js"]@dataclassclassRuntimeMessage:type:Literal["warn"]text:str# XXX: https://github.com/python/mypy/issues/5485classUrlsFn(Protocol):@staticmethoddef__call__(components:List[str],kind:Kind)->List[str]:...classHashesFn(Protocol):@staticmethoddef__call__(components:List[str],kind:Kind)->Hashes:...@dataclassclassUrls:urls:UrlsFnmessages:List[RuntimeMessage]=field(default_factory=list)hashes:HashesFn|None=NoneResourceAttr=Literal["__css__","__javascript__"]classBaseResources:_default_root_dir="."_default_root_url=DEFAULT_SERVER_HTTP_URLmode:BaseModemessages: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=componentsifhasattr(self,"_js_components"):self.js_components=self._js_componentsifhasattr(self,"_css_components"):self.css_components=self._css_componentsmode=settings.resources(mode)self.dev=mode.endswith("-dev")self.mode=cast(BaseMode,mode[:-4]ifself.develsemode)ifself.modenotinget_args(BaseMode):raiseValueError("wrong value for 'mode' parameter, expected "f"'inline', 'cdn', 'server(-dev)', 'relative(-dev)' or 'absolute(-dev)', got {mode}")ifroot_dirandnotself.mode.startswith("relative"):raiseValueError("setting 'root_dir' makes sense only when 'mode' is set to 'relative'")ifversionandnotself.mode.startswith("cdn"):raiseValueError("setting 'version' makes sense only when 'mode' is set to 'cdn'")ifroot_urlandnotself.mode.startswith("server"):raiseValueError("setting 'root_url' makes sense only when 'mode' is set to 'server'")self.root_dir=settings.rootdir(root_dir)delroot_dirself.version=settings.cdn_version(version)delversionself.minified=settings.minified(minified)delminifiedself.legacy=settings.legacy(legacy)dellegacyself.log_level=settings.log_level(log_level)dellog_levelself.path_versioner=path_versionerdelpath_versionerifroot_urlandnotroot_url.endswith("/"):# root_url should end with a /, adding oneroot_url=root_url+"/"self._root_url=root_urlself.messages=[]ifself.mode=="cdn":cdn=self._cdn_urls()self.messages.extend(cdn.messages)elifself.mode=="server":server=self._server_urls()self.messages.extend(server.messages)self.base_dir=base_dirorbokehjsdir(self.dev)# Properties --------------------------------------------------------------@propertydeflog_level(self)->LogLevel:returnself._log_level@log_level.setterdeflog_level(self,level:LogLevel)->None:valid_levels=get_args(LogLevel)ifnot(levelisNoneorlevelinvalid_levels):raiseValueError(f"Unknown log level '{level}', valid levels are: {valid_levels}")self._log_level=level@propertydefroot_url(self)->str:ifself._root_urlisnotNone:returnself._root_urlelse:returnself._default_root_url# Public methods ----------------------------------------------------------defcomponents(self,kind:Kind)->List[str]:components=self.js_componentsifkind=="js"elseself.css_componentsifself._componentsisnotNone:components=[cforcincomponentsifcinself._components]returncomponentsdef_file_paths(self,kind:Kind)->List[str]:minified=".min"ifnotself.devandself.minifiedelse""legacy=".legacy"ifself.legacyelse""files=[f"{component}{legacy}{minified}.{kind}"forcomponentinself.components(kind)]paths=[join(self.base_dir,kind,file)forfileinfiles]returnpathsdef_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_,clsinsorted(Model.model_class_reverse_map.items(),key=lambdaarg:arg[0]):external:List[str]|str|None=getattr(cls,resource_attr,None)ifisinstance(external,str):ifexternalnotinexternal_resources:external_resources.append(external)elifisinstance(external,list):foreinexternal:ifenotinexternal_resources:external_resources.append(e)returnexternal_resourcesdef_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,Falseifself.develseself.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={}ifself.mode=="inline":raw=[self._inline(path)forpathinpaths]elifself.mode=="relative":root_dir=self.root_dirorself._default_root_dirfiles=[relpath(path,root_dir)forpathinpaths]elifself.mode=="absolute":files=list(paths)elifself.mode=="cdn":cdn=self._cdn_urls()files=list(cdn.urls(self.components(kind),kind))ifcdn.hashes:hashes=cdn.hashes(self.components(kind),kind)elifself.mode=="server":server=self._server_urls()files=list(server.urls(self.components(kind),kind))return(files,raw,hashes)@staticmethoddef_inline(path:str)->str:filename=basename(path)begin=f"/* BEGIN {filename} */"withopen(path,"rb")asf:middle=f.read().decode("utf-8")end=f"/* END {filename} */"returnf"{begin}\n{middle}\n{end}"
[docs]classJSResources(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 --------------------------------------------------------------@propertydefjs_files(self)->List[str]:files,_,_=self._resolve("js")external_resources=self._collect_external_resources("__javascript__")returnexternal_resources+files@propertydefjs_raw(self)->List[str]:_,raw,_=self._resolve("js")ifself.log_levelisnotNone:raw.append(f'Bokeh.set_log_level("{self.log_level}");')ifself.dev:raw.append("Bokeh.settings.dev = true")returnraw@propertydefhashes(self)->Hashes:_,_,hashes=self._resolve("js")returnhashes# Public methods ----------------------------------------------------------defrender_js(self)->str:returnJS_RESOURCES.render(js_raw=self.js_raw,js_files=self.js_files,hashes=self.hashes)
[docs]classCSSResources(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 --------------------------------------------------------------@propertydefcss_files(self)->List[str]:files,_,_=self._resolve("css")external_resources=self._collect_external_resources("__css__")returnexternal_resources+files@propertydefcss_raw(self)->List[str]:_,raw,_=self._resolve("css")returnraw@propertydefcss_raw_str(self)->List[str]:return[json.dumps(css)forcssinself.css_raw]# Public methods ----------------------------------------------------------defrender_css(self)->str:returnCSS_RESOURCES.render(css_raw=self.css_raw,css_files=self.css_files)
[docs]classResources(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 ----------------------------------------------------------defrender(self)->str:css,js=self.render_css(),self.render_js()returnf"{css}\n{js}"
classSessionCoordinates:""" Internal class used to parse kwargs for server URL, app_path, and session_id."""_url:str_session_id:ID|Nonedef__init__(self,*,url:str=DEFAULT_SERVER_HTTP_URL,session_id:ID|None=None)->None:self._url=urlifself._url=="default":self._url=DEFAULT_SERVER_HTTP_URLifself._url.startswith("ws"):raiseValueError("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 appropriateself._session_id=session_id# Properties --------------------------------------------------------------@propertydefurl(self)->str:returnself._url@propertydefsession_id(self)->ID:""" Session ID derived from the kwargs provided."""ifself._session_idisNone:self._session_id=generate_session_id()returnself._session_id@propertydefsession_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. """returnself._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:ifversionisNone:docs_cdn=settings.docs_cdn()version=docs_cdnifdocs_cdnelse__version__.split("+")[0]# check if we want minified js and css_minified=".min"ifminifiedelse""_legacy=".legacy"iflegacyelse""base_url=_cdn_base_url()dev_container="bokeh/dev"rel_container="bokeh/release"# check the 'dev' fingerprintcontainer=dev_containerif_DEV_PAT.match(version)elserel_containerdefmk_filename(comp:str,kind:Kind)->str:returnf"{comp}-{version}{_legacy}{_minified}.{kind}"defmk_url(comp:str,kind:Kind)->str:returnf"{base_url}/{container}/"+mk_filename(comp,kind)result=Urls(urls=lambdacomponents,kind:[mk_url(component,kind)forcomponentincomponents])iflen(__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!"),))ifis_full_release(version):# TODO: TypeGuard?assertversionisnotNonesri_hashes=get_sri_hashes_for_version(version)result.hashes=lambdacomponents,kind:{mk_url(component,kind):sri_hashes[mk_filename(component,kind)]forcomponentincomponents}returnresultdef_get_server_urls(root_url:str,minified:bool=True,legacy:bool=False,path_versioner:PathVersioner|None=None)->Urls:_minified=".min"ifminifiedelse""_legacy=".legacy"iflegacyelse""defmk_url(comp:str,kind:Kind)->str:path=f"{kind}/{comp}{_legacy}{_minified}.{kind}"ifpath_versionerisnotNone:path=path_versioner(path)returnf"{root_url}static/{path}"returnUrls(urls=lambdacomponents,kind:[mk_url(component,kind)forcomponentincomponents])def_compute_single_hash(path:str)->str:assertpath.endswith(".js")fromsubprocessimportPIPE,Popendigest=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()returnout.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",)