Source code for bokeh.application.handlers.code_runner
#-----------------------------------------------------------------------------# 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.#-----------------------------------------------------------------------------''' Provide a utility class ``CodeRunner`` for use by handlers that executePython source code.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsimportosimportsysimporttracebackfromos.pathimportbasenamefromtypesimportCodeType,ModuleTypefromtypingimportCallable,List# Bokeh importsfrom...core.typesimportPathLikefrom...util.serializationimportmake_globally_unique_idfrom.handlerimporthandle_exception#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('CodeRunner',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------
[docs]classCodeRunner:''' Compile and run Python source code. .. autoclasstoc:: '''_code:CodeType|None_doc:str|None_permanent_error:str|None_permanent_error_detail:str|None_path:PathLike_source:str_argv:List[str]_package:ModuleType|Noneran:bool_failed:bool_error:str|None_error_detail:str|None
[docs]def__init__(self,source:str,path:PathLike,argv:List[str],package:ModuleType|None=None)->None:''' Args: source (str) : A string containing Python source code to execute path (str) : A filename to use in any debugging or error output argv (list[str]) : A list of string arguments to make available as ``sys.argv`` when the code executes package (bool) : An optional package module to configure Raises: ValueError, if package is specified for an __init__.py '''ifpackageandbasename(path)=="__init__.py":raiseValueError("__init__.py cannot have package specified")self._permanent_error=Noneself._permanent_error_detail=Noneself.reset_run_errors()importastself._code=Nonetry:nodes=ast.parse(source,os.fspath(path))self._code=compile(nodes,filename=path,mode='exec',dont_inherit=True)# use a zip to associate code names with values, to then find the contents of the docstringd=dict(zip(self._code.co_names,self._code.co_consts))self._doc=d.get('__doc__',None)exceptSyntaxErrorase:self._code=Nonefilename=os.path.basename(e.filename)ife.filenameisnotNoneelse"???"self._permanent_error=f"Invalid syntax in {filename!r} on line {e.linenoor'???'}:\n{e.textor'???'}"self._permanent_error_detail=traceback.format_exc()self._path=pathself._source=sourceself._argv=argvself._package=packageself.ran=False
# Properties --------------------------------------------------------------@propertydefdoc(self)->str|None:''' Contents of docstring, if code contains one. '''returnself._doc@propertydeferror(self)->str|None:''' If code execution fails, may contain a related error message. '''returnself._errorifself._permanent_errorisNoneelseself._permanent_error@propertydeferror_detail(self)->str|None:''' If code execution fails, may contain a traceback or other details. '''returnself._error_detailifself._permanent_error_detailisNoneelseself._permanent_error_detail@propertydeffailed(self)->bool:''' ``True`` if code execution failed '''returnself._failedorself._codeisNone@propertydefpath(self)->PathLike:''' The path that new modules will be configured with. '''returnself._path@propertydefsource(self)->str:''' The configured source code that will be executed when ``run`` is called. '''returnself._source# Public methods ----------------------------------------------------------
[docs]defnew_module(self)->ModuleType|None:''' Make a fresh module to run in. Returns: Module '''self.reset_run_errors()ifself._codeisNone:returnNonemodule_name='bokeh_app_'+make_globally_unique_id().replace('-','')module=ModuleType(module_name)module.__dict__['__file__']=os.path.abspath(self._path)ifself._package:module.__package__=self._package.__name__module.__path__=[os.path.dirname(self._path)]ifbasename(self.path)=="__init__.py":module.__package__=module_namemodule.__path__=[os.path.dirname(self._path)]returnmodule
[docs]defreset_run_errors(self)->None:''' Clears any transient error conditions from a previous run. Returns None '''self._failed=Falseself._error=Noneself._error_detail=None
[docs]defrun(self,module:ModuleType,post_check:Callable[[],None]|None=None)->None:''' Execute the configured source code in a module and run any post checks. Args: module (Module) : A module to execute the configured code in. post_check (callable, optional) : A function that raises an exception if expected post-conditions are not met after code execution. '''# Simulate the sys.path behaviour described here:## https://docs.python.org/2/library/sys.html#sys.path_cwd=os.getcwd()_sys_path=list(sys.path)_sys_argv=list(sys.argv)sys.path.insert(0,os.path.dirname(self._path))sys.argv=[os.path.basename(self._path)]+self._argv# XXX: self._code shouldn't be None at this point but types don't reflect thisassertself._codeisnotNonetry:exec(self._code,module.__dict__)ifpost_check:post_check()exceptExceptionase:handle_exception(self,e)finally:# undo sys.path, CWD fixupsos.chdir(_cwd)sys.path=_sys_pathsys.argv=_sys_argvself.ran=True