diff --git a/src/griffe/agents/visitor.py b/src/griffe/agents/visitor.py index 52f00655..0edbff81 100644 --- a/src/griffe/agents/visitor.py +++ b/src/griffe/agents/visitor.py @@ -335,6 +335,21 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels: lineno = node.lineno labels |= self.decorators_to_labels(decorators) + + if "property" in labels: + attribute = Attribute( + name=node.name, + value=None, + annotation=safe_get_annotation(node.returns, parent=self.current), + lineno=node.lineno, + endlineno=node.end_lineno, # type: ignore[union-attr] + docstring=self._get_docstring(node), + runtime=not self.type_guarded, + ) + attribute.labels |= labels + self.current[node.name] = attribute + return + base_property, property_function = self.get_base_property(decorators) # handle parameters diff --git a/src/griffe/docstrings/google.py b/src/griffe/docstrings/google.py index c43f2bb2..474c4d3d 100644 --- a/src/griffe/docstrings/google.py +++ b/src/griffe/docstrings/google.py @@ -363,7 +363,12 @@ def _read_returns_section( # noqa: WPS231 else: # try to retrieve the annotation from the docstring parent with suppress(AttributeError, KeyError, ValueError): - annotation = docstring.parent.returns # type: ignore[union-attr] + if docstring.parent.is_function: # type: ignore[union-attr] + annotation = docstring.parent.returns # type: ignore[union-attr] + elif docstring.parent.is_attribute: # type: ignore[union-attr] + annotation = docstring.parent.annotation # type: ignore[union-attr] + else: + raise ValueError if len(block) > 1: if annotation.is_tuple: annotation = annotation.tuple_item(index) diff --git a/src/griffe/docstrings/numpy.py b/src/griffe/docstrings/numpy.py index 026c273f..ee39ef16 100644 --- a/src/griffe/docstrings/numpy.py +++ b/src/griffe/docstrings/numpy.py @@ -358,7 +358,12 @@ def _read_returns_section( # noqa: WPS231 if annotation is None: # try to retrieve the annotation from the docstring parent with suppress(AttributeError, KeyError, ValueError): - annotation = docstring.parent.returns # type: ignore[union-attr] + if docstring.parent.is_function: # type: ignore[union-attr] + annotation = docstring.parent.returns # type: ignore[union-attr] + elif docstring.parent.is_attribute: # type: ignore[union-attr] + annotation = docstring.parent.annotation # type: ignore[union-attr] + else: + raise ValueError if len(items) > 1: if annotation.is_tuple: annotation = annotation.tuple_item(index) diff --git a/tests/test_visitor.py b/tests/test_visitor.py index b5b0b8be..aa70b694 100644 --- a/tests/test_visitor.py +++ b/tests/test_visitor.py @@ -260,3 +260,22 @@ def method(self): assert module.docstring.lineno == 2 assert module["C"].docstring.lineno == 6 assert module["C.method"].docstring.lineno == 10 + + +def test_create_properties_as_attributes(): + """Assert properties are created as attributes and not functions.""" + with temporary_visited_module( + """ + from functools import cached_property + + class C: + @property + def prop(self) -> bool: + return True + @cached_property + def cached_prop(self) -> int: + return 0 + """ + ) as module: + assert module["C.prop"].is_attribute + assert module["C.cached_prop"].is_attribute