From 75f5122996ea7eb340d31bb3006efde7d2a351f6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 11 Mar 2021 02:01:17 +0900 Subject: [PATCH] Fix #7383: autodoc: Support typehints for properties py:property directive now outputs py:property directive to describe its type annotation. --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 16 +++++++++-- .../test-ext-autodoc/target/properties.py | 6 ++++ tests/test_ext_autodoc.py | 9 ++---- tests/test_ext_autodoc_autoclass.py | 21 ++++++++++++++ tests/test_ext_autodoc_autoproperty.py | 28 +++++++++++++++++++ tests/test_ext_autodoc_configs.py | 12 +++----- 7 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 tests/roots/test-ext-autodoc/target/properties.py create mode 100644 tests/test_ext_autodoc_autoproperty.py diff --git a/CHANGES b/CHANGES index ee9f48733ad..9b740574fe1 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,7 @@ Features added -------------- * #8924: autodoc: Support ``bound`` argument for TypeVar +* #7383: autodoc: Support typehints for properties * #7549: autosummary: Enable :confval:`autosummary_generate` by default * #4826: py domain: Add ``:canonical:`` option to python directives to describe the location where the object is defined diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 0b570930149..5799bbc24f1 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2526,7 +2526,6 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # Specialized Documenter subclass for properties. """ objtype = 'property' - directivetype = 'method' member_order = 60 # before AttributeDocumenter @@ -2549,7 +2548,20 @@ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() if inspect.isabstractmethod(self.object): self.add_line(' :abstractmethod:', sourcename) - self.add_line(' :property:', sourcename) + + if safe_getattr(self.object, 'fget', None): + try: + signature = inspect.signature(self.object.fget, + type_aliases=self.config.autodoc_type_aliases) + if signature.return_annotation is not Parameter.empty: + objrepr = stringify_typehint(signature.return_annotation) + self.add_line(' :type: ' + objrepr, sourcename) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + return None + except ValueError: + raise class NewTypeAttributeDocumenter(AttributeDocumenter): diff --git a/tests/roots/test-ext-autodoc/target/properties.py b/tests/roots/test-ext-autodoc/target/properties.py new file mode 100644 index 00000000000..409fc2b5d61 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/properties.py @@ -0,0 +1,6 @@ +class Foo: + """docstring""" + + @property + def prop(self) -> int: + """docstring""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index d9986e9ca55..7c6f4e0d674 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1033,9 +1033,8 @@ def test_autodoc_descriptor(app): ' Descriptor instance docstring.', '', '', - ' .. py:method:: Class.prop', + ' .. py:property:: Class.prop', ' :module: target.descriptor', - ' :property:', '', ' Property.', '' @@ -1055,9 +1054,8 @@ def test_autodoc_cached_property(app): ' :module: target.cached_property', '', '', - ' .. py:method:: Foo.prop', + ' .. py:property:: Foo.prop', ' :module: target.cached_property', - ' :property:', '', ] @@ -1516,10 +1514,9 @@ def test_abstractmethods(app): ' :module: target.abstractmethods', '', '', - ' .. py:method:: Base.prop', + ' .. py:property:: Base.prop', ' :module: target.abstractmethods', ' :abstractmethod:', - ' :property:', '', '', ' .. py:method:: Base.staticmeth()', diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 538b36881f8..9402633872e 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -199,6 +199,27 @@ def test_decorators(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_properties(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.properties.Foo', options) + assert list(actual) == [ + '', + '.. py:class:: Foo()', + ' :module: target.properties', + '', + ' docstring', + '', + '', + ' .. py:property:: Foo.prop', + ' :module: target.properties', + ' :type: int', + '', + ' docstring', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_slots_attribute(app): options = {"members": None} diff --git a/tests/test_ext_autodoc_autoproperty.py b/tests/test_ext_autodoc_autoproperty.py new file mode 100644 index 00000000000..ee25aa8b71b --- /dev/null +++ b/tests/test_ext_autodoc_autoproperty.py @@ -0,0 +1,28 @@ +""" + test_ext_autodoc_autoproperty + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test the autodoc extension. This tests mainly the Documenters; the auto + directives are tested in a test source file translated by test_build. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import pytest + +from .test_ext_autodoc import do_autodoc + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_properties(app): + actual = do_autodoc(app, 'property', 'target.properties.Foo.prop') + assert list(actual) == [ + '', + '.. py:property:: Foo.prop', + ' :module: target.properties', + ' :type: int', + '', + ' docstring', + '', + ] diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 06bf39c24c4..cc34143ca36 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -261,16 +261,14 @@ def test_autodoc_docstring_signature(app): ' indented line', '', '', - ' .. py:method:: DocstringSig.prop1', + ' .. py:property:: DocstringSig.prop1', ' :module: target', - ' :property:', '', ' First line of docstring', '', '', - ' .. py:method:: DocstringSig.prop2', + ' .. py:property:: DocstringSig.prop2', ' :module: target', - ' :property:', '', ' First line of docstring', ' Second line of docstring', @@ -305,17 +303,15 @@ def test_autodoc_docstring_signature(app): ' indented line', '', '', - ' .. py:method:: DocstringSig.prop1', + ' .. py:property:: DocstringSig.prop1', ' :module: target', - ' :property:', '', ' DocstringSig.prop1(self)', ' First line of docstring', '', '', - ' .. py:method:: DocstringSig.prop2', + ' .. py:property:: DocstringSig.prop2', ' :module: target', - ' :property:', '', ' First line of docstring', ' Second line of docstring',