From fa4683d8f7c70a60aa839e8c820d6ac448a2a2e8 Mon Sep 17 00:00:00 2001 From: Robin Dunn <> Date: Fri, 9 Aug 2019 14:59:54 -0700 Subject: [PATCH] * Fix scanning extension modules for docstrings * Support documenting classes that are in a package's __init__ module --- build.py | 2 +- sphinxtools/librarydescription.py | 71 ++++++++++++++++++++----------- sphinxtools/modulehunter.py | 41 +++++++++++------- 3 files changed, 74 insertions(+), 40 deletions(-) diff --git a/build.py b/build.py index a342493e..03f2998d 100755 --- a/build.py +++ b/build.py @@ -1092,7 +1092,7 @@ def cmd_sphinx(options, args): def cmd_wxlib(options, args): from sphinxtools.modulehunter import ModuleHunter - cmdTimer = CommandTimer('wx.lib') + cmdTimer = CommandTimer('wxlib') pwd = pushDir(phoenixDir()) for wx_pkg in ['lib', 'py', 'svg', 'tools']: diff --git a/sphinxtools/librarydescription.py b/sphinxtools/librarydescription.py index 6f99e952..56d48134 100644 --- a/sphinxtools/librarydescription.py +++ b/sphinxtools/librarydescription.py @@ -43,23 +43,33 @@ def generic_summary(libraryItem, stream): if libraryItem.kind in [object_types.LIBRARY, object_types.PACKAGE]: list1 = libraryItem.GetItemByKind(object_types.PACKAGE) list2 = libraryItem.GetItemByKind(object_types.PY_MODULE, object_types.PYW_MODULE) + list3 = libraryItem.GetItemByKind(object_types.FUNCTION) + list4 = libraryItem.GetItemByKind(object_types.KLASS, recurse=True) - templ = [templates.TEMPLATE_PACKAGE_SUMMARY, templates.TEMPLATE_MODULE_SUMMARY] - refs = ['mod', 'mod'] + all_lists = [list1, list2, list3, list4] + templ = [ templates.TEMPLATE_PACKAGE_SUMMARY, + templates.TEMPLATE_MODULE_SUMMARY, + templates.TEMPLATE_STD_FUNCTION_SUMMARY, + templates.TEMPLATE_STD_CLASS_SUMMARY + ] + refs = ['mod', 'mod', 'func', 'ref'] + add_tilde = [True, True, True, True] elif libraryItem.kind in range(object_types.PY_MODULE, object_types.PYW_MODULE+1): list1 = libraryItem.GetItemByKind(object_types.FUNCTION) list2 = libraryItem.GetItemByKind(object_types.KLASS, recurse=True) + all_lists = [list1, list2] templ = [templates.TEMPLATE_STD_FUNCTION_SUMMARY, templates.TEMPLATE_STD_CLASS_SUMMARY] refs = ['func', 'ref'] add_tilde = [True, True] elif libraryItem.kind == object_types.KLASS: write_toc = False - list1 = libraryItem.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD) + list1 = libraryItem.GetItemByKind(object_types.METHOD, object_types.BUILTIN_FUNCTION) list2 = libraryItem.GetItemByKind(object_types.PROPERTY) + all_lists = [list1, list2] templ = [templates.TEMPLATE_METHOD_SUMMARY, templates.TEMPLATE_PROPERTY_SUMMARY] refs = ['meth', 'attr'] add_tilde = [True, True] @@ -69,7 +79,7 @@ def generic_summary(libraryItem, stream): toctree = '' - for index, sub_list in enumerate([list1, list2]): + for index, sub_list in enumerate(all_lists): table = [] for item in sub_list: @@ -739,7 +749,7 @@ class Class(ParentBase): stream.write(docs + '\n\n') - methods = self.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD) + methods = self.GetItemByKind(object_types.METHOD, object_types.BUILTIN_FUNCTION) properties = self.GetItemByKind(object_types.PROPERTY) for meth in methods: @@ -776,8 +786,8 @@ class Class(ParentBase): self.signature = self.signature.strip() - if len(self.signature) < 2: - self.is_redundant = True + # if len(self.signature) < 2: # ??? + # self.is_redundant = True if self.GetShortName().startswith('__test') or '.extern.' in self.name: self.is_redundant = True @@ -785,7 +795,7 @@ class Class(ParentBase): if self.is_redundant: return - methods = self.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD) + methods = self.GetItemByKind(object_types.METHOD, object_types.BUILTIN_FUNCTION) method_list = [] for meth in methods: @@ -901,7 +911,18 @@ class Method(ChildrenBase): self.signature = self.signature.replace('*', r'\*') if not self.signature.strip(): - self.is_redundant = True + # if there is no signature, then check if the first line of + # docstring looks like it might be it + lines = self.docs.split('\n') + first = lines[0] + rest = '\n'.join(lines[1:]) if len(lines) > 1 else '' + sig_start = self.GetShortName() + '(' + if sig_start in first: + self.signature = first[first.find(sig_start):] + self.docs = rest.strip() + + # if not self.signature.strip(): # ??? + # self.is_redundant = True def Write(self, stream): @@ -944,21 +965,23 @@ class Property(ChildrenBase): self.getter = self.setter = self.deleter = '' - try: - if item.fget: - self.getter = item.fget.__name__ - if item.fset: - self.setter = item.fset.__name__ - if item.fdel: - self.deleter = item.fdel.__name__ - except AttributeError: - # Thank you for screwing it up, Cython... - if item.fget: - self.getter = item.fget.__class__.__name__ - if item.fset: - self.setter = item.fset.__class__.__name__ - if item.fdel: - self.deleter = item.fdel.__class__.__name__ + # is it a real property? + if isinstance(item, property): + try: + if item.fget: + self.getter = item.fget.__name__ + if item.fset: + self.setter = item.fset.__name__ + if item.fdel: + self.deleter = item.fdel.__name__ + except AttributeError: + # Thank you for screwing it up, Cython... + if item.fget: + self.getter = item.fget.__class__.__name__ + if item.fset: + self.setter = item.fset.__class__.__name__ + if item.fdel: + self.deleter = item.fdel.__class__.__name__ self.docs = getdoc(item) self.comments = getcomments(item) diff --git a/sphinxtools/modulehunter.py b/sphinxtools/modulehunter.py index d1d20526..c256dd54 100644 --- a/sphinxtools/modulehunter.py +++ b/sphinxtools/modulehunter.py @@ -7,7 +7,6 @@ import os import sys import types -import importlib import traceback import pkgutil @@ -219,12 +218,14 @@ def inspect_source(method_class, obj, source): def is_classmethod(instancemethod): """ Determine if an instancemethod is a classmethod. """ - attribute = (isPython3() and ['__self__'] or ['im_self'])[0] + # attribute = (isPython3() and ['__self__'] or ['im_self'])[0] + # if hasattr(instancemethod, attribute): + # return getattr(instancemethod, attribute) is not None + # return False - if hasattr(instancemethod, attribute): - return getattr(instancemethod, attribute) is not None - - return False + return isinstance( + instancemethod, + (classmethod, types.MethodType, types.ClassMethodDescriptorType)) def describe_func(obj, parent_class, module_name): @@ -279,7 +280,10 @@ def describe_func(obj, parent_class, module_name): klass.number_lines = '%d' % len(source_code.split('\n')) if isinstance(obj, staticmethod): - klass.method = method = object_types.STATIC_METHOD + klass.kind = method = object_types.STATIC_METHOD + + if is_classmethod(obj): + klass.kind = method = object_types.CLASS_METHOD try: code = None @@ -338,7 +342,8 @@ def describe_class(obj, module_class, module_name, constants): continue try: - item = getattr(obj, name) + # item = getattr(obj, name) # ???? + item = obj_dict.get(name) except AttributeError: # Thanks to ReportLab for this funny exception... continue @@ -351,13 +356,10 @@ def describe_class(obj, module_class, module_name, constants): if ismodule(item): continue - if ismemberdescriptor(item) or isgetsetdescriptor(item): - continue - if isbuiltin(item): count += 1 elif ismethod(item) or isfunction(item) or ismethoddescriptor(item) or \ - isinstance(item, types.MethodType): + isinstance(item, (classmethod, types.MethodType, types.ClassMethodDescriptorType)): count += 1 describe_func(item, klass, module_name) elif isclass(item): @@ -365,7 +367,7 @@ def describe_class(obj, module_class, module_name, constants): describe_class(item, klass, module_name, constants) else: name = class_name + '.' + name - if isinstance(item, property): + if isinstance(item, property) or isgetsetdescriptor(item): item_class = Property(name, item) klass.Add(item_class) @@ -406,6 +408,12 @@ def describe_class(obj, module_class, module_name, constants): klass.signature = description.strip() klass.number_lines = '%d' % len(source_code.split('\n')) + if not klass.signature: + if klass.superClasses: + klass.signature = '{}({})'.format(obj.__name__, ','.join(klass.superClasses)) + else: + klass.signature = '{}(object)'.format(obj.__name__) + def describe_module(module, kind, constants=[]): """ @@ -478,7 +486,9 @@ def Import(init_name, import_name, full_process=True): sys.path.insert(0, dirname) try: - mainmod = importlib.import_module(import_name) + #mainmod = importlib.import_module(import_name) + terminal_module = import_name.split('.')[-1] + mainmod = __import__(import_name, globals(), fromlist=[terminal_module]) except (ImportError, NameError): message = format_traceback() print('Error: %s' % message) @@ -527,7 +537,8 @@ def FindModuleType(filename): def SubImport(import_string, module, parent_class, ispkg): try: - submod = importlib.import_module(import_string) + #submod = importlib.import_module(import_string) + submod = __import__(import_string, globals(), fromlist=[module]) except: # pubsub and Editra can be funny sometimes... message = "Unable to import module/package '%s.%s'.\n Exception was: %s"%(import_string, module, format_traceback())