#-----------------------------------------------------------------------------# Copyright (c) Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------''' Provide a Bokeh Application Handler to build up documents by compilingand executing Python source code.This Handler is used by the Bokeh server command line tool to buildapplications that run off scripts and notebooks... code-block:: python def make_doc(doc: Document): # do work to modify the document, add plots, widgets, etc. return doc app = Application(FunctionHandler(make_doc)) server = Server({'/bkapp': app}, io_loop=IOLoop.current()) server.start()'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsfromcontextlibimportcontextmanagerfromos.pathimportbasename,splitextfromtypesimportModuleTypefromtypingimportAny,Callable,ClassVar# Bokeh importsfrom...core.typesimportPathLikefrom...documentimportDocumentfrom...io.docimportcurdoc,patch_curdocfrom.code_runnerimportCodeRunnerfrom.handlerimportHandler#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('CodeHandler',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------
[docs]classCodeHandler(Handler):''' Run source code which modifies a Document '''# These functions, if present in the supplied code, will be monkey patched# to be no-ops, with a warning._io_functions=['output_notebook','output_file','show','save','reset_output']_loggers:dict[str,Callable[...,None]]_logger_text:ClassVar[str]_origin:ClassVar[str]
[docs]def__init__(self,*,source:str,filename:PathLike,argv:list[str]=[],package:ModuleType|None=None)->None:''' Args: source (str) : python source code filename (str) : a filename to use in any debugging or error output argv (list[str], optional) : a list of string arguments to make available as ``sys.argv`` when the code executes '''super().__init__()self._runner=CodeRunner(source,filename,argv,package=package)self._loggers={}forfinCodeHandler._io_functions:self._loggers[f]=self._make_io_logger(f)
# Properties --------------------------------------------------------------@propertydeferror(self)->str|None:''' If the handler fails, may contain a related error message. '''returnself._runner.error@propertydeferror_detail(self)->str|None:''' If the handler fails, may contain a traceback or other details. '''returnself._runner.error_detail@propertydeffailed(self)->bool:''' ``True`` if the handler failed to modify the doc '''returnself._runner.failed@propertydefsafe_to_fork(self)->bool:''' Whether it is still safe for the Bokeh server to fork new workers. ``False`` if the code has already been executed. '''returnnotself._runner.ran# Public methods ----------------------------------------------------------
[docs]defmodify_document(self,doc:Document)->None:''' Run Bokeh application code to update a ``Document`` Args: doc (Document) : a ``Document`` to update '''module=self._runner.new_module()# If no module was returned it means the code runner has some permanent# unfixable problem, e.g. the configured source code has a syntax errorifmoduleisNone:return# One reason modules are stored is to prevent the module from being gc'd# before the document is. A symptom of a gc'd module is that its globals# become None. Additionally stored modules are used to provide correct# paths to custom models resolver.doc.modules.add(module)with_monkeypatch_io(self._loggers):withpatch_curdoc(doc):self._runner.run(module,self._make_post_doc_check(doc))
[docs]defurl_path(self)->str|None:''' The last path component for the basename of the configured filename. '''ifself.failed:returnNone# TODO should fix invalid URL charactersreturn'/'+splitext(basename(self._runner.path))[0]
# Private methods ---------------------------------------------------------# subclasses must define self._logger_textdef_make_io_logger(self,name:str)->Callable[...,None]:deflogger(*args:Any,**kwargs:Any)->None:log.info(self._logger_text,self._runner.path,name)returnlogger# script is supposed to edit the doc not replace itdef_make_post_doc_check(self,doc:Document)->Callable[[],None]:deffunc()->None:ifcurdoc()isnotdoc:raiseRuntimeError(f"{self._origin} at {self._runner.path!r} replaced the output document")returnfunc
#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------# monkeypatching is a little ugly, but in this case there's no reason any legitimate# code should be calling these functions, and we're only making a best effort to# warn people so no big deal if we fail.@contextmanagerdef_monkeypatch_io(loggers:dict[str,Callable[...,None]])->dict[str,Any]:importbokeh.ioasioold:dict[str,Any]={}forfinCodeHandler._io_functions:old[f]=getattr(io,f)setattr(io,f,loggers[f])yieldforfinold:setattr(io,f,old[f])#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------