@@ -199,56 +199,46 @@ def _pairwise(iterable):
199199 return zip (a , b )
200200
201201
202- def _var_docstrings (doc_obj : Union ['Module' , 'Class' ], * ,
203- _init_tree : ast .FunctionDef = None ) -> Dict [str , 'Variable' ]:
202+ def _pep224_docstrings (doc_obj : Union ['Module' , 'Class' ], * ,
203+ _init_tree = None ) -> Tuple [Dict [str , str ],
204+ Dict [str , str ]]:
204205 """
205- Extracts docstrings for variables of `doc_obj`
206+ Extracts PEP-224 docstrings for variables of `doc_obj`
206207 (either a `pdoc.Module` or `pdoc.Class`).
207208
208- Returns a dict mapping variable names to `pdoc.Variable` objects.
209-
210- For `pdoc.Class` objects, the dict contains class' instance
211- variables (defined as `self.something` in class' `__init__`),
212- recognized by `Variable.instance_var == True`.
209+ Returns a tuple of two dicts mapping variable names to their docstrings.
210+ The second dict contains instance variables and is non-empty only in case
211+ `doc_obj` is a `pdoc.Class` which has `__init__` method.
213212 """
213+ # No variables in namespace packages
214+ if isinstance (doc_obj , Module ) and doc_obj .is_namespace :
215+ return {}, {}
216+
217+ vars = {} # type: Dict[str, str]
218+ instance_vars = {} # type: Dict[str, str]
219+
214220 if _init_tree :
215- tree = _init_tree # type: Union[ast.Module, ast.FunctionDef]
221+ tree = _init_tree
216222 else :
217- # No variables in namespace packages
218- if isinstance (doc_obj , Module ) and doc_obj .is_namespace :
219- return {}
220223 try :
221224 tree = ast .parse (inspect .getsource (doc_obj .obj ))
222225 except (OSError , TypeError , SyntaxError ):
223226 warn ("Couldn't get/parse source of '{!r}'" .format (doc_obj ))
224- return {}
225- if isinstance (doc_obj , Class ):
226- tree = tree .body [0 ] # type: ignore # ast.parse creates a dummy ast.Module wrapper
227-
228- vs = {} # type: Dict[str, Variable]
229-
230- cls = None
231- module = doc_obj
232- module_all = set (getattr (module .obj , '__all__' , ()))
233- member_obj = dict (inspect .getmembers (doc_obj .obj )).get
227+ return {}, {}
234228
235- if isinstance (doc_obj , Class ):
236- cls = doc_obj
237- module = doc_obj .module
229+ if isinstance (doc_obj , Class ):
230+ tree = tree .body [0 ] # ast.parse creates a dummy ast.Module wrapper
238231
239- # For classes, first add instance variables defined in __init__
240- if not _init_tree :
241- # Recursive call with just the __init__ tree
232+ # For classes, maybe add instance variables defined in __init__
242233 for node in tree .body :
243234 if isinstance (node , ast .FunctionDef ) and node .name == '__init__' :
244- vs . update ( _var_docstrings ( doc_obj , _init_tree = node ) )
235+ instance_vars , _ = _pep224_docstrings ( doc_obj , _init_tree = node )
245236 break
246237
247238 try :
248239 ast_AnnAssign = ast .AnnAssign # type: Type
249240 except AttributeError : # Python < 3.6
250241 ast_AnnAssign = type (None )
251-
252242 ast_Assignments = (ast .Assign , ast_AnnAssign )
253243
254244 for assign_node , str_node in _pairwise (ast .iter_child_nodes (tree )):
@@ -275,20 +265,13 @@ def _var_docstrings(doc_obj: Union['Module', 'Class'], *,
275265 else :
276266 continue
277267
278- if not _is_public (name ):
279- continue
280-
281- if module_all and name not in module_all :
282- continue
283-
284268 docstring = inspect .cleandoc (str_node .value .s ).strip ()
285269 if not docstring :
286270 continue
287271
288- vs [name ] = Variable (name , module , docstring ,
289- obj = member_obj (name ),
290- cls = cls , instance_var = bool (_init_tree ))
291- return vs
272+ vars [name ] = docstring
273+
274+ return vars , instance_vars
292275
293276
294277def _is_public (ident_name ):
@@ -299,6 +282,10 @@ def _is_public(ident_name):
299282 return not ident_name .startswith ("_" )
300283
301284
285+ def _is_function (obj ):
286+ return inspect .isroutine (obj ) and callable (obj )
287+
288+
302289def _filter_type (type : Type [T ],
303290 values : Union [Iterable ['Doc' ], Dict [str , 'Doc' ]]) -> List [T ]:
304291 """
@@ -541,6 +528,8 @@ def __init__(self, module: Union[ModuleType, str], *, docfilter: Callable[[Doc],
541528 self ._is_inheritance_linked = False
542529 """Re-entry guard for `pdoc.Module._link_inheritance()`."""
543530
531+ var_docstrings , _ = _pep224_docstrings (self )
532+
544533 # Populate self.doc with this module's public members
545534 if hasattr (self .obj , '__all__' ):
546535 public_objs = []
@@ -558,16 +547,17 @@ def is_from_this_module(obj):
558547 public_objs = [(name , inspect .unwrap (obj ))
559548 for name , obj in inspect .getmembers (self .obj )
560549 if (_is_public (name ) and
561- is_from_this_module (obj ))]
550+ ( is_from_this_module (obj ) or name in var_docstrings ))]
562551 index = list (self .obj .__dict__ ).index
563552 public_objs .sort (key = lambda i : index (i [0 ]))
553+
564554 for name , obj in public_objs :
565- if inspect . isroutine (obj ):
555+ if _is_function (obj ):
566556 self .doc [name ] = Function (name , self , obj )
567557 elif inspect .isclass (obj ):
568558 self .doc [name ] = Class (name , self , obj )
569-
570- self .doc . update ( _var_docstrings ( self ) )
559+ elif name in var_docstrings :
560+ self .doc [ name ] = Variable ( name , self , var_docstrings [ name ], obj = obj )
571561
572562 # If the module is a package, scan the directory for submodules
573563 if self .is_package :
@@ -804,8 +794,6 @@ def __init__(self, name, module, obj, *, docstring=None):
804794 self .doc = {}
805795 """A mapping from identifier name to a `pdoc.Doc` objects."""
806796
807- self .doc .update (_var_docstrings (self ))
808-
809797 public_objs = [(name , inspect .unwrap (obj ))
810798 for name , obj in inspect .getmembers (self .obj )
811799 # Filter only *own* members. The rest are inherited
@@ -814,27 +802,30 @@ def __init__(self, name, module, obj, *, docstring=None):
814802 index = list (self .obj .__dict__ ).index
815803 public_objs .sort (key = lambda i : index (i [0 ]))
816804
805+ var_docstrings , instance_var_docstrings = _pep224_docstrings (self )
806+
817807 # Convert the public Python objects to documentation objects.
818808 for name , obj in public_objs :
819- if name in self .doc and self .doc [name ].docstring :
820- continue
821- if inspect .isroutine (obj ):
809+ if _is_function (obj ):
822810 self .doc [name ] = Function (
823811 name , self .module , obj , cls = self ,
824812 method = not self ._method_type (self .obj , name ))
825- elif (inspect .isdatadescriptor (obj ) or
826- inspect .isgetsetdescriptor (obj ) or
827- inspect .ismemberdescriptor (obj )):
828- self .doc [name ] = Variable (
829- name , self .module , inspect .getdoc (obj ),
830- obj = getattr (obj , 'fget' , obj ),
831- cls = self , instance_var = True )
832813 else :
833814 self .doc [name ] = Variable (
834815 name , self .module ,
835- docstring = isinstance (obj , type ) and inspect .getdoc (obj ) or "" ,
836- cls = self ,
837- instance_var = name in getattr (self .obj , "__slots__" , ()))
816+ docstring = var_docstrings .get (name ) or inspect .getdoc (obj ), cls = self ,
817+ obj = getattr (obj , 'fget' , getattr (obj , '__get__' , obj )),
818+ instance_var = (inspect .isdatadescriptor (obj ) or
819+ inspect .ismethoddescriptor (obj ) or
820+ inspect .isgetsetdescriptor (obj ) or
821+ inspect .ismemberdescriptor (obj ) or
822+ name in getattr (self .obj , '__slots__' , ())))
823+
824+ for name , docstring in instance_var_docstrings .items ():
825+ self .doc [name ] = Variable (
826+ name , self .module , docstring , cls = self ,
827+ obj = getattr (self .obj , name , None ),
828+ instance_var = True )
838829
839830 @staticmethod
840831 def _method_type (cls : type , name : str ):
@@ -1024,7 +1015,7 @@ def __init__(self, name, module, obj, *, cls: Class = None, method=False):
10241015 `method` should be `True` when the function is a method. In
10251016 all other cases, it should be `False`.
10261017 """
1027- assert callable (obj )
1018+ assert callable (obj ), ( name , module , obj )
10281019 super ().__init__ (name , module , obj )
10291020
10301021 self .cls = cls
0 commit comments