Skip to content

Commit 2d3d05a

Browse files
committed
fix: Support cached properties
Issue #86: #86
1 parent c88282c commit 2d3d05a

File tree

5 files changed

+49
-6
lines changed

5 files changed

+49
-6
lines changed

Diff for: config/mypy.ini

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
[mypy]
22
ignore_missing_imports = true
3+
4+
[mypy-tests.fixtures.*]
5+
ignore_errors = true

Diff for: pyproject.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ include = [
2020

2121
[tool.poetry.dependencies]
2222
python = "^3.6"
23-
dataclasses = {version = ">=0.7 <0.9", python = "3.6"}
24-
typing-extensions = {version = "^3.7.4.3", python = "3.6 || 3.7"}
23+
cached-property = {version = "^1.5.2", python = "<3.8"}
2524

2625
[tool.poetry.dev-dependencies]
2726
autoflake = "^1.4"

Diff for: src/pytkdocs/loader.py

+26-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
from pytkdocs.parsers.docstrings import PARSERS
2121
from pytkdocs.properties import RE_SPECIAL
2222

23+
try:
24+
from functools import cached_property # type: ignore
25+
except ImportError:
26+
from cached_property import cached_property # type: ignore
27+
2328

2429
class ObjectNode:
2530
"""
@@ -138,7 +143,16 @@ def is_property(self) -> bool:
138143
Returns:
139144
If this node's object is a property.
140145
"""
141-
return isinstance(self.obj, property)
146+
return isinstance(self.obj, property) or self.is_cached_property()
147+
148+
def is_cached_property(self) -> bool:
149+
"""
150+
Tell if this node's object is a cached property.
151+
152+
Returns:
153+
If this node's object is a cached property.
154+
"""
155+
return isinstance(self.obj, cached_property)
142156

143157
def parent_is_class(self) -> bool:
144158
"""
@@ -608,19 +622,27 @@ def get_property_documentation(self, node: ObjectNode) -> Attribute:
608622
"""
609623
prop = node.obj
610624
path = node.dotted_path
611-
properties = ["property", "readonly" if prop.fset is None else "writable"]
625+
properties = ["property"]
626+
if node.is_cached_property():
627+
# cached_property is always writable, see the docs
628+
properties.extend(["writable", "cached"])
629+
sig_source_func = prop.func
630+
else:
631+
properties.append("readonly" if prop.fset is None else "writable")
632+
sig_source_func = prop.fget
633+
612634
source: Optional[Source]
613635

614636
try:
615-
signature = inspect.signature(prop.fget)
637+
signature = inspect.signature(sig_source_func)
616638
except (TypeError, ValueError) as error:
617639
self.errors.append(f"Couldn't get signature for '{path}': {error}")
618640
attr_type = None
619641
else:
620642
attr_type = signature.return_annotation
621643

622644
try:
623-
source = Source(*inspect.getsourcelines(prop.fget))
645+
source = Source(*inspect.getsourcelines(sig_source_func))
624646
except (OSError, TypeError) as error:
625647
self.errors.append(f"Couldn't get source for '{path}': {error}")
626648
source = None

Diff for: tests/fixtures/cached_properties.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
try:
2+
from functools import cached_property
3+
except ImportError:
4+
from cached_property import cached_property
5+
6+
7+
class C:
8+
@cached_property
9+
def aaa(self):
10+
"""aaa"""

Diff for: tests/test_loader.py

+9
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,12 @@ def test_inherited_properties_docstrings():
497497
assert obj.docstring == "SuperClass.read_only docs"
498498
obj = loader.get_object_documentation("tests.fixtures.inherited_properties:SubClass.mutable")
499499
assert obj.docstring == "SuperClass.mutable getter docs"
500+
501+
502+
def test_loading_cached_properties():
503+
"""Load cached properties."""
504+
loader = Loader(new_path_syntax=True)
505+
obj = loader.get_object_documentation("tests.fixtures.cached_properties:C")
506+
assert len(obj.children) == 1
507+
assert obj.children[0].name == obj.children[0].docstring == "aaa"
508+
assert "cached" in obj.children[0].properties

0 commit comments

Comments
 (0)