diff --git a/autoapi/_mapper.py b/autoapi/_mapper.py index b2454cb8..13ac9f70 100644 --- a/autoapi/_mapper.py +++ b/autoapi/_mapper.py @@ -1,6 +1,7 @@ import collections import copy import fnmatch +import itertools import operator import os import re @@ -27,7 +28,6 @@ PythonAttribute, PythonData, PythonException, - TopLevelPythonPythonMapper, ) from .settings import OWN_PAGE_LEVELS, TEMPLATE_DIR @@ -333,25 +333,6 @@ def find_files(patterns, dirs, ignore): yield filename seen.add(norm_name) - def add_object(self, obj): - """Add object to local and app environment storage - - Args: - obj: Instance of a AutoAPI object - """ - display = obj.display - if display and obj.type in self.own_page_types: - self.objects_to_render[obj.id] = obj - - self.all_objects[obj.id] = obj - child_stack = list(obj.children) - while child_stack: - child = child_stack.pop() - self.all_objects[child.id] = child - if display and child.type in self.own_page_types: - self.objects_to_render[child.id] = child - child_stack.extend(getattr(child, "children", ())) - def output_rst(self, source_suffix): for _, obj in status_iterator( self.objects_to_render.items(), @@ -499,27 +480,50 @@ def map(self, options=None): stringify_func=(lambda x: x[0]), ): for obj in self.create_class(data, options=options): - self.add_object(obj) - - top_level_objects = { - obj.id: obj - for obj in self.all_objects.values() - if isinstance(obj, TopLevelPythonPythonMapper) - } - parents = {obj.name: obj for obj in top_level_objects.values()} - for obj in top_level_objects.values(): + self.all_objects[obj.id] = obj + + self._create_module_hierarchy() + self._render_selection() + + self.app.env.autoapi_objects = self.objects_to_render + self.app.env.autoapi_all_objects = self.all_objects + + def _create_module_hierarchy(self) -> None: + """Populate the sub{module,package}s attributes of all top level objects.""" + for obj in self.all_objects.values(): parent_name = obj.name.rsplit(".", 1)[0] - if parent_name in parents and parent_name != obj.name: - parent = parents[parent_name] + if parent_name in self.all_objects and parent_name != obj.name: + parent = self.all_objects[parent_name] attr = f"sub{obj.type}s" getattr(parent, attr).append(obj) - for obj in top_level_objects.values(): + for obj in self.all_objects.values(): obj.submodules.sort() obj.subpackages.sort() - self.app.env.autoapi_objects = self.objects_to_render - self.app.env.autoapi_all_objects = self.all_objects + def _render_selection(self): + """Propagate display values to children.""" + for obj in sorted(self.all_objects.values(), key=lambda obj: len(obj.id)): + if obj.display: + assert obj.type in self.own_page_types + self.objects_to_render[obj.id] = obj + else: + for module in itertools.chain(obj.subpackages, obj.submodules): + module.obj["hide"] = True + + def _inner(parent): + for child in parent.children: + self.all_objects[child.id] = child + if not parent.display: + child.obj["hide"] = True + + if child.display and child.type in self.own_page_types: + self.objects_to_render[child.id] = child + + _inner(child) + + for obj in list(self.all_objects.values()): + _inner(obj) def create_class(self, data, options=None): """Create a class from the passed in data diff --git a/autoapi/_objects.py b/autoapi/_objects.py index 4e08b03e..dba5949e 100644 --- a/autoapi/_objects.py +++ b/autoapi/_objects.py @@ -76,7 +76,6 @@ def __init__( """Whether this object was imported from another module.""" self.inherited: bool = obj.get("inherited", False) """Whether this was inherited from an ancestor of the parent class.""" - self._hide = obj.get("hide", False) # For later self._class_content = class_content @@ -234,7 +233,7 @@ def _should_skip(self) -> bool: ) return ( - self._hide + self.obj.get("hide", False) or skip_undoc_member or skip_private_member or skip_special_member diff --git a/docs/changes/446.bugfix b/docs/changes/446.bugfix new file mode 100644 index 00000000..ddabf7f0 --- /dev/null +++ b/docs/changes/446.bugfix @@ -0,0 +1 @@ +Fix private subpackages causing orphan pages \ No newline at end of file diff --git a/tests/python/pypackageexample/package/_private_subpackage/__init__.py b/tests/python/pypackageexample/package/_private_subpackage/__init__.py new file mode 100644 index 00000000..3bc54ce0 --- /dev/null +++ b/tests/python/pypackageexample/package/_private_subpackage/__init__.py @@ -0,0 +1,13 @@ +"""This is a docstring.""" + +from .submodule import function as aliased_function +from .submodule import not_in_all_function + +__all__ = ( + "aliased_function", + "function", +) + + +def function(foo, bar): + """A module level function""" diff --git a/tests/python/pypackageexample/package/_private_subpackage/submodule.py b/tests/python/pypackageexample/package/_private_subpackage/submodule.py new file mode 100644 index 00000000..0bf556e9 --- /dev/null +++ b/tests/python/pypackageexample/package/_private_subpackage/submodule.py @@ -0,0 +1,41 @@ +"""Example module + +This is a description +""" + +DATA = 42 + + +def function(foo, bar): + """A module level function""" + + +def _private_function(): + """A function that shouldn't get rendered.""" + + +def not_in_all_function(): + """A function that doesn't exist in __all__ when imported.""" + + +class Class(object): + """This is a class.""" + + class_var = 42 + """Class var docstring""" + + class NestedClass(object): + """A nested class just to test things out""" + + @classmethod + def a_classmethod(): + """A class method""" + return True + + def method_okay(self, foo=None, bar=None): + """This method should parse okay""" + return True + + +class MyException(Exception): + """This is an exception.""" diff --git a/tests/python/pypackageexample/package/_private_subpackage/subpackage/__init__.py b/tests/python/pypackageexample/package/_private_subpackage/subpackage/__init__.py new file mode 100644 index 00000000..3bc54ce0 --- /dev/null +++ b/tests/python/pypackageexample/package/_private_subpackage/subpackage/__init__.py @@ -0,0 +1,13 @@ +"""This is a docstring.""" + +from .submodule import function as aliased_function +from .submodule import not_in_all_function + +__all__ = ( + "aliased_function", + "function", +) + + +def function(foo, bar): + """A module level function""" diff --git a/tests/python/pypackageexample/package/_private_subpackage/subpackage/submodule.py b/tests/python/pypackageexample/package/_private_subpackage/subpackage/submodule.py new file mode 100644 index 00000000..0bf556e9 --- /dev/null +++ b/tests/python/pypackageexample/package/_private_subpackage/subpackage/submodule.py @@ -0,0 +1,41 @@ +"""Example module + +This is a description +""" + +DATA = 42 + + +def function(foo, bar): + """A module level function""" + + +def _private_function(): + """A function that shouldn't get rendered.""" + + +def not_in_all_function(): + """A function that doesn't exist in __all__ when imported.""" + + +class Class(object): + """This is a class.""" + + class_var = 42 + """Class var docstring""" + + class NestedClass(object): + """A nested class just to test things out""" + + @classmethod + def a_classmethod(): + """A class method""" + return True + + def method_okay(self, foo=None, bar=None): + """This method should parse okay""" + return True + + +class MyException(Exception): + """This is an exception."""