Skip to content

Commit 993e4e1

Browse files
committed
feat: Implement extension
1 parent 053ade4 commit 993e4e1

4 files changed

Lines changed: 79 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ classifiers = [
2727
"Topic :: Utilities",
2828
"Typing :: Typed",
2929
]
30-
dependencies = []
30+
dependencies = [
31+
"griffe>=0.38",
32+
]
3133

3234
[project.urls]
3335
Homepage = "https://mkdocstrings.github.io/griffe-inherited-docstrings"

src/griffe_inherited_docstrings/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55

66
from __future__ import annotations
77

8-
__all__: list[str] = []
8+
from griffe_inherited_docstrings.extension import InheritDocstringsExtension
9+
10+
__all__: list[str] = ["InheritDocstringsExtension"]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""The Griffe extension."""
2+
3+
from __future__ import annotations
4+
5+
import contextlib
6+
from typing import TYPE_CHECKING
7+
8+
from griffe import Extension
9+
from griffe.exceptions import AliasResolutionError
10+
11+
if TYPE_CHECKING:
12+
from griffe import Docstring, Module, Object
13+
14+
15+
def _inherited_docstring(obj: Object) -> Docstring | None:
16+
for parent_class in obj.parent.mro(): # type: ignore[union-attr]
17+
try:
18+
if docstring := parent_class.members[obj.name].docstring:
19+
return docstring
20+
except KeyError:
21+
pass
22+
return None
23+
24+
25+
def _inherit_docstrings(obj: Object) -> None:
26+
if obj.is_module:
27+
for member in obj.members.values():
28+
if not member.is_alias:
29+
with contextlib.suppress(AliasResolutionError):
30+
_inherit_docstrings(member) # type: ignore[arg-type]
31+
elif obj.is_class:
32+
for member in obj.members.values():
33+
if not member.is_alias:
34+
if member.docstring is None and (inherited := _inherited_docstring(member)): # type: ignore[arg-type]
35+
member.docstring = inherited
36+
if member.is_class:
37+
_inherit_docstrings(member) # type: ignore[arg-type]
38+
39+
40+
class InheritDocstringsExtension(Extension):
41+
"""Griffe extension for inheriting docstrings."""
42+
43+
def on_package_loaded(self, *, pkg: Module) -> None:
44+
"""Inherit docstrings from parent classes once the whole package is loaded."""
45+
_inherit_docstrings(pkg)

tests/test_extension.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Tests for the extension."""
2+
3+
from __future__ import annotations
4+
5+
from griffe.extensions import Extensions
6+
from griffe.tests import temporary_visited_package
7+
8+
from griffe_inherited_docstrings import InheritDocstringsExtension
9+
10+
11+
def test_inherit_docstrings() -> None:
12+
"""Inherit docstrings from parent classes."""
13+
with temporary_visited_package(
14+
"package",
15+
modules={
16+
"__init__.py": """
17+
class Parent:
18+
def method(self):
19+
'''Docstring from parent method.'''
20+
21+
class Child(Parent):
22+
def method(self):
23+
...
24+
""",
25+
},
26+
extensions=Extensions(InheritDocstringsExtension()),
27+
) as package:
28+
assert package["Child.method"].docstring.value == package["Parent.method"].docstring.value

0 commit comments

Comments
 (0)