feat: Support implicit namespaces for python >= 3.3

This commit is contained in:
Olivier Samyn 🎻 2019-10-14 23:47:52 +02:00 committed by Ashley Whetter
parent 4c927b2a3f
commit 7ca958fe57
3 changed files with 33 additions and 6 deletions

View File

@ -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)

View File

@ -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))

View File

@ -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)