Python 如何在嵌入式Bokeh服务器上使用不同数据重用Bokeh应用程序

Python 如何在嵌入式Bokeh服务器上使用不同数据重用Bokeh应用程序,python,bokeh,Python,Bokeh,我已经将《用户指南》一节中的简单Bokeh应用程序示例集成到我的flask应用程序中,只是为了验证我可以正确设置集成到我的flask应用程序所需的服务器基础结构,并且我可以在没有任何阻碍的情况下成功地路由到它 我不清楚的是如何摆脱bokeh应用程序和依赖于modify\u doc全局函数之间的硬关联,该函数使用罐装数据,其签名必须是bokeh文档的单个参数。相反,我想将模型中的数据推送到一个函数中,然后Bokeh将使用该函数呈现文档。有一个警告可能重要,也可能不重要,但只有当用户在运行时加载由F

我已经将《用户指南》一节中的简单Bokeh应用程序示例集成到我的flask应用程序中,只是为了验证我可以正确设置集成到我的flask应用程序所需的服务器基础结构,并且我可以在没有任何阻碍的情况下成功地路由到它

我不清楚的是如何摆脱bokeh应用程序和依赖于
modify\u doc
全局函数之间的硬关联,该函数使用罐装数据,其签名必须是bokeh文档的单个参数。相反,我想将模型中的数据推送到一个函数中,然后Bokeh将使用该函数呈现文档。有一个警告可能重要,也可能不重要,但只有当用户在运行时加载由Flask应用程序和Flask会话维护的csv文件时,才会填充模型

作为一个最小的例子,我的flask应用程序维护了一个模型实例,其中有一个pandas数据框,我希望创建一个Bokeh应用程序,我可以重用该应用程序来绘制数据框不同列对的散点图。这些对通过Flask创建的链接呈现给用户,这些链接当前指向具有通过matplotlib生成的静态图像的页面。这些图像由一个函数创建,该函数将用于生成文件名的一对列名以及x和y坐标作为数据帧的列。列名作为参数传递给flask路由,该路由向下流到获取相应图像的模板

基本上,我想在Bokeh中的不同数据上以某种功能性方式重用相同的绘图逻辑,就像我使用Matplotlib一样。似乎
FunctionHandler
技术是固定在数据源上的,因为我试图创建一个类来维护实现
modify\u doc
方法的状态,但问题在于类中需要的
self
参数

class BokehParamPlot() :
    def __init__( self , paramName , x , y ) :   
        # Initialize member data
        self.paramName = paramName
        self.x         = x
        self.y         = y

    def modify_doc( self , doc ) :
        x = self.x
        y = self.y

        source = ColumnDataSource(data=dict(x=x, y=y))

        plot = figure()
        plot.line('x', 'y', source=source)
        doc.add_root(column(slider, plot))
上述尝试的精神是尝试对
FunctionHandler
技术进行参数化,因为应用程序将完全相同,只是数据不同,我不想为数据帧中的每一对数据创建一个应用程序。不过运气不好

  File "ParamPlot.py", line 362, in <module>
    bokeh_app = Application(FunctionHandler(BokehParamPlot.modify_doc))
  File "C:\Anaconda3\lib\site-packages\bokeh\application\handlers\function.py", line 11, in __init__
    _check_callback(func, ('doc',))
  File "C:\Anaconda3\lib\site-packages\bokeh\util\callback_manager.py", line 32, in _check_callback
    raise ValueError(error_msg % (", ".join(fargs), formatted_args))
ValueError: Callback functions must have signature func(doc), got func(self, doc)
文件“ParamPlot.py”,第362行,在
bokeh_app=应用程序(FunctionHandler(BokehParamPlot.modify_doc))
文件“C:\Anaconda3\lib\site packages\bokeh\application\handlers\function.py”,第11行,在\uuu init中__
_检查回调(func,('doc',))
文件“C:\Anaconda3\lib\site packages\bokeh\util\callback\u manager.py”,第32行,在\u check\u callback中
raise VALUERROR(错误消息%(“,”.join(fargs),格式化参数))
ValueError:回调函数必须具有签名func(doc)、get func(self、doc)

查看Bokeh源代码,另一个很有希望的途径是,但我希望我尝试的不是一些遥远的用例,我只是错过了一些点或一些与基本Python的连接,或者一些可能很快让我上路的东西。

通过向Bokeh应用程序传递查询字符串,这确实是可能的。在这个重用场景中需要发现一个关键问题,那就是
自动加载\u服务器的
会话\u id
参数的作用。[编辑说明:此会话\u id功能仅适用于Bokeh 12.4、12.5和此技术]。该会话ID在通过它传递的不同数据的不同文档呈现之间必须是不同的,因为如果使用相同的ID,则呈现的第一个数据在尝试呈现时将被缓存,以用于其余数据。如果需要将更多参数信息传递到文档中,您可以将其插入到
script
标记的
src
属性中,该标记由
autoload\u server
返回,非常类似于show

接下来是使模型数据对呈现bokeh文档的模块可见的方法。在我的例子中,我重新分层了我的Flask应用程序,将我的用户会话模型存储在一个较低级别的模块中,该模块可供Flask应用程序主脚本和绘图模块访问,我在其中保存了所有绘图逻辑。在我的Flask主页中,我执行以下逻辑来设置Flask会话cookie中的会话信息,并将a参数从Flask向下传递到我的bokeh文档

MyFlaskApp.py

@flaskApp.route( '/' , methods=['GET', 'POST'] )
def index():
    # Create or load model instance for this user session based on model uuid cookie
    if sKeyModel in session :
        if session[ sKeyModel ] not in SessionInfo.userModels :
            logger.debug( "Loading Model instance for current user" )
            SessionInfo.userModels[ session[ sKeyModel ] ] = Model( ) # TODO create new for now, but latter must support loading from persisted JSON model file
    else :
        logger.debug( "Creating Model instance for current user" )
        session[ sKeyModel ] = str( uuid.uuid1() )
        SessionInfo.userModels[ session[ sKeyModel ] ] = Model( )

    model = SessionInfo.userModels[ session[ sKeyModel ] ]
...
@flaskApp.route( '/Plots/<param>' , methods=['GET'] )
def interactivePlotsPage( param ) :     
    script = autoload_server( model      = None ,
                              session_id = "%s.%s" % ( session[sKeyModel] , param ),
                              url        = 'http://localhost:5006/' )

    return render_template( "plotsPage.html" , script=script )
bokehSession = "bokeh-session-id"
userModels = {} # GUID to user Model instances maintained in the Server
def modify_doc(doc) :
    args       = doc.session_context.request.arguments
    session_Id = str( args[SessionInfo.bokehSession][0].decode('utf-8') ).split(".")
    sessionId  = session_Id[0]
    param      = session_Id[1]

    logger.debug( "modify_doc %s" % param )

    model = SessionInfo.userModels[sessionId]
    # User now has access to the model and param to drill into the model to pull out arbitrary data to conduct plots with.
Plots.py

@flaskApp.route( '/' , methods=['GET', 'POST'] )
def index():
    # Create or load model instance for this user session based on model uuid cookie
    if sKeyModel in session :
        if session[ sKeyModel ] not in SessionInfo.userModels :
            logger.debug( "Loading Model instance for current user" )
            SessionInfo.userModels[ session[ sKeyModel ] ] = Model( ) # TODO create new for now, but latter must support loading from persisted JSON model file
    else :
        logger.debug( "Creating Model instance for current user" )
        session[ sKeyModel ] = str( uuid.uuid1() )
        SessionInfo.userModels[ session[ sKeyModel ] ] = Model( )

    model = SessionInfo.userModels[ session[ sKeyModel ] ]
...
@flaskApp.route( '/Plots/<param>' , methods=['GET'] )
def interactivePlotsPage( param ) :     
    script = autoload_server( model      = None ,
                              session_id = "%s.%s" % ( session[sKeyModel] , param ),
                              url        = 'http://localhost:5006/' )

    return render_template( "plotsPage.html" , script=script )
bokehSession = "bokeh-session-id"
userModels = {} # GUID to user Model instances maintained in the Server
def modify_doc(doc) :
    args       = doc.session_context.request.arguments
    session_Id = str( args[SessionInfo.bokehSession][0].decode('utf-8') ).split(".")
    sessionId  = session_Id[0]
    param      = session_Id[1]

    logger.debug( "modify_doc %s" % param )

    model = SessionInfo.userModels[sessionId]
    # User now has access to the model and param to drill into the model to pull out arbitrary data to conduct plots with.
对于我的应用程序,Flask应用程序可以在内存中维护所有模型数据,因为这只是一个内部数据分析仪表板工具,我们用来分析一些机器学习异常值和性能

使用查询字符串,我们可以向下传递参数,以便在Bokeh中访问,然后基本上只需要访问需要在Bokeh文档中呈现的数据。用户组中有一个条目()讨论了这种情况

翻开不同的信息来源,我也发现了这个 来自布莱恩·范德文:

有很多方法可以做到这一点,也可以做“重担” 在别处你只需要传达结果/数据/任何可能的信息 应用程序需要在应用程序中进行绘图。但那可能是 通过多种方式完成:

  • 应用程序可以访问HTML请求参数(新功能)
  • 应用程序可以访问cookie
  • 应用程序可以访问外部数据库(redis、mongo等)中的数据
  • 应用程序可以对RESTAPI进行AJAX调用
基于此,我认为一个可能的解决方案是:生成数据 要绘制(在烧瓶侧),请使用pull_session()获取会话 使用session.id作为密钥临时将数据存储在redis中 使用bokeh应用程序中的会话id从redis获取数据

我认为真正能激发你思考的是如何回答这个问题“我怎样才能使数据在全局范围内对这个函数可见?”