From: Vladimír Vondruš Date: Fri, 27 Sep 2024 22:48:30 +0000 (+0200) Subject: documentation/python: properly handle nested INPUT_MODULES. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=33265c8b79c9b2a2c460830394ad5cf9a27ead32;p=blog.git documentation/python: properly handle nested INPUT_MODULES. The use case is explicitly including a module that otherwise doesn't get transitively imported from the parents. They were treated as a standalone root module until now, causing quite a mess. Now they aren't, but there's an additional check requiring the user to explicitly supply also all parents. --- diff --git a/documentation/python.py b/documentation/python.py index 2bb70124..2465dc3a 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -620,8 +620,16 @@ def crawl_module(state: State, path: List[str], module) -> List[Tuple[List[str], state.crawled.add(id(module)) # This module isn't a duplicate, thus we can now safely add itself to - # parent's members (if there's a parent) - if len(path) > 1: state.name_map['.'.join(path[:-1])].members += [path[-1]] + # parent's members, if this is not a root module + if len(path) > 1: + # It's possible to supply nested modules in INPUT_MODULES, but for each + # such module all parents should be listed as well. We could probably + # add some dummies for the missing modules but the extra logic for + # deduplicating those if they get actually crawled later etc etc is + # way more complicated than just telling the user to list everything. + parent_path_str = '.'.join(path[:-1]) + assert parent_path_str in state.name_map, "%s listed in INPUT_MODULES without %s being known yet, add the parent explicitly earlier" % ('.'.join(path), parent_path_str) + state.name_map[parent_path_str].members += [path[-1]] module_entry = Empty() module_entry.type = EntryType.MODULE @@ -2720,8 +2728,12 @@ def run(basedir, config, *, templates=default_templates, search_add_lookahead_ba module = importlib.import_module(module) else: module_name = module.__name__ - modules_to_crawl += [([module_name], module)] - class_index += [module_name] + module_path = module_name.split('.') + modules_to_crawl += [(module_path, module)] + # Add the module to the class index only if it's a top-level one, + # otherwise expect that it'll appear somewhere deeper on its own + if len(module_path) == 1: + class_index += [module_name] while modules_to_crawl: path, object = modules_to_crawl.pop(0) if id(object) in state.crawled: continue diff --git a/documentation/test_python/inspect_string/classes.html b/documentation/test_python/inspect_string/classes.html index 13e5de16..ce78d977 100644 --- a/documentation/test_python/inspect_string/classes.html +++ b/documentation/test_python/inspect_string/classes.html @@ -36,13 +36,14 @@
  • module inspect_string A module diff --git a/documentation/test_python/test_inspect.py b/documentation/test_python/test_inspect.py index c5a9cf0e..7bd2a0a6 100644 --- a/documentation/test_python/test_inspect.py +++ b/documentation/test_python/test_inspect.py @@ -37,12 +37,16 @@ import m.sphinx class String(BaseInspectTestCase): def test(self): + sys.path.append(self.path) self.run_python({ 'LINKS_NAVBAR1': [ ('Modules', 'modules', []), ('Classes', 'classes', [])], + 'INPUT_MODULES': ['inspect_string', 'inspect_string.subpackage', 'inspect_string.subpackage.inner'] }) self.assertEqual(*self.actual_expected_contents('inspect_string.html')) + self.assertEqual(*self.actual_expected_contents('inspect_string.subpackage.html')) + self.assertEqual(*self.actual_expected_contents('inspect_string.subpackage.inner.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.another_module.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.FooSlots.html')) @@ -63,15 +67,18 @@ class Object(BaseInspectTestCase): # an object and not a string sys.path.append(os.path.join(os.path.dirname(self.path), 'inspect_string')) import inspect_string + import inspect_string.subpackage.inner self.run_python({ 'LINKS_NAVBAR1': [ ('Modules', 'modules', []), ('Classes', 'classes', [])], - 'INPUT_MODULES': [inspect_string] + 'INPUT_MODULES': [inspect_string, inspect_string.subpackage, inspect_string.subpackage.inner] }) # The output should be the same as when inspecting a string self.assertEqual(*self.actual_expected_contents('inspect_string.html', '../inspect_string/inspect_string.html')) + self.assertEqual(*self.actual_expected_contents('inspect_string.subpackage.html', '../inspect_string/inspect_string.subpackage.html')) + self.assertEqual(*self.actual_expected_contents('inspect_string.subpackage.inner.html', '../inspect_string/inspect_string.subpackage.inner.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.another_module.html', '../inspect_string/inspect_string.another_module.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html', '../inspect_string/inspect_string.Foo.html')) self.assertEqual(*self.actual_expected_contents('inspect_string.FooSlots.html', '../inspect_string/inspect_string.FooSlots.html'))