77
88import os
99import 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
1314sys .path .insert (0 , os .path .abspath ("../src" ))
5960autodoc_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+
62144def _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