Source code for bokeh.application.handlers.directory
#-----------------------------------------------------------------------------# Copyright (c) 2012 - 2023, 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 runningthe code from ``main.py`` or ``main.ipynb`` files in specified directories.The directory may also optionally contain:* A ``server_lifecyle.py`` module to provide lifecycle callbacks for the application and sessions.* A ``static`` subdirectory containing app-specific static resources to serve.* A ``theme.yaml`` file containing a Bokeh theme to automatically apply to all new documents.* A ``templates`` subdirectory containing templates for app displayA full directory layout might look like:.. code-block:: none myapp | +---main.py +---server_lifecycle.py +---static +---theme.yaml +---templates +---index.html'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsimportsysfromos.pathimport(basename,dirname,exists,join,)fromtypesimportModuleTypefromtypingimportTYPE_CHECKING,Any,Coroutine# External importsfromjinja2importEnvironment,FileSystemLoader,Template# Bokeh importsfrom...core.typesimportPathLikefrom...documentimportDocumentfrom..applicationimportServerContext,SessionContextfrom.code_runnerimportCodeRunnerfrom.handlerimportHandlerfrom.notebookimportNotebookHandlerfrom.scriptimportScriptHandlerfrom.server_lifecycleimportServerLifecycleHandlerfrom.server_request_handlerimportServerRequestHandlerifTYPE_CHECKING:fromtornado.httputilimportHTTPServerRequestfrom...themesimportTheme#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('DirectoryHandler',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------
[docs]classDirectoryHandler(Handler):''' Load an application directory which modifies a Document. '''_package_runner:CodeRunner|None_package:ModuleType|None_lifecycle_handler:Handler_request_handler:Handler_theme:Theme|None_static:str|None_template:Template|None
[docs]def__init__(self,*,filename:PathLike,argv:list[str]=[])->None:''' Keywords: filename (str) : a path to an application directory with either "main.py" or "main.ipynb" argv (list[str], optional) : a list of string arguments to make available as sys.argv to main.py '''super().__init__()src_path=filenameinit_py=join(src_path,'__init__.py')ifexists(init_py):withopen(init_py,encoding="utf8")asinit_py_f:self._package_runner=CodeRunner(init_py_f.read(),init_py,argv)self._package=self._package_runner.new_module()assertself._packageisnotNonesys.modules[self._package.__name__]=self._packageelse:self._package_runner=Noneself._package=Nonemain_py=join(src_path,'main.py')main_ipy=join(src_path,'main.ipynb')ifexists(main_py)andexists(main_ipy):log.warning(f"Found both 'main.py' and 'main.ipynb' in {src_path}, using 'main.py'")main=main_pyelifexists(main_py):main=main_pyelifexists(main_ipy):main=main_ipyelse:raiseValueError(f"No 'main.py' or 'main.ipynb' in {src_path}")self._path=src_pathself._main=mainhandler=NotebookHandlerifmain.endswith('.ipynb')elseScriptHandlerself._main_handler=handler(filename=self._main,argv=argv,package=self._package)hooks=Noneapp_hooks=join(src_path,'app_hooks.py')lifecycle=join(src_path,'server_lifecycle.py')ifexists(app_hooks)andexists(lifecycle):raiseValueError("Directory style apps can provide either server_lifecycle.py or app_hooks.py, not both.")elifexists(lifecycle):hooks=lifecycleelifexists(app_hooks):hooks=app_hooksifhooksisnotNone:self._lifecycle_handler=ServerLifecycleHandler(filename=hooks,argv=argv,package=self._package)else:self._lifecycle_handler=Handler()# no-op handlerifexists(app_hooks):asserthooksisnotNoneself._request_handler=ServerRequestHandler(filename=hooks,argv=argv,package=self._package)else:self._request_handler=Handler()# no-op handlerself._theme=Nonethemeyaml=join(src_path,'theme.yaml')ifexists(themeyaml):frombokeh.themesimportThemeself._theme=Theme(filename=themeyaml)appstatic=join(src_path,'static')ifexists(appstatic):self._static=appstaticself._template=Noneappindex=join(src_path,'templates','index.html')ifexists(appindex):env=Environment(loader=FileSystemLoader(dirname(appindex)))self._template=env.get_template('index.html')
# Properties --------------------------------------------------------------@propertydeferror(self)->str|None:''' If the handler fails, may contain a related error message. '''returnself._main_handler.errororself._lifecycle_handler.error@propertydeferror_detail(self)->str|None:''' If the handler fails, may contain a traceback or other details. '''returnself._main_handler.error_detailorself._lifecycle_handler.error_detail@propertydeffailed(self)->bool:''' ``True`` if the handler failed to modify the doc '''returnself._main_handler.failedorself._lifecycle_handler.failed@propertydefsafe_to_fork(self)->bool:''' Whether it is still safe for the Bokeh server to fork new workers. ``False`` if the configured code (script, notebook, etc.) has already been run. '''returnself._main_handler.safe_to_fork# Public methods ----------------------------------------------------------
[docs]defmodify_document(self,doc:Document)->None:''' Execute the configured ``main.py`` or ``main.ipynb`` to modify the document. This method will also search the app directory for any theme or template files, and automatically configure the document with them if they are found. '''ifself._lifecycle_handler.failed:return# Note: we do NOT copy self._theme, which assumes the Theme# class is immutable (has no setters)ifself._themeisnotNone:doc.theme=self._themeifself._templateisnotNone:doc.template=self._template# This internal handler should never add a templateself._main_handler.modify_document(doc)
[docs]defon_server_loaded(self,server_context:ServerContext)->None:''' Execute `on_server_unloaded`` from ``server_lifecycle.py`` (if it is defined) when the server is first started. Args: server_context (ServerContext) : '''ifself._package_runnerandself._package:self._package_runner.run(self._package)returnself._lifecycle_handler.on_server_loaded(server_context)
[docs]defon_server_unloaded(self,server_context:ServerContext)->None:''' Execute ``on_server_unloaded`` from ``server_lifecycle.py`` (if it is defined) when the server cleanly exits. (Before stopping the server's ``IOLoop``.) Args: server_context (ServerContext) : .. warning:: In practice this code may not run, since servers are often killed by a signal. '''returnself._lifecycle_handler.on_server_unloaded(server_context)
[docs]defon_session_created(self,session_context:SessionContext)->Coroutine[Any,Any,None]:''' Execute ``on_session_created`` from ``server_lifecycle.py`` (if it is defined) when a new session is created. Args: session_context (SessionContext) : '''returnself._lifecycle_handler.on_session_created(session_context)
[docs]defon_session_destroyed(self,session_context:SessionContext)->Coroutine[Any,Any,None]:''' Execute ``on_session_destroyed`` from ``server_lifecycle.py`` (if it is defined) when a session is destroyed. Args: session_context (SessionContext) : '''returnself._lifecycle_handler.on_session_destroyed(session_context)
[docs]defprocess_request(self,request:HTTPServerRequest)->dict[str,Any]:''' Processes incoming HTTP request returning a dictionary of additional data to add to the session_context. Args: request: HTTP request Returns: A dictionary of JSON serializable data to be included on the session context. '''returnself._request_handler.process_request(request)
[docs]defurl_path(self)->str|None:''' The last path component for the basename of the path to the configured directory. '''ifself.failed:returnNoneelse:# TODO should fix invalid URL charactersreturn'/'+basename(self._path)