diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 7b6c873e..c02213b1 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -761,6 +761,24 @@ def iter_modules(paths): if m.is_namespace and not m.doc: del self.doc[root] self._context.pop(m.refname) + elif hasattr(self.obj, '__all__'): + # Python extension modules don't get recognized by `is_package` because they have no + # "__path__" attribute. We treat them here separately. We support submodules of + # extension modules only if they are explicitly exposed via the "__all__" attribute + # because otherwise it's hard to distinguish proper submodules from re-exports (i.e., + # the function `is_from_this_module` doesn't work on submodules). + for name, obj in public_objs: + if inspect.ismodule(obj) and not hasattr(obj, '__file__') and name not in self.doc: + try: + m = Module( + obj, docfilter=docfilter, supermodule=self, + context=self._context, skip_errors=skip_errors) + except Exception as ex: + if skip_errors: + warn(str(ex), Module.ImportWarning) + continue + raise + self.doc[name] = m # Apply docfilter if docfilter: diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index c967dc87..d95e6191 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -472,6 +472,35 @@ def test_module(self): self.assertEqual(sorted(m.name for m in m.submodules()), [EXAMPLE_MODULE + '.' + m for m in submodules]) + @ignore_warnings + def test_module_without_path(self): + # GH-319: https://github.com/pdoc3/pdoc/issues/319 + parent_module = ModuleType('parent_module') + child_module1 = ModuleType('child_module1') + child_module2 = ModuleType('child_module2') + grandchild_module = ModuleType('grandchild_module') + + child_module1.grandchild_module = grandchild_module + child_module1.__all__ = ['grandchild_module'] + + parent_module.child_module1 = child_module1 + parent_module.child_module2 = child_module2 + parent_module.__all__ = ['child_module1', 'child_module2'] + + assert not hasattr(parent_module, '__path__') + assert not hasattr(child_module1, '__path__') + assert not hasattr(child_module2, '__path__') + assert not hasattr(grandchild_module, '__path__') + + parent_module_pdoc = pdoc.Module(parent_module) + + children_modules_pdoc = sorted(parent_module_pdoc.submodules(), key=lambda m: m.name) + self.assertEqual( + [m.name for m in children_modules_pdoc], ['child_module1', 'child_module2']) + self.assertEqual( + [m.name for m in children_modules_pdoc[0].submodules()], ['grandchild_module']) + self.assertEqual(children_modules_pdoc[1].submodules(), []) + def test_Module_find_class(self): class A: pass