You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Sphinx扩展中获取的Toctree对象过时,热更新无法同步最新内容的问题求助

Sphinx扩展中获取的Toctree对象过时,热更新无法同步最新内容的问题求助

我写了一个Sphinx扩展,用来把文档的全局toctree替换成指定名称的toctree。现在遇到一个棘手的问题:设置了自定义toctree的页面标记是正常的,但当我在另一个.rst文件里更新实际的toctree对象时,render_named_toctree函数里获取到的toc_tree节点始终是旧版本,热更新没办法同步最新内容。

核心的render_named_toctree函数代码如下:

def render_named_toctree(
        app: Sphinx, docname: str, tocname: str, collapse: bool = True, **kwargs: Any
    ) -> str:

    builder = app.builder
    maxdepth = kwargs.get('maxdepth',0)
    includehidden = kwargs.get("includehidden", False)
    titles_only = kwargs.get("titles_only", False)
    toc_env = app.env.master_doctree.ids[tocname]
    toc_tree = toc_env.children[0]

    # 我也尝试过用遍历的方式查找,结果还是一样
    # for toctree_node in app.env.master_doctree.findall(addnodes.toctree):
    #     if tocname in toctree_node.parent.attributes["ids"]:
    #         toc_tree = toctree_node

    resolved = _resolve_toctree(app.env, docname, builder, toc_tree, prune=True, collapse=collapse, maxdepth=int(maxdepth), includehidden=includehidden, titles_only=titles_only)

    if not resolved:
        resolved = None
    return builder.render_partial(resolved)["fragment"]

我搭了一个测试文档的小项目,当前的目录结构(忽略build文件)是这样的:

├───src
│   └───sphinx_replace_htmlpage_toctree
│           __init__.py
│
└───test
    │   conf.py
    │   index.rst
    └───tutorial
            foo.rst
            index.rst

测试用的文档内容很简单,tutorial/index.rst和tutorial/foo.rst只有标题,主要看index.rst里的两个toctree:

Docs
=====


Test document to see if the toctree changes

.. toctree::
    :name: maintoc

    self
    tutorial/index

.. toctree::
    :name: tutorialtree

    
    tutorial/index
    tutorial/foo

conf.py里的配置用来给tutorial/index.rst指定自定义toctree,内容如下:

project = 'replace toctree tester'
copyright = '2024, Foo'
author = 'Bar'
release = '0.0.1'
html_theme = 'alabaster'

extensions = [
            "sphinx_replace_htmlpage_toctree"
            ]

replace_global_tocs = {
    "tutorial/*": "tutorialtree",
}

我用这个命令启动热更新测试:

sphinx-autobuild test test/build/html -a

现在的问题是:当我修改index.rst里的tutorialtree(比如移除某个条目),网页上的导航树完全没变化,查看到render_named_toctree里拿到的还是旧的toctree内容。只有加上-E flag重新全量构建才会生效,但这样构建时间太长了,按理应该不用全量构建就能同步的。

我怀疑是没正确告诉Sphinx某个文档已更新,导致它用了旧的pickled doctree,而不是新的。我尝试过在多个Sphinx事件里标记index和tutorial/index为需要更新,比如连接了这些事件:

  • env-get-updated
  • env-updated
  • env-before-read-docs
  • env-get-outdated

但不管用,获取到的toctree还是旧的。

下面是扩展__init__.py的完整代码,供参考:

from typing import *

import re

from docutils import nodes

from sphinx import addnodes
from sphinx.util import logging
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
from sphinx.util.docutils import SphinxDirective
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.environment.adapters.toctree import _resolve_toctree
from sphinx.environment import BuildEnvironment

__version__ = "0.0.1"
_LOGGER = logging.getLogger(__name__)

EXTENSION_NAME = "replace-htmlpage-toctree"
TOCLIST_ATTR = "replace_docname_toctree"

def update_toctree_context(app: Sphinx, pagename, templatename, context, doctree):
    """Updates the toctree key in the context object if the pagename has a custom toc
    """

    if pagename in app.env.replace_docname_toctree:
        context["toctree"] =  lambda **kwargs: render_named_toctree(app, pagename, app.env.replace_docname_toctree[pagename], **kwargs)
    return

def render_named_toctree(
        app: Sphinx, docname: str, tocname: str, collapse: bool = True, **kwargs: Any
    ) -> str:

    builder = app.builder
    maxdepth = kwargs.get('maxdepth',0)
    includehidden = kwargs.get("includehidden", False)
    titles_only = kwargs.get("titles_only", False)
    toc_env = app.env.master_doctree.ids[tocname]
    toc_tree = toc_env.children[0]

    # for toctree_node in app.env.master_doctree.findall(addnodes.toctree):
    #     if tocname in toctree_node.parent.attributes["ids"]:
    #         toc_tree = toctree_node

    resolved = _resolve_toctree(app.env, docname, builder, toc_tree, prune=True, collapse=collapse, maxdepth=int(maxdepth), includehidden=includehidden, titles_only=titles_only)

    if not resolved:
        resolved = None
    return builder.render_partial(resolved)["fragment"]


def isold(app, env, added, changed, removed):
    return {"tutorial/index", "index"}

def process_namedtocs(app: Sphinx, env: BuildEnvironment):

    app.env.replace_docname_toctree = {}
    for doc, treename in app.config.replace_global_tocs.items():
        env_ids = app.env.master_doctree.ids
        if treename not in env_ids:
            _LOGGER.error(f"No toctree with name {treename} was found")
            continue
        
        named_toc = env_ids[treename]
        if "toctree-wrapper" not in named_toc.attributes["classes"]:
            _LOGGER.error(f"{treename} is not a toctree")
            continue
        
        if "*" in doc:
            expr = re.compile(doc)
            for found_doc in env.found_docs:
                if expr.match(found_doc):
                    app.env.replace_docname_toctree[found_doc] = treename
        elif doc in env.found_docs:
            app.env.replace_docname_toctree[doc] = treename
        else:
            _LOGGER.error(f"{EXTENSION_NAME}: {doc} was not found and is not an expression.")
    
    return ["tutorial/index", "index"]

def before_read(app: Sphinx, env: BuildEnvironment, docnames: list):

    if "tutorial/index" not in docnames: docnames.append("tutorial/index")
    if "index" not in docnames: docnames.append("index")

    return

def return_updated(app, env):
    return ["tutorial/index", "index"]

def setup(app: Sphinx) -> ExtensionMetadata:

    app.add_config_value('replace_global_tocs', {}, 'env')
    app.connect('html-page-context', update_toctree_context)

    app.connect('env-get-updated', process_namedtocs)
    app.connect("env-updated", return_updated)
    app.connect("env-get-outdated", isold)

    return ExtensionMetadata(version=__version__, parallel_read_safe=False, parallel_write_safe=False)

备注:内容来源于stack exchange,提问作者Slalamander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 12:34:34