diff --git a/autoapi/extension.py b/autoapi/extension.py index 032a929..8027821 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -261,6 +261,7 @@ def setup(app): app.add_config_value("autoapi_add_toctree_entry", True, "html") app.add_config_value("autoapi_template_dir", None, "html") app.add_config_value("autoapi_include_summaries", False, "html") + app.add_config_value("autoapi_python_use_implicit_namespaces", False, "html") app.add_config_value("autoapi_python_class_content", "class", "html") app.add_config_value("autoapi_generate_api_docs", True, "html") app.add_autodocumenter(documenters.AutoapiFunctionDocumenter) diff --git a/autoapi/mappers/python/mapper.py b/autoapi/mappers/python/mapper.py index a1dc80d..d605709 100644 --- a/autoapi/mappers/python/mapper.py +++ b/autoapi/mappers/python/mapper.py @@ -1,6 +1,7 @@ import collections import copy import os +import sys import sphinx.util from sphinx.util.console import bold @@ -218,10 +219,23 @@ class PythonSphinxMapper(SphinxMapperBase): else: _OBJ_MAP["property"] = PythonAttribute + def __init__(self, app, template_dir=None, url_root=None): + super(PythonSphinxMapper, self).__init__(app, template_dir, url_root) + + if sys.version_info < (3, 3): + self._use_implicit_namespace = False + else: + self._use_implicit_namespace = ( + self.app.config.autoapi_python_use_implicit_namespaces + ) + def _find_files(self, patterns, dirs, ignore): for dir_ in dirs: dir_root = dir_ - if os.path.exists(os.path.join(dir_, "__init__.py")): + if ( + os.path.exists(os.path.join(dir_, "__init__.py")) + or self._use_implicit_namespace + ): dir_root = os.path.abspath(os.path.join(dir_, os.pardir)) for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore): @@ -240,18 +254,21 @@ class PythonSphinxMapper(SphinxMapperBase): length=len(dir_root_files), stringify_func=(lambda x: x[1]), ): - data = self.read_file(path=path) + data = self.read_file(path=path, dir_root=dir_root) if data: data["relative_path"] = os.path.relpath(path, dir_root) self.paths[path] = data - def read_file(self, path, **kwargs): + def read_file(self, path, dir_root=None, **kwargs): """Read file input into memory, returning deserialized objects :param path: Path of file to read """ try: - parsed_data = Parser().parse_file(path) + if self._use_implicit_namespace: + parsed_data = Parser().parse_file_in_namespace(path, dir_root) + else: + parsed_data = Parser().parse_file(path) return parsed_data except (IOError, TypeError, ImportError): LOGGER.warning("Error reading file: {0}".format(path)) diff --git a/autoapi/mappers/python/parser.py b/autoapi/mappers/python/parser.py index 5ddafe6..e24bdeb 100644 --- a/autoapi/mappers/python/parser.py +++ b/autoapi/mappers/python/parser.py @@ -25,14 +25,14 @@ class Parser(object): return to_decode - def parse_file(self, file_path): + def _parse_file(self, file_path, condition): directory, filename = os.path.split(file_path) module_parts = [] if filename != "__init__.py": module_part = os.path.splitext(filename)[0] module_parts = [module_part] module_parts = collections.deque(module_parts) - while os.path.isfile(os.path.join(directory, "__init__.py")): + while condition(directory): directory, module_part = os.path.split(directory) if module_part: module_parts.appendleft(module_part) @@ -41,6 +41,15 @@ class Parser(object): node = astroid.MANAGER.ast_from_file(file_path, module_name, source=True) return self.parse(node) + def parse_file(self, file_path): + return self._parse_file( + file_path, + lambda directory: os.path.isfile(os.path.join(directory, "__init__.py")), + ) + + def parse_file_in_namespace(self, file_path, dir_root): + return self._parse_file(file_path, lambda directory: directory != dir_root) + def parse_annassign(self, node): return self.parse_assign(node)