77
88import os
99import sys
10- from typing import Any
10+ import pkgutil
11+ from typing import get_type_hints
12+ from typing_extensions import override
13+
14+ from sphinx .errors import PycodeError
15+ from sphinx .pycode import ModuleAnalyzer
16+ from sphinx .application import Sphinx
17+ from sphinx .ext .autodoc import Documenter
18+ from sphinx_toolbox .more_autodoc .autotypeddict import TypedDictDocumenter
1119
1220# Add the src directory to the path so we can import the package
1321sys .path .insert (0 , os .path .abspath ("../src" ))
5967autodoc_typehints_description_target = "documented"
6068
6169
62- def _inject_type_submodules (_app : Any , docname : str , source : list [str ]) -> None :
70+ # -- Autodocumenter extensions -----------------------------------------------
71+
72+
73+ def _collect_field_docstrings (cls : type ) -> dict [str , list [str ]]:
74+ """Collect field docstrings from cls and all TypedDict ancestors via __orig_bases__."""
75+ result : dict [str , list [str ]] = {}
76+ # Parents first — child docstrings overwrite via later assignment
77+ for base in getattr (cls , "__orig_bases__" , ()):
78+ origin = getattr (base , "__origin__" , base )
79+ if isinstance (origin , type ) and origin is not cls :
80+ result .update (_collect_field_docstrings (origin ))
81+ try :
82+ attr_docs = ModuleAnalyzer .for_module (cls .__module__ ).find_attr_docs ()
83+ for (_ , attr_name ), doc_lines in attr_docs .items ():
84+ result [attr_name ] = doc_lines
85+ except PycodeError :
86+ pass
87+ return result
88+
89+
90+ class _InheritedDocsTypedDictDocumenter (TypedDictDocumenter ):
91+ """TypedDictDocumenter that collects field docstrings from parent TypedDicts.
92+
93+ Upstream only scans self.object.__module__ for field docstrings, so
94+ inherited descriptions are lost. This subclass traverses __orig_bases__.
95+ Upstream bug: https://github.com/sphinx-doc/sphinx/issues/9290
96+ Patching sphinx_toolbox 4.1.2.
97+ """
98+
99+ @override
100+ def sort_members (
101+ self ,
102+ documenters : list [tuple [Documenter , bool ]],
103+ order : str ,
104+ ) -> list [tuple [Documenter , bool ]]:
105+ # Skip TypedDictDocumenter.sort_members (returns [] after adding
106+ # lines with wrong docstrings). Call ClassDocumenter.sort_members
107+ # to get the properly sorted documenters list.
108+ documenters = super (TypedDictDocumenter , self ).sort_members (documenters , order )
109+ docstrings = _collect_field_docstrings (self .object )
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+
136+ # -- Dynamic type documentation ----------------------------------------------
137+
138+
139+ def _inject_type_submodules (_app : Sphinx , docname : str , source : list [str ]) -> None :
63140 """Auto-generate automodule directives for all type submodules.
64141
65142 Replaces the ``.. auto-all-types::`` placeholder in types.rst with
@@ -70,8 +147,6 @@ def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None:
70147 """
71148 if docname != "api/types" :
72149 return
73- import pkgutil
74-
75150 import runloop_api_client .types as types_pkg
76151
77152 directives : list [str ] = []
@@ -85,8 +160,9 @@ def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None:
85160 source [0 ] = source [0 ].replace (".. auto-all-types::" , "\n " .join (directives ))
86161
87162
88- def setup (app : Any ) -> None :
89- app .connect ("source-read" , _inject_type_submodules )
163+ def setup (app : Sphinx ) -> None :
164+ app .add_autodocumenter (_InheritedDocsTypedDictDocumenter , override = True )
165+ app .connect ("source-read" , _inject_type_submodules ) # pyright: ignore[reportUnknownMemberType]
90166
91167
92168# Intersphinx mapping
0 commit comments