Skip to content

Commit f04a506

Browse files
authored
docs: show full field descriptions for SDK parameter types (#784)
1 parent 6a99c56 commit f04a506

4 files changed

Lines changed: 86 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
version: '0.10.2'
3030

3131
- name: Install dependencies
32-
run: uv sync --all-extras
32+
run: uv sync --all-extras --group docs
3333

3434
- name: Run lints
3535
run: ./scripts/lint

docs/conf.py

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77

88
import os
99
import 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
1321
sys.path.insert(0, os.path.abspath("../src"))
@@ -59,7 +67,76 @@
5967
autodoc_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

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ docs = [
7272
"furo>=2025.9.25",
7373
"sphinx>=7.4.7",
7474
"sphinx-autodoc-typehints>=2.3.0",
75+
"sphinx-tabs>=3.4.0",
7576
"sphinx-toolbox>=4.0.0",
7677
]
7778

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)