Ray 文档工程实践:解析 autosummary 的 class_v2.rst 模板与 API 分组生成机制
2026/9/19 21:51:36 网站建设 项目流程

Ray 文档工程实践:解析 autosummary 的 class_v2.rst 模板与 API 分组生成机制

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

导读

doc/source/_templates/autosummary/class_v2.rst是 Ray 官方文档(Sphinx + autosummary 体系)中用于渲染「类(class)级 API 参考页」的 Jinja 模板,负责把ray.data.DatasetDataIterator等类的构造函数、方法列表按「API 分组」自动组织成多段 autosummary 目录树。本文以该模板为核心,逐行拆解其 Jinja 语法与三个关键过滤器(has_public_constructorget_api_groupsselect_api_group)的仓库实现,并给出在 Ray 源码中如何通过@PublicAPI(api_group=...)标注来控制文档分组的实战方法。读完本文,你将能理解 Ray 的 API 文档如何从装饰器元数据一路驱动到最终渲染页面,也能自己为 Ray 的 API 参考页定制或复刻这套模板。

一、模板在文档体系中的定位

Ray 的 API 参考文档大量使用 Sphinx autosummary 的「模板化 stub 生成」机制:先在一个autosummary指令中列出类名,Sphinx 按:template:指定的模板为每个类生成一个.rststub 文件,再由手写的 API 页面通过.. include::引入。

在 Ray 仓库中:

  • 模板目录为 doc/source/_templates/autosummary,其中包含 8 个类模板:base.rstclass.rstclass_v2.rstclass_without_autosummary.rstclass_without_autosummary_noindex.rstclass_without_autosummary_noinheritance.rstclass_without_init_args.rst,以及 2 个 Pydantic 模型模板autopydantic.rstautopydantic_show_json.rst
  • class_v2.rst目前唯一被ray.data的 API 文档引用,见 doc/source/data/api/_autogen.rst 中的:template: autosummary/class_v2.rst指令,被渲染的类包括DataIteratorDatasetSchemastats.DatasetSummarygrouped_data.GroupedDataaggregate.AggregateFnaggregate.AggregateFnV2
  • 生成的 stub 由 doc/source/data/api/dataset.rst 等手写页面通过.. include:: ray.data.Dataset.rst引入,最终拼成完整的「Dataset API」页面。

class_v2.rst最接近的是 class.rst:后者同样用autosummary列出方法和属性,但不做任何分组;class_v2.rst的差异在于引入了「API 分组」这一维度——这是本模板的核心设计。

二、逐行拆解 class_v2.rst 模板

完整模板源码位于 doc/source/_templates/autosummary/class_v2.rst(全文 29 行),按功能可分为四段。

2.1 设置模块上下文

.. currentmodule:: {{ module }}

{{ module }}是 autosummary 注入的 Jinja 变量,值为被渲染类所属模块的完整限定名(如ray.data)。该指令让后续所有autoclassautosummary条目都在该模块命名空间下解析,条目中写Dataset.map即可正确指向ray.data.Dataset.map

2.2 条件渲染构造函数(公开 API 才渲染)

{% if name | has_public_constructor(module) %} {{ name }} {{ '-' * name | length }} .. autoclass:: {{ objname }} {% endif %}
  • name:类名(如Dataset);objname:类的完整限定名(如ray.data.Dataset)。
  • has_public_constructor(module)是 Ray 注册到 Jinja 的自定义过滤器,实现在 doc/source/api_autogen.py 第 60-62 行:
def has_public_constructor(class_name, module_name): cls = getattr(import_module(module_name), class_name) return _is_public_api(cls)

它动态导入类所在模块、取出类对象,判断该类是否带@PublicAPI标注(_is_public_api检查obj._annotated_type.value == "PublicAPI",见 python/ray/util/annotations.py 第 88-92 行)。只有公开 API 类才会在页面上渲染.. autoclass::块并展示其构造函数签名与 docstring。

注意这里的标题行使用了下划线标题语法:{{ name }}生成标题文本,{{ '-' * name | length }}生成等长的-下划线,标题文本将作为 stub 文件的 H1。

2.3 按 API 分组渲染方法列表(核心逻辑)

{% block methods %} {% if methods %} {% set api_groups = methods | get_api_groups(name, module) %} {% for api_group in api_groups %} {% if api_groups | length > 1 %} {{ api_group }} {{ '-' * api_group | length }} {% endif %} .. autosummary:: :nosignatures: :toctree: doc {% for method in methods | select_api_group(name, module, api_group) %} {{ name }}.{{ method }} {%- endfor %} {% endfor %} {% endif %} {% endblock %}

执行流程:

  1. methods是 autosummary 收集到的该类所有公共方法名列表;
  2. get_api_groups(name, module)遍历这些方法,收集它们被标注的 API 分组集合,返回去重排序后的分组名列表(实现在 doc/source/api_autogen.py 第 65-75 行):
    def get_api_groups(method_names, class_name, module_name): api_groups = set() cls = getattr(import_module(module_name), class_name) for method_name in method_names: method = getattr(cls, method_name) if _is_public_api(method): api_groups.add( safe_getattr(method, "_annotated_api_group", DEFAULT_API_GROUP) ) return sorted(api_groups)

    只有被@PublicAPI标注的方法才会被纳入分组统计,未标注的方法不产生分组;未显式指定api_group的方法归入DEFAULT_API_GROUP,即"Others"(doc/source/api_autogen.py 第 31 行);

  3. 外层for api_group in api_groups遍历每个分组,为每个分组生成一个小节标题(同样用-下划线,仅当分组数 > 1 时才显示分组标题,避免单组时出现冗余标题);
  4. 每个分组内嵌一个.. autosummary::指令,select_api_group(name, module, api_group)过滤器挑选出属于该分组且是公开 API 的方法(实现在 doc/source/api_autogen.py 第 78-85 行),逐条生成Dataset.map形式的条目;
  5. :nosignatures:使方法条目不展示签名;:toctree: doc让 Sphinx 为每个方法在doc/子目录下生成独立 stub 页。

2.4 过滤器注册与 Jekyll 上下文

模板依赖的三个过滤器has_public_constructorget_api_groupsselect_api_group(以及class.rst使用的filter_out_undoc_class_members)在 doc/source/api_autogen.py 第 102-105 行统一注册进jinja2.filters.FILTERS

FILTERS["get_api_groups"] = get_api_groups FILTERS["select_api_group"] = select_api_group FILTERS["has_public_constructor"] = has_public_constructor

conf.py在导入api_autogen时即完成注册(见 doc/source/conf.py 第 29-31 行),因此模板渲染与独立 stub 生成两条路径(见下文第五节)使用同一套过滤器实现,保证行为一致。

三、API 分组元数据从哪来:@PublicAPI 装饰器

分组信息并非模板凭空创造,而是来自 Ray 源码中每个公开方法上的@PublicAPI装饰器标注,实现在 python/ray/util/annotations.py。

3.1 装饰器签名与默认值

PublicAPI支持两种用法(python/ray/util/annotations.py 第 33-94 行):

@PublicAPI # 裸装饰器,等价于 stability="stable", api_group="Others" @PublicAPI(stability="beta", api_group="Basic Transformations")
  • stability"stable"(跨 minor 版本向后兼容)、"beta"(早期用户可用但可能变更)、"alpha"(允许破坏性变更,供进阶用户使用);
  • api_group:仅用于文档渲染,同组的 API 在文档页中聚合在一起,默认"Others"

_mark_annotated将标注写入对象属性(python/ray/util/annotations.py 第 327-334 行):

obj._annotated = obj.__name__ obj._annotated_type = type # AnnotationType.PUBLIC_API obj._annotated_api_group = api_group

这正是get_api_groupsselect_api_group读取的_annotated_api_group的来源。

3.2 ray.data 的真实分组定义

在 python/ray/data/dataset.py 第 203-211 行定义了 9 个分组常量:

常量分组名
BT_API_GROUPBasic Transformations
SSR_API_GROUPSorting, Shuffling and Repartitioning
SMJ_API_GROUPSplitting, Merging, Joining Datasets
GGA_API_GROUPGrouped and Global Aggregations
CD_API_GROUPConsuming Data
IOC_API_GROUPI/O and Conversion
IM_API_GROUPInspecting Metadata
E_API_GROUPExecution
EXPRESSION_API_GROUPExpressions

方法上的使用示例(python/ray/data/dataset.py 第 425 行等):

@PublicAPI(api_group=BT_API_GROUP) def map(self, fn, ...): ... @PublicAPI(api_group=EXPRESSION_API_GROUP, stability="alpha") def filter(self, fn, ...): ...

由此,ray.data.Dataset的 API 参考页会按「Basic Transformations」「Sorting, Shuffling and Repartitioning」等分组依次渲染方法小节——这正是class_v2.rst分组循环的输入。

四、与其他类模板的对比与选型

Ray 提供多套类模板以满足不同场景,理解它们的差异有助于选型:

模板文件构造函数展示方法/属性条目特点
class_v2.rst仅公开 API(has_public_constructor过滤)api_group分组,:toctree: doc生成方法子页带 API 分组维度的完整渲染
class.rstautoclass+:show-inheritance:Methods / Attributes 两个 rubric,不做分组;filter_out_undoc_class_members过滤无 docstring 成员经典默认模板
class_without_autosummary.rstautoclass+:members: :show-inheritance:不生成 autosummary 条目,全部内联展开页面尽量自包含
class_without_autosummary_noindex.rst同上 +:noindex:同上避免索引/签名冲突,适合多页复用
class_without_autosummary_noinheritance.rstautoclass+:members:(无继承)同上不显示继承成员
class_without_init_args.rstautoclass:: {{ objname }}()内联:members:隐藏构造函数参数

在 ray.data 模块内即可看到选型实例:doc/source/data/api/checkpoint.rst 与 doc/source/data/api/execution_options.rst 使用class_without_autosummary.rst,doc/source/data/api/llm.rst 使用class_without_autosummary_noinheritance.rst,而 doc/source/data/api/_autogen.rst 中的核心数据类(Dataset 等)使用class_v2.rst

共同约定:所有模板开头都用{{ fullname.split('.')[-1] | escape | underline }}生成短标签标题(如Dataset而非ray.data.Dataset),避免 API 侧边栏与页面 H1 冗长,参见 base.rst 中的注释说明。

五、底层机制:模板如何被驱动生成 stub

5.1 两条生成路径

class_v2.rst通过两条路径被使用,二者共用generate_api_stubs(doc/source/api_autogen.py 第 129-172 行):

  1. 完整文档构建:Sphinx 在builder-inited事件钩子中调用_autogen_apis(doc/source/conf.py 第 697-701、763 行),生成 stub 后再进行正常渲染;
  2. 独立 stub 生成:直接运行python doc/source/api_autogen.py,通过_build_standalone_app(doc/source/api_autogen.py 第 108-126 行)构建一个DummyApplication,把_templates目录加入模板搜索路径(app.config.templates_path.append(os.path.join(srcdir, "_templates"))),并挂载AUTOSUMMARY_FILENAME_MAP(第 41-48 行)——该映射把ray.serve.deployment(装饰器)重命名为ray.serve.deployment_decorator,避免与ray.serve.Deployment(类)在大小写不敏感文件系统上发生文件名冲突。

这套设计源于 API 文档一致性检查(ci/ray_ci/doc):它需要读取生成的 stub.rst文件来核对手写 API 页面与 autosummary 源是否一致。历史上 stub 只是完整make -C doc/ html的副作用,现在独立生成后,检查无需完整构建 Sphinx 即可运行(见 doc/source/api_autogen.py 第 1-21 行模块说明)。

5.2 失败策略:拒绝静默失败

generate_api_stubs在生成结果为空时抛出RuntimeError(第 166-171 行),取代了旧版conf.py中把任何失败降级为 warning 的try/except——autosummary 源或模板一旦损坏,构建会响亮地失败,而不是生成空 fixture 让一致性检查误以为「无事可做」。这也是保证class_v2.rst模板正确性的工程护栏。

5.3 可选依赖的 mock 处理

某些类的模块会急切导入可选三方库(如ray.data.llm引入vllmsglang)。独立 stub 生成路径只 mock「确实缺失」的模块(doc/source/api_mock_imports.py 第 82-104 行),而不是 conf.py 的全量 mock 列表——因为对一个已安装的库(如 pandas)做 mock 反而会让裸import ray.data失败。mock 上下文同时包裹过滤器中的动态import_module()调用(第 163-164 行),确保has_public_constructor等过滤器在 mock 环境下也能解析类与方法。此外BUILD_ONLY_MOCK_MODULES(第 75-79 行)专门处理ray._raylet等仅在源码构建时缺失的编译模块。

六、实操:为 Ray 文档添加带分组的类 API 页

参考 ray.data 的既有做法,可总结出在 Ray 仓库中为某个类启用class_v2.rst渲染的完整流程:

  1. 标注公开 API:在类上使用@PublicAPI(保证构造函数被has_public_constructor放行),在需要分组的方法上使用@PublicAPI(api_group="<Group Name>");分组的英文名称建议复用 python/ray/data/dataset.py 第 203-211 行的常量命名风格。
  2. 加入 autosummary 源:仿照 doc/source/data/api/_autogen.rst,新建或修改AUTOGEN_FILES中的.rst文件,写autosummary指令并指定:template: autosummary/class_v2.rst,把类名(含子模块前缀,如grouped_data.GroupedData)逐条列出。AUTOGEN_FILES是唯一的源清单,与conf.py共享(doc/source/api_autogen.py 第 37-39 行)。
  3. 生成并引入 stub:运行python doc/source/api_autogen.py生成ray.data.<Class>.rst等 stub 文件,然后由手写 API 页面通过.. include::引入(参考 doc/source/data/api/dataset.rst 第 6 行)。
  4. 校验:stub 生成的失败现在会直接使构建或一致性检查报错,可用于快速发现模板语法、过滤器的 import 或标注缺失问题。

需要特别注意的是,class_v2.rst只对「公开 API」方法生成条目——_is_public_api要求方法带_annotated_type == PublicAPI标注,未加@PublicAPI的方法即使写在 docstring 里也不会出现在 API 参考页上,因此分组标注与公开性标注缺一不可。

七、总结

class_v2.rst是 Ray 大规模 API 文档工程的缩影:它以 29 行的 Jinja 模板,配合@PublicAPI装饰器写入的元数据、api_autogen.py注册的四个自定义过滤器、以及独立的 stub 生成与失败检测机制,把「类方法按语义分组」的需求优雅地落到了文档渲染层面。理解这套机制,不仅能读懂 ray.data 等模块 API 页面的生成原理,也能为复刻或扩展 Ray 的文档体系提供可直接借鉴的模板化方案。相关核心文件:模板 class_v2.rst、过滤器与生成器 api_autogen.py、标注实现 annotations.py、分组常量 dataset.py(第 203-211 行)、autosummary 源 data/api/_autogen.rst。

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询