Skip to content

Commit d90f709

Browse files
committed
docs: compact every module that re-exports from outside its subtree
The compact treatment was gated on package indexes, but plain modules re-export across boundaries too: mcp.server.caching re-exports CACHEABLE_METHODS and CacheableMethod from mcp_types.methods and rendered full duplicates of their canonical documentation. Run every documented module through the pass; six module pages flip. Three mechanisms tightened while widening: compact pages keep the module's own public docstringed members that are not in __all__ (a plain stub renders those, and flipping must drop nothing); links target the deepest alias hop whose module is documented, so they point at the defining page by construction instead of relying on mkdocstrings registering alias identifiers; and the redundant self-root preload is omitted (rendering a stub loads its own package). Statically invisible __all__ names and shadowed module lookups fail with clean errors instead of tracebacks.
1 parent 57f9e0a commit d90f709

1 file changed

Lines changed: 72 additions & 36 deletions

File tree

scripts/docs/gen_ref_pages.py

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -60,49 +60,79 @@ def to_nav(self, title: str) -> NavItem:
6060
return {title: items}
6161

6262

63-
def _compact_index(package: griffe.Module, documented: set[str]) -> str | None:
64-
"""Build a compact index body for a package that re-exports from outside its own subtree.
63+
def _compact_index(module: griffe.Module, documented: set[str]) -> str | None:
64+
"""Build a compact page body for a module that re-exports from outside its own subtree.
6565
6666
mkdocstrings renders a re-export whose canonical documentation lives on
6767
another page as a full duplicate of it: deterministically for aliases
6868
within one top-level package (`mcp.client.auth` re-exporting from
6969
`mcp.shared.auth`), and order-dependently across top-level packages
7070
(`from mcp_types import y` + `__all__` renders the duplicate only when
7171
the other package happens to be loaded already, and silently omits the
72-
member when it isn't). Packages whose exports all live in their own
73-
subtree (`mcp_types` re-exporting its private `._types` module) are
74-
unaffected and keep the plain `::: package` stub (return `None`): their
75-
index is itself the canonical rendering.
72+
member when it isn't). Modules whose exports all live in their own
73+
subtree (`mcp_types` re-exporting its private `._types` module, or a
74+
module whose `__all__` lists only its own definitions) are unaffected
75+
and keep the plain `::: module` stub (return `None`): their page is
76+
itself the canonical rendering.
7677
77-
For an affected package, pin the semantics instead of inheriting the
78+
For an affected module, pin the semantics instead of inheriting the
7879
accident: every export whose canonical page exists elsewhere under the API
7980
reference becomes a link to it, and only exports documented nowhere else
8081
(re-exports from private modules) keep their full body here, via an
8182
explicit `members:` list.
8283
"""
83-
prefix = f"{package.path}."
84-
exports = {str(export): package.members[str(export)] for export in package.exports or ()}
84+
prefix = f"{module.path}."
85+
exports: dict[str, griffe.Object | griffe.Alias] = {}
86+
for export in module.exports or ():
87+
name = str(export)
88+
# Listed exports must be statically documentable: a name provided at
89+
# runtime (module `__getattr__`) is a docs error by policy, not a skip.
90+
if (member := module.members.get(name)) is None:
91+
msg = f"gen_ref_pages: export {module.path}.{name} is not statically visible"
92+
raise SystemExit(msg)
93+
exports[name] = member
8594
if not any(member.is_alias and not member.target_path.startswith(prefix) for member in exports.values()):
8695
return None
8796

97+
# A plain stub also renders the module's own public members that are not
98+
# in `__all__` (`show_if_no_docstring: false` hides the docstring-less
99+
# ones); keep them, so flipping a page to compact drops nothing.
100+
public = dict(exports)
101+
for name, member in module.members.items():
102+
if (
103+
name not in public
104+
and not name.startswith("_")
105+
and not member.is_alias
106+
and member.kind is not griffe.Kind.MODULE
107+
and member.has_docstrings
108+
):
109+
public[name] = member
110+
88111
inline: list[str] = []
89112
sections: dict[str, list[str]] = {}
90-
for name in sorted(exports, key=str.lower):
91-
member = exports[name]
92-
# A target only gets an anchor on its canonical page if it is rendered
93-
# there, which the default `show_if_no_docstring: false` limits to
94-
# objects with docstrings.
95-
if member.is_alias and member.target_path.rpartition(".")[0] in documented and member.has_docstrings:
96-
link_target = member.target_path
97-
else:
98-
inline.append(name)
99-
link_target = f"{package.path}.{name}"
100-
entry = f"- [`{name}`][{link_target}]"
113+
for name in sorted(public, key=str.lower):
114+
member = public[name]
101115
try:
102116
target = member.final_target if member.is_alias else member
103117
except griffe.AliasResolutionError as exc:
104-
msg = f"gen_ref_pages: export {package.path}.{name} resolves outside the documented packages"
118+
msg = f"gen_ref_pages: export {module.path}.{name} resolves outside the documented packages"
105119
raise SystemExit(msg) from exc
120+
# Link to the anchor another page actually renders: the deepest alias
121+
# hop whose module is documented (the final target may live in a
122+
# private module and only be re-exported by a public one), rendered
123+
# there only when docstringed (`show_if_no_docstring: false`).
124+
anchor = None
125+
hop = member
126+
while hop.is_alias:
127+
if hop.target_path.rpartition(".")[0] in documented:
128+
anchor = hop.target_path
129+
hop = hop.target
130+
if anchor is not None and member.has_docstrings:
131+
link_target = anchor
132+
else:
133+
inline.append(name)
134+
link_target = f"{module.path}.{name}"
135+
entry = f"- [`{name}`][{link_target}]"
106136
if docstring := target.docstring:
107137
summary = " ".join(docstring.value.split("\n\n", 1)[0].split("\n"))
108138
entry += f" — {summary}"
@@ -111,12 +141,15 @@ def _compact_index(package: griffe.Module, documented: set[str]) -> str | None:
111141
# Rendering the stub resolves the cross-package aliases again, in
112142
# mkdocstrings' own collection. On a warm incremental rebuild the target
113143
# package's pages can all be cache hits, so nothing else loads it and the
114-
# resolution crashes (AliasResolutionError); preloading pins it.
144+
# resolution crashes (AliasResolutionError); preloading pins it. The
145+
# module's own root package needs no pin: rendering the stub loads it.
115146
preload = sorted(
116-
{member.target_path.split(".")[0] for member in exports.values() if member.is_alias} - {package.path}
147+
{member.target_path.split(".")[0] for member in exports.values() if member.is_alias}
148+
- {module.path.split(".")[0]}
117149
)
118-
body = [f"::: {package.path}", " options:"]
119-
body += [" preload_modules:", *(f" - {module}" for module in preload)]
150+
body = [f"::: {module.path}", " options:"]
151+
if preload:
152+
body += [" preload_modules:", *(f" - {pkg}" for pkg in preload)]
120153
if inline:
121154
body += [" members:", *(f" - {name}" for name in inline)]
122155
else:
@@ -145,7 +178,7 @@ def generate() -> list[NavItem]:
145178

146179
root = _Node()
147180
stubs: dict[Path, str] = {}
148-
package_index: dict[str, Path] = {}
181+
pages: dict[str, Path] = {}
149182
documented: set[str] = set()
150183

151184
for package in PACKAGES:
@@ -155,8 +188,7 @@ def generate() -> list[NavItem]:
155188
doc_path = path.relative_to(base).with_suffix(".md")
156189

157190
parts = tuple(module_path.parts)
158-
is_package = parts[-1] == "__init__"
159-
if is_package:
191+
if parts[-1] == "__init__":
160192
parts = parts[:-1]
161193
doc_path = doc_path.with_name("index.md")
162194
# A private component anywhere makes the module private: checking
@@ -167,21 +199,25 @@ def generate() -> list[NavItem]:
167199
ident = ".".join(parts)
168200
documented.add(ident)
169201
stubs[API_DIR / doc_path] = _stub(parts[-1], f"::: {ident}")
170-
if is_package:
171-
package_index[ident] = API_DIR / doc_path
202+
pages[ident] = API_DIR / doc_path
172203

173204
node = root
174205
for part in parts:
175206
node = node.child(part)
176207
node.url = f"api/{doc_path.as_posix()}"
177208

178-
# Load every package before inspecting any of them: aliases only resolve
179-
# once the module they point at is in the loader's collection.
209+
# Load the root packages before inspecting any module: aliases only
210+
# resolve once the module they point at is in the loader's collection.
180211
loader = griffe.GriffeLoader(search_paths=[str(package.parent) for package in PACKAGES])
181-
modules = {ident: loader.load(ident) for ident in package_index}
182-
for ident, doc_path in package_index.items():
183-
module = modules[ident]
184-
assert isinstance(module, griffe.Module)
212+
for package in PACKAGES:
213+
loader.load(package.name)
214+
for ident, doc_path in pages.items():
215+
try:
216+
module = loader.modules_collection[ident]
217+
except KeyError as exc:
218+
raise SystemExit(f"gen_ref_pages: cannot find {ident} in the loaded packages") from exc
219+
if not isinstance(module, griffe.Module):
220+
raise SystemExit(f"gen_ref_pages: {ident} is shadowed by a non-module member")
185221
if body := _compact_index(module, documented):
186222
stubs[doc_path] = _stub(ident.rpartition(".")[2], body)
187223

0 commit comments

Comments
 (0)