From 33265c8b79c9b2a2c460830394ad5cf9a27ead32 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 28 Sep 2024 00:48:30 +0200 Subject: [PATCH] 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. --- documentation/python.py | 20 ++++-- .../test_python/inspect_string/classes.html | 3 +- .../inspect_string/inspect_string.html | 4 +- .../inspect_string.subpackage.html | 69 +++++++++++++++++++ .../inspect_string.subpackage.inner.html | 45 ++++++++++++ .../inspect_string/subpackage/inner.py | 5 ++ .../test_python/inspect_string/modules.html | 7 +- documentation/test_python/test_inspect.py | 9 ++- 8 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 documentation/test_python/inspect_string/inspect_string.subpackage.html create mode 100644 documentation/test_python/inspect_string/inspect_string.subpackage.inner.html create mode 100644 documentation/test_python/inspect_string/inspect_string/subpackage/inner.py 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')) -- 2.30.2