Skip to content

Commit b60d867

Browse files
committed
docs: show full field descriptions for SDK parameter types
1 parent 6a99c56 commit b60d867

1 file changed

Lines changed: 83 additions & 3 deletions

File tree

docs/conf.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import os
99
import sys
10-
from typing import Any
10+
import pkgutil
11+
from typing import Any, get_type_hints
1112

1213
# Add the src directory to the path so we can import the package
1314
sys.path.insert(0, os.path.abspath("../src"))
@@ -59,6 +60,87 @@
5960
autodoc_typehints_description_target = "documented"
6061

6162

63+
# -- Patches -----------------------------------------------------------------
64+
65+
66+
def _patch_autotypeddict_inherited_docstrings() -> None:
67+
"""Patch autotypeddict to include field docstrings from parent TypedDicts.
68+
69+
sphinx_toolbox's sort_members only scans self.object.__module__ for field
70+
docstrings (autotypeddict.py:381-384). SDK TypedDicts like SDKDevboxCreateParams
71+
inherit fields from types in other modules (e.g. DevboxCreateParams), so those
72+
descriptions are lost. This patch collects docstrings from the full
73+
__orig_bases__ chain.
74+
75+
Upstream bug: https://github.com/sphinx-doc/sphinx/issues/9290
76+
"""
77+
from sphinx.errors import PycodeError
78+
from sphinx.pycode import ModuleAnalyzer
79+
from sphinx_toolbox.more_autodoc.autotypeddict import TypedDictDocumenter
80+
81+
def _collect_field_docstrings(cls: type) -> dict[str, list[str]]:
82+
result: dict[str, list[str]] = {}
83+
visited: set[type] = set()
84+
85+
def _walk(klass: type) -> None:
86+
if klass in visited or not hasattr(klass, "__annotations__"):
87+
return
88+
visited.add(klass)
89+
try:
90+
for (_, fname), doc in ModuleAnalyzer.for_module(klass.__module__).find_attr_docs().items():
91+
if fname not in result:
92+
result[fname] = doc
93+
except PycodeError:
94+
pass
95+
for base in getattr(klass, "__orig_bases__", []):
96+
if isinstance(base, type):
97+
_walk(base)
98+
99+
_walk(cls)
100+
return result
101+
102+
def _patched_sort_members(
103+
self: TypedDictDocumenter,
104+
documenters: list[tuple[Any, bool]],
105+
order: str,
106+
) -> list[tuple[Any, bool]]:
107+
documenters = super(TypedDictDocumenter, self).sort_members(documenters, order)
108+
docstrings = _collect_field_docstrings(self.object)
109+
110+
required_keys: list[str] = []
111+
optional_keys: list[str] = []
112+
types = get_type_hints(self.object)
113+
114+
for d in documenters:
115+
name = d[0].name.split(".")[-1]
116+
if name in self.object.__required_keys__:
117+
required_keys.append(name)
118+
elif name in self.object.__optional_keys__:
119+
optional_keys.append(name)
120+
121+
sourcename = self.get_sourcename()
122+
if required_keys:
123+
self.add_line("", sourcename)
124+
self.add_line(":Required Keys:", sourcename)
125+
self.document_keys(required_keys, types, docstrings) # pyright: ignore[reportUnknownMemberType]
126+
self.add_line("", sourcename)
127+
if optional_keys:
128+
self.add_line("", sourcename)
129+
self.add_line(":Optional Keys:", sourcename)
130+
self.document_keys(optional_keys, types, docstrings) # pyright: ignore[reportUnknownMemberType]
131+
self.add_line("", sourcename)
132+
133+
return []
134+
135+
TypedDictDocumenter.sort_members = _patched_sort_members # type: ignore[assignment]
136+
137+
138+
_patch_autotypeddict_inherited_docstrings()
139+
140+
141+
# -- Dynamic type documentation ----------------------------------------------
142+
143+
62144
def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None:
63145
"""Auto-generate automodule directives for all type submodules.
64146
@@ -70,8 +152,6 @@ def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None:
70152
"""
71153
if docname != "api/types":
72154
return
73-
import pkgutil
74-
75155
import runloop_api_client.types as types_pkg
76156

77157
directives: list[str] = []

0 commit comments

Comments
 (0)