Skip to content

Commit

Permalink
Add type comments support in PropertyDocumenter
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz committed Apr 6, 2023
1 parent 9299003 commit a3e9d7d
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -27,6 +27,8 @@ Deprecated
Features added
--------------

* #11277: :rst:dir:`autoproperty` allows the return type to be specified as
a type comment (e.g., ``# type: () -> int``). Patch by Bénédikt Tran
* #10811: Autosummary: extend ``__all__`` to imported members for template rendering
when option ``autosummary_ignore_module_all`` is set to ``False``. Patch by
Clement Pinard
Expand Down
59 changes: 36 additions & 23 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -2706,6 +2706,16 @@ def import_object(self, raiseerror: bool = False) -> bool:
self.isclassmethod = False
return ret

def format_args(self, **kwargs: Any) -> str | None:
func = self._get_property_getter()
if func is None:
return None

# update the annotations of the property getter
self.env.app.emit('autodoc-before-process-signature', func, False)
# correctly format the arguments for a property
return super().format_args(**kwargs)

def document_members(self, all_members: bool = False) -> None:
pass

Expand All @@ -2721,30 +2731,33 @@ def add_directive_header(self, sig: str) -> None:
if self.isclassmethod:
self.add_line(' :classmethod:', sourcename)

if safe_getattr(self.object, 'fget', None): # property
func = self.object.fget
elif safe_getattr(self.object, 'func', None): # cached_property
func = self.object.func
else:
func = None
func = self._get_property_getter()
if func is None or self.config.autodoc_typehints == 'none':
return

if func and self.config.autodoc_typehints != 'none':
try:
signature = inspect.signature(func,
type_aliases=self.config.autodoc_type_aliases)
if signature.return_annotation is not Parameter.empty:
if self.config.autodoc_typehints_format == "short":
objrepr = stringify_annotation(signature.return_annotation, "smart")
else:
objrepr = stringify_annotation(signature.return_annotation,
"fully-qualified-except-typing")
self.add_line(' :type: ' + objrepr, sourcename)
except TypeError as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
self.fullname, exc)
pass
except ValueError:
pass
try:
signature = inspect.signature(func,
type_aliases=self.config.autodoc_type_aliases)
if signature.return_annotation is not Parameter.empty:
if self.config.autodoc_typehints_format == "short":
objrepr = stringify_annotation(signature.return_annotation, "smart")
else:
objrepr = stringify_annotation(signature.return_annotation,
"fully-qualified-except-typing")
self.add_line(' :type: ' + objrepr, sourcename)
except TypeError as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
self.fullname, exc)
pass
except ValueError:
pass

def _get_property_getter(self):
if safe_getattr(self.object, 'fget', None): # property
return self.object.fget
if safe_getattr(self.object, 'func', None): # cached_property
return self.object.func
return None


def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any:
Expand Down
11 changes: 11 additions & 0 deletions tests/roots/test-ext-autodoc/target/properties.py
Expand Up @@ -9,3 +9,14 @@ def prop1(self) -> int:
@property
def prop2(self) -> int:
"""docstring"""

@property
def prop1_with_type_comment(self):
# type: () -> int
"""docstring"""

@classmethod
@property
def prop2_with_type_comment(self):
# type: () -> int
"""docstring"""
15 changes: 15 additions & 0 deletions tests/test_ext_autodoc_autoclass.py
Expand Up @@ -213,13 +213,28 @@ def test_properties(app):
' docstring',
'',
'',
' .. py:property:: Foo.prop1_with_type_comment',
' :module: target.properties',
' :type: int',
'',
' docstring',
'',
'',
' .. py:property:: Foo.prop2',
' :module: target.properties',
' :classmethod:',
' :type: int',
'',
' docstring',
'',
'',
' .. py:property:: Foo.prop2_with_type_comment',
' :module: target.properties',
' :classmethod:',
' :type: int',
'',
' docstring',
'',
]


Expand Down
29 changes: 29 additions & 0 deletions tests/test_ext_autodoc_autoproperty.py
Expand Up @@ -38,6 +38,35 @@ def test_class_properties(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_properties(app):
actual = do_autodoc(app, 'property', 'target.properties.Foo.prop1_with_type_comment')
assert list(actual) == [
'',
'.. py:property:: Foo.prop1_with_type_comment',
' :module: target.properties',
' :type: int',
'',
' docstring',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_class_properties(app):
actual = do_autodoc(app, 'property', 'target.properties.Foo.prop2_with_type_comment')
assert list(actual) == [
'',
'.. py:property:: Foo.prop2_with_type_comment',
' :module: target.properties',
' :classmethod:',
' :type: int',
'',
' docstring',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cached_properties(app):
actual = do_autodoc(app, 'property', 'target.cached_property.Foo.prop')
Expand Down

0 comments on commit a3e9d7d

Please sign in to comment.