docs: preserve API signatures and discover AutoModelVLLM

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-07 03:38:20 +00:00
parent 2d2d385545
commit f0728b758c
4 changed files with 206 additions and 21 deletions
+82 -20
View File
@@ -26,7 +26,10 @@ STYLESHEET = REPO_ROOT / "gh-pages-output" / "api-reference.css"
os.chdir(REPO_ROOT)
tree_structure = {
"funasr.auto": {"auto_model": "funasr/auto/auto_model.py"},
"funasr.auto": {
"auto_model": "funasr/auto/auto_model.py",
"auto_model_vllm": "funasr/auto/auto_model_vllm.py",
},
"funasr.register": {"register": "funasr/register.py"},
"funasr.models": {},
"funasr.frontends": {},
@@ -115,6 +118,43 @@ def get_source_lines(filepath, node):
def github_url(filepath, lineno):
return f"{REPO_URL}/blob/{BRANCH}/{filepath}#L{lineno}"
def callable_signature(node, source, bound=False, constructor=False):
"""Render source expressions without importing code or evaluating defaults."""
def expression(value):
return ast.get_source_segment(source, value).strip()
def argument(value, default=None, prefix=""):
result = prefix + value.arg
if value.annotation is not None:
result += ": " + expression(value.annotation)
if default is not None:
result += (" = " if value.annotation is not None else "=") + expression(default)
return result
args = node.args
positional = args.posonlyargs + args.args
defaults = [None] * (len(positional) - len(args.defaults)) + list(args.defaults)
static = any(isinstance(d, ast.Name) and d.id == "staticmethod"
for d in node.decorator_list)
start = 1 if bound and not static and positional else 0
parts = []
for index in range(start, len(positional)):
parts.append(argument(positional[index], defaults[index]))
if index + 1 == len(args.posonlyargs):
parts.append("/")
if args.vararg is not None:
parts.append(argument(args.vararg, prefix="*"))
elif args.kwonlyargs:
parts.append("*")
parts.extend(argument(value, default) for value, default
in zip(args.kwonlyargs, args.kw_defaults))
if args.kwarg is not None:
parts.append(argument(args.kwarg, prefix="**"))
signature = "(" + ", ".join(parts) + ")"
if node.returns is not None and not constructor:
signature += " -> " + expression(node.returns)
return signature
# Extract all data
all_data = {}
for top_level, sub_modules in tree_structure.items():
@@ -136,27 +176,30 @@ for top_level, sub_modules in tree_structure.items():
class_doc = ast.get_docstring(node) or ""
src, lineno, trunc = get_source_lines(filepath, node)
methods = []
constructor = None
for item in node.body:
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
if item.name == "__init__":
constructor = {
"signature": callable_signature(item, source, bound=True, constructor=True),
"doc": ast.get_docstring(item) or "",
"lineno": item.lineno,
}
if item.name.startswith("_"):
continue
mdoc = ast.get_docstring(item) or ""
args = [a.arg for a in item.args.args if a.arg != "self"]
if item.args.vararg: args.append("*" + item.args.vararg.arg)
if item.args.kwarg: args.append("**" + item.args.kwarg.arg)
msrc, mline, mtrunc = get_source_lines(filepath, item)
methods.append({"name": item.name, "args": args, "doc": mdoc,
methods.append({"name": item.name,
"signature": callable_signature(item, source, bound=True), "doc": mdoc,
"source": msrc, "lineno": mline, "truncated": mtrunc})
entries.append({"type": "class", "name": node.name, "doc": class_doc,
"methods": methods, "source": src, "lineno": lineno,
"methods": methods, "constructor": constructor, "source": src, "lineno": lineno,
"truncated": trunc, "filepath": filepath})
elif isinstance(node, ast.FunctionDef) and not node.name.startswith("_"):
fdoc = ast.get_docstring(node) or ""
args = [a.arg for a in node.args.args]
if node.args.vararg: args.append("*" + node.args.vararg.arg)
if node.args.kwarg: args.append("**" + node.args.kwarg.arg)
fsrc, fline, ftrunc = get_source_lines(filepath, node)
entries.append({"type": "function", "name": node.name, "args": args,
entries.append({"type": "function", "name": node.name,
"signature": callable_signature(node, source),
"doc": fdoc, "source": fsrc, "lineno": fline,
"truncated": ftrunc, "filepath": filepath})
if entries:
@@ -169,7 +212,20 @@ print(f"Extracted: {len(all_data)} modules, {total_entries} entries")
sidebar = ""
content = ""
overview_links = ""
# Allocate newly documented vLLM entries after legacy entries, even though their
# navigation belongs beside AutoModel. Existing numeric deep links stay stable.
new_module = "funasr.auto.auto_model_vllm"
anchor_modules = [key for key in all_data if key != new_module]
if new_module in all_data:
anchor_modules.append(new_module)
eid = 0
for key in anchor_modules:
for entry in all_data[key]:
eid += 1
entry["anchor"] = f"e{eid}"
for method in entry.get("methods", []):
eid += 1
method["anchor"] = f"e{eid}"
for group_index, top_level in enumerate(tree_structure):
sub_modules = tree_structure[top_level]
@@ -182,23 +238,30 @@ for group_index, top_level in enumerate(tree_structure):
module_id = f"{group_id}-module-{module_index}"
sidebar += f'<div class="l2"><button type="button" class="l2-title" aria-expanded="true" aria-controls="{module_id}"><span>{esc(sub_name)}</span><span class="arr" aria-hidden="true">▸</span></button><div class="l2-children" id="{module_id}">\n'
for entry in all_data[key]:
eid += 1
eid_str = f"e{eid}"
eid_str = entry["anchor"]
filepath = entry.get("filepath", "")
if entry["type"] == "class":
if entry["name"] in {"AutoModel", "RegisterTables"}:
if entry["name"] in {"AutoModel", "AutoModelVLLM", "RegisterTables"}:
overview_links += f'<a href="#{eid_str}" class="entry-link">{esc(entry["name"])} <span aria-hidden="true">→</span></a> '
sidebar += f'<a class="l3-item" href="#{eid_str}" data-target="{eid_str}"><span class="mb badge-c" aria-hidden="true">C</span><span>{esc(entry["name"])}</span></a>\n'
gh_link = github_url(filepath, entry["lineno"])
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-c">class</span><h2>{esc(entry["name"])}</h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
title = esc(entry["name"])
if entry["constructor"] is not None:
title = '<code>' + esc(entry["name"] + entry["constructor"]["signature"]) + '</code>'
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-c">class</span><h2>{title}</h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source code</summary><pre><code>{esc(entry["source"])}</code></pre>'
if entry["truncated"]:
content += f'<a href="{gh_link}" target="_blank" class="src-more">View full source on GitHub →</a>'
content += '</details>'
if entry["constructor"] is not None:
constructor = entry["constructor"]
constructor_url = github_url(filepath, constructor["lineno"])
content += f'<h3 class="mt">Constructor</h3><div class="mblk"><a href="{constructor_url}" class="gh-link">L{constructor["lineno"]}</a>'
content += format_doc(constructor["doc"]) + '</div>'
if entry["methods"]:
content += '<h3 class="mt">Methods</h3>'
for m in entry["methods"]:
sig = f'.{m["name"]}({", ".join(m["args"])})'
sig = f'.{m["name"]}{m["signature"]}'
mgh = github_url(filepath, m["lineno"])
content += f'<div class="mblk"><code>{esc(sig)}</code> <a href="{mgh}" target="_blank" class="gh-link">L{m["lineno"]}</a><div class="mdoc">{format_doc(m["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source</summary><pre><code>{esc(m["source"])}</code></pre>'
@@ -207,10 +270,9 @@ for group_index, top_level in enumerate(tree_structure):
content += '</details></div>'
content += '</div>\n'
for m in entry["methods"]:
eid += 1
mid = f"e{eid}"
mid = m["anchor"]
sidebar += f'<a class="l3-item l3m" href="#{mid}" data-target="{mid}"><span class="mb badge-m" aria-hidden="true">M</span><span>.{esc(m["name"])}()</span></a>\n'
sig = f'{entry["name"]}.{m["name"]}({", ".join(m["args"])})'
sig = f'{entry["name"]}.{m["name"]}{m["signature"]}'
mgh = github_url(filepath, m["lineno"])
content += f'<div class="api-detail" id="{mid}"><span class="dtag badge-m">method</span><h2><code>{esc(sig)}</code></h2><div class="dmod">{key}.{entry["name"]} · <a href="{mgh}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(m["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source code</summary><pre><code>{esc(m["source"])}</code></pre>'
@@ -218,10 +280,10 @@ for group_index, top_level in enumerate(tree_structure):
content += f'<a href="{mgh}" target="_blank" class="src-more">View full source on GitHub →</a>'
content += '</details></div>\n'
else:
args_str = ", ".join(entry.get("args", []))
signature = entry["name"] + entry["signature"]
gh_link = github_url(filepath, entry["lineno"])
sidebar += f'<a class="l3-item" href="#{eid_str}" data-target="{eid_str}"><span class="mb badge-f" aria-hidden="true">F</span><span>{esc(entry["name"])}</span></a>\n'
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-f">function</span><h2><code>{esc(entry["name"])}({esc(args_str)})</code></h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-f">function</span><h2><code>{esc(signature)}</code></h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source code</summary><pre><code>{esc(entry["source"])}</code></pre>'
if entry["truncated"]:
content += f'<a href="{gh_link}" target="_blank" class="src-more">View full source on GitHub →</a>'