Skip to content

Commit b14c0cc

Browse files
committed
Fix types missing from documentation generated by autodoc-style directives
Fixes #473
1 parent 841ce10 commit b14c0cc

File tree

4 files changed

+116
-12
lines changed

4 files changed

+116
-12
lines changed

autoapi/documenters.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def format_signature(self, **kwargs):
9898

9999

100100
class AutoapiFunctionDocumenter(
101-
AutoapiDocumenter, autodoc.FunctionDocumenter, _AutoapiDocstringSignatureMixin
101+
AutoapiDocumenter, _AutoapiDocstringSignatureMixin, autodoc.FunctionDocumenter
102102
):
103103
objtype = "apifunction"
104104
directivetype = "function"
@@ -147,9 +147,7 @@ def format_args(self, **kwargs):
147147
return "(" + to_format + ")"
148148

149149

150-
class AutoapiClassDocumenter(
151-
AutoapiDocumenter, autodoc.ClassDocumenter, _AutoapiDocstringSignatureMixin
152-
):
150+
class AutoapiClassDocumenter(AutoapiDocumenter, autodoc.ClassDocumenter):
153151
objtype = "apiclass"
154152
directivetype = "class"
155153
doc_as_attr = False
@@ -174,9 +172,18 @@ def add_directive_header(self, sig):
174172
bases = ", ".join(f":class:`{base}`" for base in self.object.bases)
175173
self.add_line(f" Bases: {bases}", sourcename)
176174

175+
def format_signature(self, **kwargs):
176+
# Set "manual" attributes at the last possible moment.
177+
# This is to let a manual entry or docstring searching happen first,
178+
# and falling back to the discovered signature only when necessary.
179+
if self.args is None:
180+
self.args = self.object.args
181+
182+
return super().format_signature(**kwargs)
183+
177184

178185
class AutoapiMethodDocumenter(
179-
AutoapiDocumenter, autodoc.MethodDocumenter, _AutoapiDocstringSignatureMixin
186+
AutoapiDocumenter, _AutoapiDocstringSignatureMixin, autodoc.MethodDocumenter
180187
):
181188
objtype = "apimethod"
182189
directivetype = "method"
@@ -230,16 +237,16 @@ def add_directive_header(self, sig):
230237
autodoc.ClassLevelDocumenter.add_directive_header(self, sig)
231238

232239
sourcename = self.get_sourcename()
233-
if self.options.annotation and self.options.annotation is not autodoc.SUPPRESS:
234-
self.add_line(f" :type: {self.options.annotation}", sourcename)
235-
236240
for property_type in (
237241
"abstractmethod",
238242
"classmethod",
239243
):
240244
if property_type in self.object.properties:
241245
self.add_line(f" :{property_type}:", sourcename)
242246

247+
if self.object.annotation:
248+
self.add_line(f" :type: {self.object.annotation}", sourcename)
249+
243250

244251
class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):
245252
objtype = "apidata"
@@ -254,14 +261,16 @@ def add_directive_header(self, sig):
254261
autodoc.ModuleLevelDocumenter.add_directive_header(self, sig)
255262
sourcename = self.get_sourcename()
256263
if not self.options.annotation:
257-
# TODO: Change sphinx to allow overriding of object description
258264
if self.object.value is not None:
259-
self.add_line(f" :annotation: = {self.object.value}", sourcename)
265+
self.add_line(f" :value: {self.object.value}", sourcename)
260266
elif self.options.annotation is autodoc.SUPPRESS:
261267
pass
262268
else:
263269
self.add_line(f" :annotation: {self.options.annotation}", sourcename)
264270

271+
if self.object.annotation:
272+
self.add_line(f" :type: {self.object.annotation}", sourcename)
273+
265274

266275
class AutoapiAttributeDocumenter(AutoapiDocumenter, autodoc.AttributeDocumenter):
267276
objtype = "apiattribute"
@@ -277,14 +286,16 @@ def add_directive_header(self, sig):
277286
autodoc.ClassLevelDocumenter.add_directive_header(self, sig)
278287
sourcename = self.get_sourcename()
279288
if not self.options.annotation:
280-
# TODO: Change sphinx to allow overriding of object description
281289
if self.object.value is not None:
282-
self.add_line(f" :annotation: = {self.object.value}", sourcename)
290+
self.add_line(f" :value: {self.object.value}", sourcename)
283291
elif self.options.annotation is autodoc.SUPPRESS:
284292
pass
285293
else:
286294
self.add_line(f" :annotation: {self.options.annotation}", sourcename)
287295

296+
if self.object.annotation:
297+
self.add_line(f" :type: {self.object.annotation}", sourcename)
298+
288299

289300
class AutoapiModuleDocumenter(AutoapiDocumenter, autodoc.ModuleDocumenter):
290301
objtype = "apimodule"

docs/changes/473.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix types missing from documentation generated by autodoc-style directives.

tests/python/pyexample/example/example.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
This is a description
44
"""
55

6+
from dataclasses import dataclass
67
from functools import cached_property
78

89
A_TUPLE = ("a", "b")
@@ -171,3 +172,26 @@ def fn_with_long_sig(
171172
arguments
172173
):
173174
"""A function with a long signature."""
175+
176+
177+
TYPED_DATA: int = 1
178+
"""This is TYPED_DATA."""
179+
180+
181+
@dataclass
182+
class TypedAttrs:
183+
one: str
184+
"""This is TypedAttrs.one."""
185+
two: int = 1
186+
"""This is TypedAttrs.two."""
187+
188+
189+
class TypedClassInit:
190+
"""This is TypedClassInit."""
191+
192+
def __init__(self, one: int = 1) -> None:
193+
self._one = one
194+
195+
def typed_method(self, two: int) -> int:
196+
"""This is TypedClassInit.typed_method."""
197+
return self._one + two

tests/python/test_pyintegration.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,74 @@ def test_manual_directives(self, parse):
177177
example_file = parse("_build/html/manualapi.html")
178178
assert example_file.find(id="example.decorator_okay")
179179

180+
def test_dataclass(self, parse):
181+
example_file = parse("_build/html/manualapi.html")
182+
183+
typedattrs_sig = example_file.find(id="example.TypedAttrs")
184+
assert typedattrs_sig
185+
186+
typedattrs = typedattrs_sig.parent
187+
188+
one = typedattrs.find(id="example.TypedAttrs.one")
189+
assert one
190+
one_value = one.find_all(class_="property")
191+
assert one_value[0].text == ": str"
192+
one_docstring = one.parent.find("dd").contents[0].text
193+
assert one_docstring.strip() == "This is TypedAttrs.one."
194+
195+
two = typedattrs.find(id="example.TypedAttrs.two")
196+
assert two
197+
two_value = two.find_all(class_="property")
198+
assert two_value[0].text == ": int"
199+
assert two_value[1].text == " = 1"
200+
two_docstring = two.parent.find("dd").contents[0].text
201+
assert two_docstring.strip() == "This is TypedAttrs.two."
202+
203+
def test_data(self, parse):
204+
example_file = parse("_build/html/manualapi.html")
205+
206+
typed_data = example_file.find(id="example.TYPED_DATA")
207+
assert typed_data
208+
typed_data_value = typed_data.find_all(class_="property")
209+
assert typed_data_value[0].text == ": int"
210+
assert typed_data_value[1].text == " = 1"
211+
212+
typed_data_docstring = typed_data.parent.find("dd").contents[0].text
213+
assert typed_data_docstring.strip() == "This is TYPED_DATA."
214+
215+
def test_class(self, parse):
216+
example_file = parse("_build/html/manualapi.html")
217+
218+
typed_cls = example_file.find(id="example.TypedClassInit")
219+
assert typed_cls
220+
arg = typed_cls.find(class_="sig-param")
221+
assert arg.text == "one: int = 1"
222+
typed_cls_docstring = typed_cls.parent.find("dd").contents[0].text
223+
assert typed_cls_docstring.strip() == "This is TypedClassInit."
224+
225+
typed_method = example_file.find(id="example.TypedClassInit.typed_method")
226+
assert typed_method
227+
arg = typed_method.find(class_="sig-param")
228+
assert arg.text == "two: int"
229+
return_type = typed_method.find(class_="sig-return-typehint")
230+
assert return_type.text == "int"
231+
typed_method_docstring = typed_method.parent.find("dd").contents[0].text
232+
assert typed_method_docstring.strip() == "This is TypedClassInit.typed_method."
233+
234+
def test_property(self, parse):
235+
example_file = parse("_build/html/manualapi.html")
236+
237+
foo_sig = example_file.find(id="example.Foo")
238+
assert foo_sig
239+
foo = foo_sig.parent
240+
241+
property_simple = foo.find(id="example.Foo.property_simple")
242+
assert property_simple
243+
property_simple_value = property_simple.find_all(class_="property")
244+
assert property_simple_value[-1].text == ": int"
245+
property_simple_docstring = property_simple.parent.find("dd").text.strip()
246+
assert property_simple_docstring == "This property should parse okay."
247+
180248

181249
class TestMovedConfPy(TestSimpleModule):
182250
@pytest.fixture(autouse=True, scope="class")

0 commit comments

Comments
 (0)