Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support type comments in PropertyDocumenter #11298

Merged
merged 3 commits into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
5 changes: 5 additions & 0 deletions tests/roots/test-ext-autodoc/target/cached_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ class Foo:
@cached_property
def prop(self) -> int:
return 1

@cached_property
def prop_with_type_comment(self):
# type: () -> int
return 1
11 changes: 11 additions & 0 deletions tests/roots/test-ext-autodoc/target/properties.py
Original file line number Diff line number Diff line change
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"""
5 changes: 5 additions & 0 deletions tests/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,11 @@ def test_autodoc_cached_property(app):
' :module: target.cached_property',
' :type: int',
'',
'',
' .. py:property:: Foo.prop_with_type_comment',
' :module: target.cached_property',
' :type: int',
'',
]


Expand Down
15 changes: 15 additions & 0 deletions tests/test_ext_autodoc_autoclass.py
Original file line number Diff line number Diff line change
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
41 changes: 41 additions & 0 deletions tests/test_ext_autodoc_autoproperty.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ def test_class_properties(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_properties_with_type_comment(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_with_type_comment(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 All @@ -48,3 +77,15 @@ def test_cached_properties(app):
' :type: int',
'',
]


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