
Bokeh DirectoryHandler 深度解析目录式 Bokeh Server 应用的组织、加载与生命周期【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh导读Bokeh Server 的目录式应用Directory-style App是官方推荐的多文件应用组织方式把main.py或main.ipynb、生命周期回调、静态资源、主题与页面模板统一放进一个目录交给bokeh serve myapp一条命令启动。本文以 Bokeh 的 DirectoryHandler 实现 为骨架结合 单元测试 与 examples/server/app 中的真实应用目录完整讲解目录结构约定、各子模块的加载规则、应用与会话生命周期回调、请求钩子以及 theme/template/static 的自动装配机制。读完你将能独立搭建、调试和扩展一个生产可用的 Bokeh 目录式应用。一、目录式应用是什么1.1 Handler 在 Bokeh 应用中的角色Bokeh 服务端应用中Application是 Document 的工厂每当一个会话session建立服务器会创建一个新的空 Document再依次调用所有 handler 的modify_document()方法填充内容最终用填充好的 Document 服务该会话见 application.py 中create_document()/initialize_document()的实现。Handler基类见 handler.py定义了这一机制的全部钩子。DirectoryHandler正是其中负责“目录式应用”的处理器它继承了Handler的全部契约并将一个目录内的多个文件组织成完整的应用。1.2 官方推荐的目录结构由 directory.py 模块 docstring 给出的标准布局如下myapp | ---main.py ---server_lifecycle.py ---static ---theme.yaml ---templates ---index.html其中各文件/目录的含义与可选项如下表路径是否必需作用main.py或main.ipynb必需二选一每次会话执行的应用主代码负责向curdoc()添加根模型server_lifecycle.py可选提供on_server_loaded等应用/会话生命周期回调旧式命名app_hooks.py可选生命周期回调 process_request()请求钩子新式命名与server_lifecycle.py互斥__init__.py可选存在时把目录当作 Python 包加载使main.py可以from . import ...相对导入static/可选应用专属静态资源目录由服务器直接对外提供theme.yaml可选Bokeh 主题文件自动应用到每个新 Documenttemplates/index.html可选Jinja2 页面模板用于自定义应用外壳页面二、DirectoryHandler 的构造与文件发现规则DirectoryHandler.__init__(filename, argv[])接收目录路径与可选的argv参数列表该列表会以sys.argv形式注入main.py。构造过程在 directory.py 中按固定顺序完成文件发现与子处理器装配2.1 包的加载可选__init__.py若目录下存在__init__.pyDirectoryHandler会用它创建一个CodeRunner与模块对象并注册到sys.modules见 directory.py。这使得目录内的main.py可以像普通 Python 包一样使用相对导入如from . import data。gapminder示例目录examples/server/app/gapminder就同时包含__init__.py与data.py由main.py导入共享数据。2.2 主入口发现main.py与main.ipynb发现规则见 directory.py为同时存在main.py与main.ipynb优先使用main.py并输出警告日志Found both main.py and main.ipynb ...仅存在其中一个使用存在的那个两者都不存在抛出ValueError: No main.py or main.ipynb in path。随后根据入口文件后缀选择底层处理器.ipynb走NotebookHandler.py走ScriptHandlerdirectory.py。ScriptHandler的本质是读取源码文本交给CodeHandler执行见 script.py执行期间 Document 以curdoc形式暴露、argv以sys.argv暴露。这些规则均有对应测试覆盖test_directory.py。2.3 生命周期/请求钩子server_lifecycle.py与app_hooks.pyDirectoryHandler会检测server_lifecycle.py和app_hooks.py两个文件规则见 directory.py为两者同时存在直接抛出ValueError“Directory style apps can provide either server_lifecycle.py or app_hooks.py, not both.”对应测试见 test_directory_with_lifecycle_and_app_hooks_errors仅存在server_lifecycle.py装配ServerLifecycleHandler处理生命周期回调仅存在app_hooks.py同时装配ServerLifecycleHandler与ServerRequestHandler即一个文件同时提供生命周期回调和请求钩子都不存在装配两个 no-op 的Handler()占位。ServerLifecycleHandler见 server_lifecycle.py会从钩子模块的命名空间中提取并校验四个回调函数签名on_server_loaded(server_context)、on_server_unloaded(server_context)、on_session_created(session_context)、on_session_destroyed(session_context)。真实示例见 examples/server/app/stocks/app_hooks.py它利用on_server_loaded在服务器启动时一次性从 Yahoo Finance 拉取股票数据并挂到server_context上供各会话共享。2.4 主题、静态资源与模板theme.yaml存在则用bokeh.themes.Theme(filenamethemeyaml)加载directory.py。真实文件示例见 examples/server/app/clustering/theme.yaml其中按attrs:键为Plot、Grid、Title等模型统一设置默认视觉属性static/子目录存在则记录为应用静态路径directory.py最终通过Application.add()汇总到应用的static_path见 application.py。dash、faces等示例目录都带有static/目录见 examples/server/app/dash/statictemplates/index.html存在则用 Jinja2 的FileSystemLoader加载为页面模板directory.py。真实模板见 examples/server/app/crossfilter/templates/index.html它通过{% extends base %}继承 Bokeh 默认页面骨架并自定义样式块。三、modify_document文档装配的顺序与语义每次会话创建时Application.initialize_document()会调用 handler 的modify_document()。DirectoryHandler.modify_document()见 directory.py的执行顺序非常关键若server_lifecycle.py/app_hooks.py已失败failed直接返回不再装配文档若存在theme.yaml将doc.theme设置为该Theme实例。源码注释特别说明不复制 theme 对象前提是Theme类不可变无 setter若存在templates/index.html设置doc.template最后委托_main_handler.modify_document(doc)执行main.py/main.ipynb。3.1 主题、模板与脚本的优先级测试 test_directory_has_theme_file 揭示了一个重要语义主题先于脚本应用脚本内的显式赋值可以覆盖主题值而删除该显式赋值后又回落到主题值——测试中断言脚本里some.foo 57生效、del some_model.foo后回到主题值 14、doc.theme None后回到模型默认值 2。这源于 Bokeh Document 的属性解析层级显式设置 主题 默认值。模板同理测试 test_directory_with_template 验证了存在templates/index.html时doc.template是jinja2.Template实例不存在时回落为 Bokeh 内置的FILE模板bokeh.core.templates.FILE。3.2 会话内的模块加载时机modify_document每次会话都会执行main.py而包级__init__.py的CodeRunner只在服务器启动的on_server_loaded时运行一次directory.py。这意味着每个会话拿到的是main.py重新执行后构建的独立 Document而包级代码与server_lifecycle.py/app_hooks.py中的共享状态如stocks示例中缓存的行情数据在整个服务器生命周期内只初始化一次。四、生命周期回调与请求钩子4.1 四个生命周期时机DirectoryHandler将钩子事件透传给内部的ServerLifecycleHandlerdirectory.py回调触发时机建议用途on_server_loaded(server_context)服务器首次启动、任何会话建立之前一次性初始化加载共享数据、建立连接如stocks示例拉取行情on_server_unloaded(server_context)服务器干净退出停止 IOLoop 之前一次性清理。⚠️ 源码警告服务器常被信号直接杀死此回调实践中可能不执行见 handler.pyon_session_created(session_context)每个新会话创建时modify_document之前每会话初始化on_session_destroyed(session_context)会话销毁时每会话清理Handler基类对这四个方法提供空实现handler.py因此未定义某回调时不会有副作用测试 test_directory_with_server_lifecycle 验证了四个回调的返回值透传行为。4.2 process_request把 HTTP 请求数据注入会话当使用app_hooks.py时ServerRequestHandler接管process_request(request)directory.py对每个 HTTP 请求返回“可 JSON 序列化”的附加数据字典。这些数据最终汇入Application.process_request()的结果application.py供会话上下文使用。测试 test_directory_with_request_handler 展示了钩子返回request[headers]、handler 原样透传的用法。五、其他公开属性与状态5.1 错误状态error / error_detail / failedDirectoryHandler将main处理器与生命周期处理器的错误信息合并暴露directory.pyerror失败时的错误消息含文件名、行号、函数与代码行error_detail完整 traceback 或细节failed任一内部处理器失败即为True。错误记录逻辑在Handler.handle_exception()handler.py开发模式下这些错误会反馈到客户端以便调试。5.2 url_path 与 safe_to_forkurl_path()directory.py返回/ basename(目录名)即应用被挂载的默认 URL 路径处理器失败时返回Nonesafe_to_forkdirectory.py在main.py尚未执行时为True一旦执行过则为False——服务器据此判断是否还能安全地 fork 新 worker 进程Application.safe_to_fork会聚合所有 handler 的结果见 application.py。测试 test_safe_to_fork 验证了modify_document前后该标志的翻转。六、动手实践一个完整的目录式应用综合以上机制一个覆盖全部可选特性的最小应用目录如下可直接用bokeh serve myapp启动myapp/ ├── __init__.py # 可选启用包式相对导入 ├── main.py # 必需 ├── app_hooks.py # 可选生命周期 请求钩子与 server_lifecycle.py 互斥 ├── static/ │ └── css/custom.css # 可选应用静态资源 ├── theme.yaml # 可选自动应用的主题 └── templates/ └── index.html # 可选自定义页面模板main.py示例每个会话执行向curdoc()添加根模型import sys from bokeh.io import curdoc from bokeh.plotting import figure p figure(titlesys.argv[1] if len(sys.argv) 1 else Directory App) curdoc().add_root(p)app_hooks.py示例一次性共享数据 请求钩子参考 examples/server/app/stocks/app_hooks.pydef on_server_loaded(server_context): # 服务器启动时执行一次可把共享数据挂到 server_context setattr(server_context, shared, {init: True}) def process_request(request): # 把请求头等数据注入会话上下文须 JSON 可序列化 return {headers: request.get(headers, {})}theme.yaml示例参考 examples/server/app/clustering/theme.yamlattrs: Plot: width: 400 height: 400 background_fill_color: lightgreytemplates/index.html示例继承 Bokeh 基础模板参考 examples/server/app/crossfilter/templates/index.html{% extends base %} {% block title %}My Bokeh App{% endblock %} {% block preamble %} stylebody { font-family: sans-serif; }/style {% endblock %}部署与调试要点启动命令bokeh serve myapp应用将挂载在/myapp由url_path()决定也支持bokeh serve --show myapp直接打开浏览器调试钩子修改app_hooks.py或server_lifecycle.py需要重启服务器而main.py每次会话都会重新执行适合快速迭代冲突排查main.py与main.ipynb并存时按文档规则以main.py为准app_hooks.py与server_lifecycle.py并存会直接报ValueError务必只保留其一。七、结语DirectoryHandler把 Bokeh 服务端应用的“代码执行、生命周期、请求处理、主题、模板、静态资源”六件事统一收敛到一个目录约定中main.py/main.ipynb负责每会话的文档构建server_lifecycle.py/app_hooks.py负责服务级与会话级的钩子theme.yaml、templates/index.html、static/则由modify_document自动装配。理解 directory.py 的加载顺序与优先级语义配合 test_directory.py 中的行为验证你就能精确掌控目录式应用的初始化时机、状态共享边界与错误排查路径从而构建出结构清晰、可维护的 Bokeh Server 应用。【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考