深入解析 Bokeh 文档画廊系统:gallery_detail.rst 模板如何驱动示例页面自动生成

发布时间:2026/9/14 18:10:13
深入解析 Bokeh 文档画廊系统:gallery_detail.rst 模板如何驱动示例页面自动生成 深入解析 Bokeh 文档画廊系统gallery_detail.rst 模板如何驱动示例页面自动生成【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh导读Bokeh 官方文档中那张琳琅满目的示例画廊Gallery页面并不是人工逐个书写的而是由 Sphinx 扩展在文档构建阶段自动批量生成的。本文以 Bokeh 仓库中负责单个示例详情页的 Jinja2 模板 gallery_detail.rst 为核心完整讲解从gallery.json配置、bokeh-gallery指令扫描、模板渲染到bokeh-plot指令执行示例代码、输出交互图与源码块的全链路机制。读完本文你将掌握 Bokeh 画廊的底层工作原理并能在自己的 Sphinx 文档项目中复刻这套配置驱动 模板生成的示例文档流水线。一、gallery_detail.rst单个示例详情页的完整模板gallery_detail.rst位于 src/bokeh/sphinxext/_internal/_templates/gallery_detail.rst全文只有 13 行却承担着为画廊中每一个示例生成独立 RST 详情页的重任。先看它的完整内容:orphan: .. index:: single: examples; {{ filename }} {{ ref }} {{ filename }} {{ - * filename|length }} .. bokeh-plot:: {{ source_path }} :process-docstring: :source-position: below这是一个 Jinja2 模板由三个变量驱动逐行拆解如下行内容作用:orphan:声明该页面是孤儿文档即使没有被任何 toctree 引用Sphinx 也不会发出警告。画廊详情页是程序批量生成的不参与目录树因此必须加此标记.. index:: single: examples; {{ filename }}为 Sphinx 生成索引条目把filename示例名登记到examples索引分组下方便文档内联搜索{{ ref }}插入由生成器构造的 RST 锚点引用形如.. _example_markers_basic_scatters:供其他页面用:ref:交叉引用定位该示例{{ filename }}{{ - * filename\|length }}用示例文件名作为页面标题并用等长的-生成 RST 二级标题下划线.. bokeh-plot:: {{ source_path }}核心指令指向示例 Python 源文件的绝对路径由 Sphinx 扩展实际执行该脚本:process-docstring:布尔选项把示例文件顶部的模块 docstring 单独渲染为格式化说明块:source-position: below枚举选项将格式化后的源码块放在图表下方模板虽然简短但孤儿文档 索引登记 交叉引用锚点 可执行代码指令四个要素缺一不可构成了每个示例页的完整骨架。二、模板从何而来bokeh_gallery 扩展的生成流程gallery_detail.rst 本身不直接出现在文档源码树中而是由 Sphinx 扩展 bokeh_gallery.py 在config-inited阶段动态写出的。该扩展在文档的 conf.py 中通过bokeh.sphinxext._internal.bokeh_gallery注册。2.1 关键数据结构GalleryDetail生成器内部用 TypedDict 描述每个示例的元信息见 bokeh_gallery.pyclass GalleryDetail(TypedDict): name: str # 示例文件名不含 .py path: str # 相对仓库根的路径如 examples/basic/scatters/markers.py ref: str # RST 锚点如 .. _example_markers_basic_scatters: rst_file_path: str # 生成的目标文件相对路径如 basic/scatters/markers.rst2.2 核心函数 get_details扫描示例目录get_details(app)bokeh_gallery.py遍历conf.py中配置的bokeh_example_subdirs列表对每个子目录中的文件做三件事过滤跳过以_开头的文件、非.py文件以及在bokeh_sampledata_xref_skiplist中的示例构造锚点生成ref f.. _example_{name}_{subdir}:形式的交叉引用标签产出条目组装成GalleryDetail并汇总返回。以实际目录 examples/basic/scatters 为例扫描后会产生markers.rst、color_scatter.rst、elements.rst、image_url.rst等条目锚点分别为.. _example_markers_basic_scatters:等。2.3 config_inited_handler写文件与增量构建config_inited_handlerbokeh_gallery.py在 Sphinx 配置初始化后执行完整的生成逻辑检查画廊配置文件gallery.json是否存在缺失则抛SphinxError用ensuredir创建画廊目录docs/gallery与示例目录docs/examples含所有子目录用一个extras集合记录目录中已存在的文件用于构建后清理残留对每个GalleryDetail渲染模板并写入文件source_path abspath(join(app.srcdir, .., .., .., detail[path])) f.write(GALLERY_DETAIL.render(filenamedetail[name], source_pathsource_path, refdetail[ref]))注意这里source_path被换算成绝对路径相对文档源码目录向上三级回到仓库根这样bokeh-plot指令才能准确读取examples/...下的示例脚本。增量构建优化如果目标详情文件已存在且其 mtime 比gallery.json新则跳过重写见第 145 行getmtime判断构建结束后删除extras中剩余文件即本次不再生成的历史残留保持画廊目录干净。渲染时的模板对象来自 templates.py即GALLERY_DETAIL _env.get_template(gallery_detail.rst)三、Gallery 索引页与 gallery_detail 的联动画廊的入口页面 docs/bokeh/source/docs/gallery.rst 本身不含任何示例内容只通过bokeh-gallery指令声明要展示哪些目录下的示例.. tab-set:: :class: bk-gallery-tabs .. tab-item:: Basic plotting .. bokeh-gallery:: basic/scatters basic/lines basic/data basic/axes basic/bars basic/areas basic/layouts basic/annotations3.1 BokehGalleryDirective读取 gallery.jsonBokehGalleryDirectivebokeh_gallery.py运行时定位当前文档所在目录下的gallery.json即 docs/bokeh/source/docs/gallery.json解析指令 content 中列出的每个location如basic/scatters到 JSON 中查找对应的示例条目数组对每个条目构造缩略图选项url默认指向examples/.../*.html若条目含url字段则直接使用并新窗口打开、img、alt、title缺省用文件名 stem、desc用索引页模板gallery_page.rst渲染出整张缩略图网格。3.2 gallery_page.rst缩略图网格模板配套的索引模板 gallery_page.rst 生成如下 HTML 结构节选div classbk-gallery div classbk-thumbnail a href... img classgallery width300 height300 src../_images/{{ opt[img] }}.png srcset../_images/{{ opt[img] }}.png, ../_images/{{ opt[img] }}2x.png 2x alt{{ opt[alt] }} /a div classbk-thumbnail-title{{ opt[title] }}/div div classbk-thumbnail-description{{ opt[desc] }}/div /div /div可见它支持1x/2x 高清缩略图srcset、可选的标题与描述说明。而server/app这类远程 Demo 条目见 gallery.json则带url与desc字段点击后跳转到demo.bokeh.org上的在线演示——这正是BokehGalleryDirective中url in detail分支处理的场景。四、bokeh-plot 指令示例页的执行引擎gallery_detail.rst 中真正跑代码的是.. bokeh-plot::指令其实现位于 bokeh_plot.py。该指令同时支持两种用法用法一指向外部示例文件画廊页采用此方式.. bokeh-plot:: path/to/plot.py用法二内联代码.. bokeh-plot:: from bokeh.plotting import figure, output_file, show output_file(example.html) x [1, 2, 3, 4, 5] y [6, 7, 6, 4, 5] p figure(titleexample, width300, height300) p.line(x, y, line_width2) p.scatter(x, y, size10, fill_colorwhite) show(p)两种方式不能混用同时给出参数与 content 会抛出SphinxError见 bokeh_plot.py。另外 Bokeh 文档内部约定了一个__REPO__/前缀路径以它开头时会被解析为仓库根目录下的相对路径便于文档源码树内引用。4.1 指令选项option_specBokehPlotDirective在 bokeh_plot.py 中定义了完整的选项规格选项取值默认说明:process-docstring:布尔flag无是否将示例文件的模块 docstring 单独渲染为格式化说明块:source-position:above/below/nonebelow格式化源码块放在图表上方、下方还是不显示:linenos:布尔flag无源码块是否显示行号gallery_detail.rst 固定使用:process-docstring:与:source-position: below即说明文字在上、交互图居中、源码在下的经典示例页布局。process_code_blockbokeh_plot.py还负责把 docstring 从源码中剥离_remove_module_docstring避免展示源码时重复出现模块说明。4.2 执行流水线从源码到交互图run()方法的完整调用链bokeh_plot.pyprocess_args_or_content()—— 确定源码与路径来源process_source()—— 依次执行_evaluate_source()调用ExampleHandler运行示例代码产生一个 BokehDocument并要求文档根节点恰好一个len(d.roots) ! 1时报错读取_sphinx_height_hint()获取图表高度提示调用autoload_static(root, RESOURCES, js_filename)生成独立的 JS 文件与script标签构造autoload_script节点将script_tag直接嵌入 HTML必要时用height_hint包一层div styleheight:...px;process_sampledata()正则扫描源码中的sampledata引用登记到all_sampledata_xrefs与all_gallery_overview供后续采样数据交叉引用生成见 bokeh_plot.py返回target intro above autoload below节点序列即锚点 → docstring 说明 → 源码块可选上方→ 交互图 → 源码块可选下方。最终页面结构因此是交叉引用锚点 → 模块 docstring 说明 → 可交互 Bokeh 图通过 autoload 脚本懒加载 JS→ 缩进格式化的 Python 源码块。4.3 ExampleHandler如何安全执行示例脚本示例脚本包含show(p)、output_file(...)等 I/O 调用直接运行会弹浏览器、写文件因此 example_handler.py 提供了ExampleHandler——一个精简版 Bokeh application handler继承Handler内部用CodeRunner编译执行源码记录error/error_detail/docMonkeypatch 策略见_monkeypatchexample_handler.pyoutput_notebook、output_file、reset_output被替换为空操作_passshow、save被替换为_add_root即把对象直接加入当前curdoc()bokeh.document.Document被替换为返回当前文档的_curdoc由于这些函数从bokeh.io传递导入到bokeh.plotting两个模块都会被同时打补丁执行完成后在finally中恢复原函数并还原curdoc确保文档构建环境不被污染。这就是画廊示例页能静默产出图表而不触发任何浏览器或文件输出的原因。五、实际配置conf.py 中的画廊开关Bokeh 文档构建在 conf.py 中配置了整套画廊参数extensions [ ... bokeh.sphinxext.bokeh_plot, bokeh.sphinxext._internal.bokeh_gallery, ... ] bokeh_example_subdirs [ advanced/extensions, basic/annotations, basic/areas, basic/axes, basic/bars, basic/data, basic/layouts, basic/lines, basic/scatters, styling/visuals, styling/plots, styling/mathtext, styling/themes, interaction/js_callbacks, interaction/legends, interaction/linking, interaction/tools, interaction/tooltips, interaction/widgets, models, plotting, output/webgl, topics/categorical, topics/hierarchical, topics/contour, topics/geo, topics/graph, topics/hex, topics/images, topics/pie, topics/stats, topics/timeseries, ] bokeh_missing_google_api_key_ok False bokeh_sampledata_xref_skiplist [ examples/basic/data/ajax_source.py, examples/basic/data/server_sent_events_source.py, examples/basic/layouts/custom_layout.py, examples/styling/dom/css_classes.py, examples/models/widgets.py, ]各配置项含义注册于 bokeh_gallery.py 的setup与 bokeh_plot.py配置项默认值作用bokeh_gallery_dirdocs/gallery画廊详情页的输出目录bokeh_examples_dirdocs/examples生成示例页的目录bokeh_example_subdirs[]需要扫描生成画廊条目的示例子目录列表bokeh_sampledata_xref_skiplist[]跳过采样数据交叉引用的示例路径黑名单bokeh_missing_google_api_key_okTrue见 bokeh_plot.py缺少GOOGLE_API_KEY时是否允许继续构建Bokeh 自身文档将其设为False强制校验若gallery.json缺失config_inited_handler会直接报错可见该文件是画廊系统的配置中枢。六、把新示例加入画廊端到端步骤结合以上源码往画廊中添加一个新示例的完整流程为写示例脚本在 examples 下相应子目录创建my_plot.py文件顶部用模块 docstring 书写说明文字会作为详情页的说明块脚本末尾调用show(p)登记目录确保该子目录如basic/scatters出现在 conf.py 的bokeh_example_subdirs中配置缩略图信息在 gallery.json 对应location数组中追加条目{ name: my_plot.py, alt: Thumbnail link to the examples/basic/scatters/my_plot.py example ... }title、desc、url均为可选字段其中url存在时会作为外部链接新窗口打开 4.在画廊索引页声明在 gallery.rst 对应tab-item的.. bokeh-gallery::下追加basic/scatters若已声明则跳过 5.重新构建文档config_inited_handler扫描目录 → 渲染gallery_detail.rst生成docs/examples/basic/scatters/my_plot.rst→bokeh-plot执行示例并输出交互图与源码块若该文件已存在且较新则跳过增量构建。七、构建性能与并行安全画廊系统为文档构建做了两方面性能考量增量写入详情文件比gallery.json新则跳过重写避免每次全量重建耗时统计与预警BokehPlotDirective记录每个示例的total/evaluate/serialize/write四段耗时_PlotTiming见 bokeh_plot.py在build-finished阶段汇总输出总耗时并用nlargest(5, ...)列出最慢的 5 个示例便于定位拖慢文档构建的脚本。此外两个扩展的setup都返回PARALLEL_SAFE明确声明支持 Sphinx 并行构建同时通过env_merge_info正确合并并行子进程的bokeh_plot_files与耗时数据bokeh_plot.py保证并行模式下生成的 JS 资源与统计信息不丢失。八、总结一套可复用的配置驱动示例文档范式从 gallery_detail.rst 这个 13 行模板出发可以看到 Bokeh 文档团队搭建的完整示例文档流水线数据驱动gallery.json定义画廊内容与元数据conf.py 定义扫描范围模板驱动gallery_detail.rst负责单个示例页gallery_page.rst 负责索引缩略图代码驱动bokeh_gallery.py 批量生成页面bokeh_plot.py 与 example_handler.py 负责安全执行示例并产出可交互图表。这套模式的核心价值在于新增一个示例只需写脚本 改一行 JSON页面、锚点、索引、缩略图全部自动生成且天然支持增量构建与并行构建。对于任何希望以示例即文档方式维护技术文档的 Sphinx 项目Bokeh 这套从模板到指令再到执行器的实现都是一份可直接借鉴的完整范本。相关测试与构建入口可进一步参考 tests/codebase 与 docs/bokeh/Makefile 深入了解。【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考