Skip to content

Commit c115ad9

Browse files
author
AntoineD
committed
Griffe plugin is more robust
1 parent 70670e4 commit c115ad9

File tree

3 files changed

+332
-221
lines changed

3 files changed

+332
-221
lines changed

src/docstring_inheritance/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ def __init__(
8181
)
8282

8383

84-
class GoogleDocstringInheritanceMeta(_BaseDocstringInheritanceMeta):
84+
class _BaseGoogleDocstringInheritanceMeta(_BaseDocstringInheritanceMeta):
85+
pass
86+
87+
88+
class GoogleDocstringInheritanceMeta(_BaseGoogleDocstringInheritanceMeta):
8589
"""Metaclass for inheriting docstrings in Google format."""
8690

8791
def __init__(
@@ -99,7 +103,7 @@ def __init__(
99103
)
100104

101105

102-
class GoogleDocstringInheritanceInitMeta(_BaseDocstringInheritanceMeta):
106+
class GoogleDocstringInheritanceInitMeta(_BaseGoogleDocstringInheritanceMeta):
103107
"""Metaclass for inheriting docstrings in Google format with init-in-class."""
104108

105109
def __init__(
@@ -117,7 +121,11 @@ def __init__(
117121
)
118122

119123

120-
class NumpyDocstringInheritanceMeta(_BaseDocstringInheritanceMeta):
124+
class _BaseNumpyDocstringInheritanceMeta(_BaseDocstringInheritanceMeta):
125+
pass
126+
127+
128+
class NumpyDocstringInheritanceMeta(_BaseNumpyDocstringInheritanceMeta):
121129
"""Metaclass for inheriting docstrings in Numpy format."""
122130

123131
def __init__(
@@ -135,7 +143,7 @@ def __init__(
135143
)
136144

137145

138-
class NumpyDocstringInheritanceInitMeta(_BaseDocstringInheritanceMeta):
146+
class NumpyDocstringInheritanceInitMeta(_BaseNumpyDocstringInheritanceMeta):
139147
"""Metaclass for inheriting docstrings in Numpy format with init-in-class."""
140148

141149
def __init__(

src/docstring_inheritance/griffe.py

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,38 +23,39 @@
2323
import inspect
2424
from typing import TYPE_CHECKING
2525
from typing import Any
26-
from typing import ClassVar
27-
from typing import Literal
2826

2927
from griffe import Alias
3028
from griffe import Attribute
3129
from griffe import Docstring
3230
from griffe import Extension
3331
from griffe import ObjectNode
32+
from griffe import Parser
3433
from griffe import dynamic_import
3534
from griffe import get_logger
3635

36+
from docstring_inheritance import _BaseGoogleDocstringInheritanceMeta
37+
from docstring_inheritance import _BaseNumpyDocstringInheritanceMeta
38+
3739
if TYPE_CHECKING:
3840
import ast
41+
from typing import Literal
3942

4043
from griffe import Class
44+
from griffe import DocstringOptions
45+
from griffe import DocstringStyle
4146
from griffe import Inspector
4247
from griffe import Object
43-
from griffe import Parser
4448
from griffe import Visitor
4549

50+
ParserType = Parser | DocstringStyle | None
51+
DocstringParser = Parser | Literal[""]
52+
4653
_logger = get_logger(__name__)
4754

4855

4956
class DocstringInheritance(Extension):
5057
"""Inherit docstrings when the package docstring-inheritance is used."""
5158

52-
__parser: Literal["google", "numpy", "sphinx"] | Parser | None = None
53-
"""The docstring parser."""
54-
55-
__parser_options: ClassVar[dict[str, Any]] = {}
56-
"""The docstring parser options."""
57-
5859
def on_class_members( # noqa: D102
5960
self,
6061
*,
@@ -69,33 +70,46 @@ def on_class_members( # noqa: D102
6970

7071
runtime_cls = self.__import_dynamically(cls)
7172

72-
if not self.__has_docstring_inheritance(runtime_cls):
73+
docstring_parser_kind = self.__get_inherited_docstring_parser_kind(runtime_cls)
74+
if not docstring_parser_kind:
7375
return
7476

77+
parser_options = agent.docstring_options
78+
7579
# Inherit the class docstring.
76-
self.__set_docstring(cls, runtime_cls)
80+
self.__set_docstring(cls, runtime_cls, docstring_parser_kind, parser_options)
7781

7882
# Inherit the methods docstrings.
7983
for member in cls.members.values():
8084
if not isinstance(member, Attribute):
81-
runtime_obj = self.__import_dynamically(member)
82-
self.__set_docstring(member, runtime_obj)
85+
runtime_member = self.__import_dynamically(member)
86+
self.__set_docstring(
87+
member, runtime_member, docstring_parser_kind, parser_options
88+
)
8389

8490
@staticmethod
8591
def __import_dynamically(obj: Object | Alias) -> Any:
8692
"""Import dynamically and return an object."""
8793
try:
8894
return dynamic_import(obj.path)
8995
except ImportError:
90-
_logger.debug("Could not get dynamic docstring for %s", obj.path)
96+
_logger.debug("Can not get the dynamic imported docstring for %s", obj.path)
9197

9298
@classmethod
93-
def __set_docstring(cls, obj: Object | Alias, runtime_obj: Any) -> None:
99+
def __set_docstring(
100+
cls,
101+
obj: Object | Alias,
102+
runtime_obj: Any,
103+
docstring_parser: Parser,
104+
parser_options: DocstringOptions,
105+
) -> None:
94106
"""Set the docstring from a runtime object.
95107
96108
Args:
97109
obj: The griffe object.
98110
runtime_obj: The runtime object.
111+
docstring_parser: The docstring parser.
112+
parser_options: The parser options.
99113
"""
100114
if runtime_obj is None:
101115
return
@@ -114,41 +128,36 @@ def __set_docstring(cls, obj: Object | Alias, runtime_obj: Any) -> None:
114128
obj.docstring.value = inspect.cleandoc(docstring)
115129
else:
116130
assert not isinstance(obj, Alias)
117-
cls.__find_parser(obj)
118131
obj.docstring = Docstring(
119132
docstring,
120133
parent=obj,
121-
parser=cls.__parser,
122-
parser_options=cls.__parser_options,
134+
parser=docstring_parser,
135+
parser_options=parser_options,
123136
)
124137

125-
@staticmethod
126-
def __has_docstring_inheritance(cls: type[Any]) -> bool:
127-
"""Return whether a class has docstring inheritance."""
128-
for base in cls.__class__.__mro__:
129-
if base.__name__.endswith("DocstringInheritanceMeta"):
130-
return True
131-
return False
132-
133138
@classmethod
134-
def __find_parser(cls, obj: Object) -> None:
135-
"""Search a docstring parser recursively from an object parents."""
136-
if cls.__parser is not None:
137-
return
138-
139-
parent = obj.parent
140-
if parent is None:
141-
msg = f"Cannot find a parent of the object {obj}"
142-
raise RuntimeError(msg)
143-
144-
if parent.docstring is None:
145-
msg = f"Cannot find a docstring for the parent of the object {obj}"
146-
raise RuntimeError(msg)
139+
def __get_inherited_docstring_parser_kind(
140+
cls,
141+
class_: type[Any],
142+
) -> DocstringParser:
143+
"""Return the inherited docstring parser kind."""
144+
# Is it the first class that has a metaclass defined which has
145+
# DocstringInheritanceMeta in its hierarchy?
146+
# In other words, none of its - eventually multiple inherited - base classes
147+
# has a metaclass with DocstringInheritanceMeta.
148+
for base_class in class_.__mro__[1:]:
149+
if docstring_style := cls.__get_parser_kind(base_class):
150+
return docstring_style
151+
return ""
147152

148-
parser = parent.docstring.parser
149-
150-
if parser is None:
151-
cls.__find_parser(parent)
152-
else:
153-
cls.__parser = parser
154-
cls.__parser_options = parent.docstring.parser_options
153+
@staticmethod
154+
def __get_parser_kind(
155+
class_: type[Any],
156+
) -> DocstringParser:
157+
"""Return the docstring parser kind."""
158+
for base_meta in class_.__class__.__mro__:
159+
if issubclass(base_meta, _BaseGoogleDocstringInheritanceMeta):
160+
return Parser.google
161+
if issubclass(base_meta, _BaseNumpyDocstringInheritanceMeta):
162+
return Parser.numpy
163+
return ""

0 commit comments

Comments
 (0)