Skip to content

Commit

Permalink
Fix types missing from documentation generated by autodoc-style direc…
Browse files Browse the repository at this point in the history
…tives

Fixes #473
  • Loading branch information
AWhetter committed Sep 25, 2024
1 parent 841ce10 commit b14c0cc
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 12 deletions.
35 changes: 23 additions & 12 deletions autoapi/documenters.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def format_signature(self, **kwargs):


class AutoapiFunctionDocumenter(
AutoapiDocumenter, autodoc.FunctionDocumenter, _AutoapiDocstringSignatureMixin
AutoapiDocumenter, _AutoapiDocstringSignatureMixin, autodoc.FunctionDocumenter
):
objtype = "apifunction"
directivetype = "function"
Expand Down Expand Up @@ -147,9 +147,7 @@ def format_args(self, **kwargs):
return "(" + to_format + ")"


class AutoapiClassDocumenter(
AutoapiDocumenter, autodoc.ClassDocumenter, _AutoapiDocstringSignatureMixin
):
class AutoapiClassDocumenter(AutoapiDocumenter, autodoc.ClassDocumenter):
objtype = "apiclass"
directivetype = "class"
doc_as_attr = False
Expand All @@ -174,9 +172,18 @@ def add_directive_header(self, sig):
bases = ", ".join(f":class:`{base}`" for base in self.object.bases)
self.add_line(f" Bases: {bases}", sourcename)

def format_signature(self, **kwargs):
# Set "manual" attributes at the last possible moment.
# This is to let a manual entry or docstring searching happen first,
# and falling back to the discovered signature only when necessary.
if self.args is None:
self.args = self.object.args

return super().format_signature(**kwargs)


class AutoapiMethodDocumenter(
AutoapiDocumenter, autodoc.MethodDocumenter, _AutoapiDocstringSignatureMixin
AutoapiDocumenter, _AutoapiDocstringSignatureMixin, autodoc.MethodDocumenter
):
objtype = "apimethod"
directivetype = "method"
Expand Down Expand Up @@ -230,16 +237,16 @@ def add_directive_header(self, sig):
autodoc.ClassLevelDocumenter.add_directive_header(self, sig)

sourcename = self.get_sourcename()
if self.options.annotation and self.options.annotation is not autodoc.SUPPRESS:
self.add_line(f" :type: {self.options.annotation}", sourcename)

for property_type in (
"abstractmethod",
"classmethod",
):
if property_type in self.object.properties:
self.add_line(f" :{property_type}:", sourcename)

if self.object.annotation:
self.add_line(f" :type: {self.object.annotation}", sourcename)


class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):
objtype = "apidata"
Expand All @@ -254,14 +261,16 @@ def add_directive_header(self, sig):
autodoc.ModuleLevelDocumenter.add_directive_header(self, sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
# TODO: Change sphinx to allow overriding of object description
if self.object.value is not None:
self.add_line(f" :annotation: = {self.object.value}", sourcename)
self.add_line(f" :value: {self.object.value}", sourcename)
elif self.options.annotation is autodoc.SUPPRESS:
pass
else:
self.add_line(f" :annotation: {self.options.annotation}", sourcename)

if self.object.annotation:
self.add_line(f" :type: {self.object.annotation}", sourcename)


class AutoapiAttributeDocumenter(AutoapiDocumenter, autodoc.AttributeDocumenter):
objtype = "apiattribute"
Expand All @@ -277,14 +286,16 @@ def add_directive_header(self, sig):
autodoc.ClassLevelDocumenter.add_directive_header(self, sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
# TODO: Change sphinx to allow overriding of object description
if self.object.value is not None:
self.add_line(f" :annotation: = {self.object.value}", sourcename)
self.add_line(f" :value: {self.object.value}", sourcename)
elif self.options.annotation is autodoc.SUPPRESS:
pass
else:
self.add_line(f" :annotation: {self.options.annotation}", sourcename)

if self.object.annotation:
self.add_line(f" :type: {self.object.annotation}", sourcename)


class AutoapiModuleDocumenter(AutoapiDocumenter, autodoc.ModuleDocumenter):
objtype = "apimodule"
Expand Down
1 change: 1 addition & 0 deletions docs/changes/473.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix types missing from documentation generated by autodoc-style directives.
24 changes: 24 additions & 0 deletions tests/python/pyexample/example/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This is a description
"""

from dataclasses import dataclass
from functools import cached_property

A_TUPLE = ("a", "b")
Expand Down Expand Up @@ -171,3 +172,26 @@ def fn_with_long_sig(
arguments
):
"""A function with a long signature."""


TYPED_DATA: int = 1
"""This is TYPED_DATA."""


@dataclass
class TypedAttrs:
one: str
"""This is TypedAttrs.one."""
two: int = 1
"""This is TypedAttrs.two."""


class TypedClassInit:
"""This is TypedClassInit."""

def __init__(self, one: int = 1) -> None:
self._one = one

def typed_method(self, two: int) -> int:
"""This is TypedClassInit.typed_method."""
return self._one + two
68 changes: 68 additions & 0 deletions tests/python/test_pyintegration.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,74 @@ def test_manual_directives(self, parse):
example_file = parse("_build/html/manualapi.html")
assert example_file.find(id="example.decorator_okay")

def test_dataclass(self, parse):
example_file = parse("_build/html/manualapi.html")

typedattrs_sig = example_file.find(id="example.TypedAttrs")
assert typedattrs_sig

typedattrs = typedattrs_sig.parent

one = typedattrs.find(id="example.TypedAttrs.one")
assert one
one_value = one.find_all(class_="property")
assert one_value[0].text == ": str"
one_docstring = one.parent.find("dd").contents[0].text
assert one_docstring.strip() == "This is TypedAttrs.one."

two = typedattrs.find(id="example.TypedAttrs.two")
assert two
two_value = two.find_all(class_="property")
assert two_value[0].text == ": int"
assert two_value[1].text == " = 1"
two_docstring = two.parent.find("dd").contents[0].text
assert two_docstring.strip() == "This is TypedAttrs.two."

def test_data(self, parse):
example_file = parse("_build/html/manualapi.html")

typed_data = example_file.find(id="example.TYPED_DATA")
assert typed_data
typed_data_value = typed_data.find_all(class_="property")
assert typed_data_value[0].text == ": int"
assert typed_data_value[1].text == " = 1"

typed_data_docstring = typed_data.parent.find("dd").contents[0].text
assert typed_data_docstring.strip() == "This is TYPED_DATA."

def test_class(self, parse):
example_file = parse("_build/html/manualapi.html")

typed_cls = example_file.find(id="example.TypedClassInit")
assert typed_cls
arg = typed_cls.find(class_="sig-param")
assert arg.text == "one: int = 1"
typed_cls_docstring = typed_cls.parent.find("dd").contents[0].text
assert typed_cls_docstring.strip() == "This is TypedClassInit."

typed_method = example_file.find(id="example.TypedClassInit.typed_method")
assert typed_method
arg = typed_method.find(class_="sig-param")
assert arg.text == "two: int"
return_type = typed_method.find(class_="sig-return-typehint")
assert return_type.text == "int"
typed_method_docstring = typed_method.parent.find("dd").contents[0].text
assert typed_method_docstring.strip() == "This is TypedClassInit.typed_method."

def test_property(self, parse):
example_file = parse("_build/html/manualapi.html")

foo_sig = example_file.find(id="example.Foo")
assert foo_sig
foo = foo_sig.parent

property_simple = foo.find(id="example.Foo.property_simple")
assert property_simple
property_simple_value = property_simple.find_all(class_="property")
assert property_simple_value[-1].text == ": int"
property_simple_docstring = property_simple.parent.find("dd").text.strip()
assert property_simple_docstring == "This property should parse okay."


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

0 comments on commit b14c0cc

Please sign in to comment.