Coverage for src/griffe_inherited_docstrings/_internal/extension.py: 89.61%
43 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 17:16 +0200
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 17:16 +0200
1from __future__ import annotations
3import contextlib
4from typing import TYPE_CHECKING, Any
6from griffe import AliasResolutionError, Docstring, Extension
8if TYPE_CHECKING:
9 from griffe import Module, Object
12def _docstring_above(obj: Object) -> Docstring | None:
13 with contextlib.suppress(IndexError, KeyError):
14 for parent in obj.parent.mro(): # type: ignore[union-attr]
15 # Fetch docstring from first parent that has the member.
16 if obj.name in parent.members:
17 return parent.members[obj.name].docstring
18 return None
21def _inherit_docstrings(obj: Object, *, merge: bool = False, seen: set[str] | None = None) -> None:
22 if seen is None: 22 ↛ 23line 22 didn't jump to line 23 because the condition on line 22 was never true
23 seen = set()
25 if obj.path in seen:
26 return
28 seen.add(obj.path)
30 if obj.is_module:
31 for member in obj.members.values():
32 if not member.is_alias: 32 ↛ 31line 32 didn't jump to line 31 because the condition on line 32 was always true
33 with contextlib.suppress(AliasResolutionError):
34 _inherit_docstrings(member, merge=merge, seen=seen) # type: ignore[arg-type]
36 elif obj.is_class: 36 ↛ exitline 36 didn't return from function '_inherit_docstrings' because the condition on line 36 was always true
37 # Recursively handle top-most parents first.
38 # It means that we can just check the first parent with the member
39 # when actually inheriting (and optionally merging) a docstring,
40 # since the docstrings of the other parents have already been inherited.
41 for parent in reversed(obj.mro()): # type: ignore[attr-defined]
42 _inherit_docstrings(parent, merge=merge, seen=seen)
44 for member in obj.members.values():
45 if not member.is_alias: 45 ↛ 44line 45 didn't jump to line 44 because the condition on line 45 was always true
46 if docstring_above := _docstring_above(member): # type: ignore[arg-type]
47 if merge:
48 if member.docstring is None:
49 member.docstring = Docstring(
50 docstring_above.value,
51 parent=member, # type: ignore[arg-type]
52 parser=docstring_above.parser,
53 parser_options=docstring_above.parser_options,
54 )
55 elif member.docstring.value:
56 member.docstring.value = docstring_above.value + "\n\n" + member.docstring.value
57 else:
58 member.docstring.value = docstring_above.value
59 elif member.docstring is None: 59 ↛ 61line 59 didn't jump to line 61 because the condition on line 59 was always true
60 member.docstring = docstring_above
61 if member.is_class: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 _inherit_docstrings(member, merge=merge, seen=seen) # type: ignore[arg-type]
65class InheritDocstringsExtension(Extension):
66 """Griffe extension for inheriting docstrings."""
68 def __init__(self, *, merge: bool = False) -> None:
69 """Initialize the extension by setting the merge flag.
71 Parameters:
72 merge: Whether to merge the docstrings from the parent classes into the docstring of the member.
73 """
74 self.merge = merge
75 """Whether to merge the docstrings from the parent classes into the docstring of the member."""
77 def on_package(self, *, pkg: Module, **kwargs: Any) -> None: # noqa: ARG002
78 """Inherit docstrings from parent classes once the whole package is loaded."""
79 _inherit_docstrings(pkg, merge=self.merge, seen=set())