2323import inspect
2424from typing import TYPE_CHECKING
2525from typing import Any
26- from typing import ClassVar
27- from typing import Literal
2826
2927from griffe import Alias
3028from griffe import Attribute
3129from griffe import Docstring
3230from griffe import Extension
3331from griffe import ObjectNode
32+ from griffe import Parser
3433from griffe import dynamic_import
3534from griffe import get_logger
3635
36+ from docstring_inheritance import _BaseGoogleDocstringInheritanceMeta
37+ from docstring_inheritance import _BaseNumpyDocstringInheritanceMeta
38+
3739if 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
4956class 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