From 210a9534b2e679f965f8b41f5471c44b9acce048 Mon Sep 17 00:00:00 2001 From: David Yan Date: Mon, 18 May 2020 17:08:17 -0700 Subject: [PATCH] Add type hints for attributes --- sphinx_autodoc_typehints.py | 21 +++++++++++++ tests/roots/test-dummy/dummy_module.py | 30 +++++++++++++++++- tests/test_sphinx_autodoc_typehints.py | 43 ++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/sphinx_autodoc_typehints.py b/sphinx_autodoc_typehints.py index e3539f95..e9e6ce5c 100644 --- a/sphinx_autodoc_typehints.py +++ b/sphinx_autodoc_typehints.py @@ -358,6 +358,27 @@ def process_docstring(app, what, name, obj, options, lines): if callable(obj): if inspect.isclass(obj): + # Use type hints from instance and class variables for attributes + outer_obj = inspect.unwrap(obj) + type_hints = get_all_type_hints(outer_obj, name) + for argname, annotation in type_hints.items(): + formatted_annotation = format_annotation( + annotation, fully_qualified=app.config.typehints_fully_qualified) + + searchfor_attr = '.. attribute:: {}'.format(argname) + searchfor_ivar = ':ivar {}:'.format(argname) + for i, line in enumerate(lines): + if line.startswith(searchfor_attr): + i += 1 + while i < len(lines): + if lines[i].startswith(':') or lines[i].startswith('.. '): + break + i += 1 + lines.insert(i, ' :type: {}'.format(formatted_annotation)) + break + if line.startswith(searchfor_ivar): + lines.insert(i, ':vartype {}: {}'.format(argname, formatted_annotation)) + break obj = getattr(obj, '__init__') obj = inspect.unwrap(obj) diff --git a/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py index d091cd89..7d729f40 100644 --- a/tests/roots/test-dummy/dummy_module.py +++ b/tests/roots/test-dummy/dummy_module.py @@ -1,6 +1,6 @@ import typing from mailbox import Mailbox -from typing import Callable, Union +from typing import Callable, ClassVar, Union try: from dataclasses import dataclass @@ -24,8 +24,26 @@ class Class: :param x: foo :param y: bar :param z: baz + + .. attribute:: x + + Multiline + + Description + + .. attribute:: y + + bar + + .. attribute:: z + + baz """ + x: bool + y: int + z: ClassVar[str] + def __init__(self, x: bool, y: int, z: typing.Optional[str] = None) -> None: pass @@ -88,8 +106,12 @@ def a_property(self) -> str: class InnerClass: """ Inner class. + + :ivar x: foo """ + x: bool + def inner_method(self, x: bool) -> str: """ Inner method. @@ -111,9 +133,15 @@ class DummyException(Exception): """ Exception docstring + .. attribute:: message + + Message description + :param message: blah """ + message: str + def __init__(self, message: str) -> None: super().__init__(message) diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 6853ef1c..b44861bf 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -246,10 +246,36 @@ class dummy_module.Class(x, y, z=None) * **z** ("Optional"["str"]) – baz + x + + Multiline + + Description + + Type: + "bool" + + y + + bar + + Type: + "int" + + z + + baz + + Type: + "ClassVar"["str"] + class InnerClass Inner class. + Variables: + **x** ("bool") -- foo + _InnerClass__dunder_inner_method(x) Dunder inner method. @@ -270,6 +296,8 @@ class InnerClass Return type: "str" + x: bool = None + _Class__dunder_method(x) Dunder method docstring. @@ -356,13 +384,28 @@ class InnerClass Return type: "str" + x: bool = None + + y: int = None + + z: ClassVar[str] = None + exception dummy_module.DummyException(message) Exception docstring + message + + Message description + + Type: + "str" + Parameters: **message** ("str") – blah + message: str = None + dummy_module.function(x, y, z_=None) Function docstring.