Fixed private submodules displaying when private-members turned off
Also fixed whitespace issues in templates.pull/164/head
parent
60464d0d23
commit
a4f798183c
@ -1,737 +0,0 @@
|
||||
import collections
|
||||
import copy
|
||||
import os
|
||||
|
||||
import astroid
|
||||
import sphinx
|
||||
import sphinx.util.docstrings
|
||||
import sphinx.util.logging
|
||||
|
||||
from .base import PythonMapperBase, SphinxMapperBase
|
||||
from . import astroid_utils
|
||||
from ..utils import slugify
|
||||
|
||||
try:
|
||||
_TEXT_TYPE = unicode
|
||||
except NameError:
|
||||
_TEXT_TYPE = str
|
||||
|
||||
LOGGER = sphinx.util.logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _expand_wildcard_placeholder(original_module, originals_map, placeholder):
|
||||
"""Expand a wildcard placeholder to a sequence of named placeholders.
|
||||
|
||||
:param original_module: The data dictionary of the module
|
||||
that the placeholder is imported from.
|
||||
:type original_module: dict
|
||||
:param originals_map: A map of the names of children under the module
|
||||
to their data dictionaries.
|
||||
:type originals_map: dict(str, dict)
|
||||
:param placeholder: The wildcard placeholder to expand.
|
||||
:type placeholder: dict
|
||||
|
||||
:returns: The placeholders that the wildcard placeholder represents.
|
||||
:rtype: list(dict)
|
||||
"""
|
||||
originals = originals_map.values()
|
||||
if original_module["all"] is not None:
|
||||
originals = []
|
||||
for name in original_module["all"]:
|
||||
if name == "__all__":
|
||||
continue
|
||||
|
||||
if name not in originals_map:
|
||||
msg = "Invalid __all__ entry {0} in {1}".format(
|
||||
name, original_module["name"]
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
continue
|
||||
|
||||
originals.append(originals_map[name])
|
||||
|
||||
placeholders = []
|
||||
for original in originals:
|
||||
new_full_name = placeholder["full_name"].replace("*", original["name"])
|
||||
new_original_path = placeholder["original_path"].replace("*", original["name"])
|
||||
if "original_path" in original:
|
||||
new_original_path = original["original_path"]
|
||||
new_placeholder = dict(
|
||||
placeholder,
|
||||
name=original["name"],
|
||||
full_name=new_full_name,
|
||||
original_path=new_original_path,
|
||||
)
|
||||
placeholders.append(new_placeholder)
|
||||
|
||||
return placeholders
|
||||
|
||||
|
||||
def _resolve_module_placeholders(modules, module_name, visit_path, resolved):
|
||||
"""Resolve all placeholder children under a module.
|
||||
|
||||
:param modules: A mapping of module names to their data dictionary.
|
||||
Placeholders are resolved in place.
|
||||
:type modules: dict(str, dict)
|
||||
:param module_name: The name of the module to resolve.
|
||||
:type module_name: str
|
||||
:param visit_path: An ordered set of visited module names.
|
||||
:type visited: collections.OrderedDict
|
||||
:param resolved: A set of already resolved module names.
|
||||
:type resolved: set(str)
|
||||
"""
|
||||
if module_name in resolved:
|
||||
return
|
||||
|
||||
visit_path[module_name] = True
|
||||
|
||||
module, children = modules[module_name]
|
||||
for child in list(children.values()):
|
||||
if child["type"] != "placeholder":
|
||||
continue
|
||||
|
||||
if child["original_path"] in modules:
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
imported_from, original_name = child["original_path"].rsplit(".", 1)
|
||||
if imported_from in visit_path:
|
||||
msg = "Cannot resolve cyclic import: {0}, {1}".format(
|
||||
", ".join(visit_path), imported_from
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
if imported_from not in modules:
|
||||
msg = "Cannot resolve import of unknown module {0} in {1}".format(
|
||||
imported_from, module_name
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
_resolve_module_placeholders(modules, imported_from, visit_path, resolved)
|
||||
|
||||
if original_name == "*":
|
||||
original_module, originals_map = modules[imported_from]
|
||||
|
||||
# Replace the wildcard placeholder
|
||||
# with a list of named placeholders.
|
||||
new_placeholders = _expand_wildcard_placeholder(
|
||||
original_module, originals_map, child
|
||||
)
|
||||
child_index = module["children"].index(child)
|
||||
module["children"][child_index : child_index + 1] = new_placeholders
|
||||
children.pop(child["name"])
|
||||
|
||||
for new_placeholder in new_placeholders:
|
||||
if new_placeholder["name"] not in children:
|
||||
children[new_placeholder["name"]] = new_placeholder
|
||||
original = originals_map[new_placeholder["name"]]
|
||||
_resolve_placeholder(new_placeholder, original)
|
||||
elif original_name not in modules[imported_from][1]:
|
||||
msg = "Cannot resolve import of {0} in {1}".format(
|
||||
child["original_path"], module_name
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
else:
|
||||
original = modules[imported_from][1][original_name]
|
||||
_resolve_placeholder(child, original)
|
||||
|
||||
del visit_path[module_name]
|
||||
resolved.add(module_name)
|
||||
|
||||
|
||||
def _resolve_placeholder(placeholder, original):
|
||||
"""Resolve a placeholder to the given original object.
|
||||
|
||||
:param placeholder: The placeholder to resolve, in place.
|
||||
:type placeholder: dict
|
||||
:param original: The object that the placeholder represents.
|
||||
:type original: dict
|
||||
"""
|
||||
new = copy.deepcopy(original)
|
||||
# The name remains the same.
|
||||
new["name"] = placeholder["name"]
|
||||
new["full_name"] = placeholder["full_name"]
|
||||
# Record where the placeholder originally came from.
|
||||
new["original_path"] = original["full_name"]
|
||||
# The source lines for this placeholder do not exist in this file.
|
||||
# The keys might not exist if original is a resolved placeholder.
|
||||
new.pop("from_line_no", None)
|
||||
new.pop("to_line_no", None)
|
||||
|
||||
# Resolve the children
|
||||
stack = list(new.get("children", ()))
|
||||
while stack:
|
||||
child = stack.pop()
|
||||
# Relocate the child to the new location
|
||||
assert child["full_name"].startswith(original["full_name"])
|
||||
suffix = child["full_name"][len(original["full_name"]) :]
|
||||
child["full_name"] = new["full_name"] + suffix
|
||||
# The source lines for this placeholder do not exist in this file.
|
||||
# The keys might not exist if original is a resolved placeholder.
|
||||
child.pop("from_line_no", None)
|
||||
child.pop("to_line_no", None)
|
||||
# Resolve the remaining children
|
||||
stack.extend(child.get("children", ()))
|
||||
|
||||
placeholder.clear()
|
||||
placeholder.update(new)
|
||||
|
||||
|
||||
class PythonSphinxMapper(SphinxMapperBase):
|
||||
|
||||
"""Auto API domain handler for Python
|
||||
|
||||
Parses directly from Python files.
|
||||
|
||||
:param app: Sphinx application passed in as part of the extension
|
||||
"""
|
||||
|
||||
def load(self, patterns, dirs, ignore=None):
|
||||
"""Load objects from the filesystem into the ``paths`` dictionary
|
||||
|
||||
Also include an attribute on the object, ``relative_path`` which is the
|
||||
shortened, relative path the package/module
|
||||
"""
|
||||
for dir_ in dirs:
|
||||
dir_root = dir_
|
||||
if os.path.exists(os.path.join(dir_, "__init__.py")):
|
||||
dir_root = os.path.abspath(os.path.join(dir_, os.pardir))
|
||||
|
||||
for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore):
|
||||
data = self.read_file(path=path)
|
||||
if data:
|
||||
data["relative_path"] = os.path.relpath(path, dir_root)
|
||||
self.paths[path] = data
|
||||
|
||||
def read_file(self, path, **kwargs):
|
||||
"""Read file input into memory, returning deserialized objects
|
||||
|
||||
:param path: Path of file to read
|
||||
"""
|
||||
try:
|
||||
parsed_data = Parser().parse_file(path)
|
||||
return parsed_data
|
||||
except (IOError, TypeError, ImportError):
|
||||
LOGGER.warning("Error reading file: {0}".format(path))
|
||||
return None
|
||||
|
||||
def _resolve_placeholders(self):
|
||||
"""Resolve objects that have been imported from elsewhere."""
|
||||
modules = {}
|
||||
for module in self.paths.values():
|
||||
children = {child["name"]: child for child in module["children"]}
|
||||
modules[module["name"]] = (module, children)
|
||||
|
||||
resolved = set()
|
||||
for module_name in modules:
|
||||
visit_path = collections.OrderedDict()
|
||||
_resolve_module_placeholders(modules, module_name, visit_path, resolved)
|
||||
|
||||
def map(self, options=None):
|
||||
self._resolve_placeholders()
|
||||
|
||||
super(PythonSphinxMapper, self).map(options)
|
||||
|
||||
parents = {obj.name: obj for obj in self.objects.values()}
|
||||
for obj in self.objects.values():
|
||||
parent_name = obj.name.rsplit(".", 1)[0]
|
||||
if parent_name in parents and parent_name != obj.name:
|
||||
parent = parents[parent_name]
|
||||
attr = "sub{}s".format(obj.type)
|
||||
getattr(parent, attr).append(obj)
|
||||
|
||||
for obj in self.objects.values():
|
||||
obj.submodules.sort()
|
||||
obj.subpackages.sort()
|
||||
|
||||
def create_class(self, data, options=None, **kwargs):
|
||||
"""Create a class from the passed in data
|
||||
|
||||
:param data: dictionary data of parser output
|
||||
"""
|
||||
obj_map = dict(
|
||||
(cls.type, cls)
|
||||
for cls in [
|
||||
PythonClass,
|
||||
PythonFunction,
|
||||
PythonModule,
|
||||
PythonMethod,
|
||||
PythonPackage,
|
||||
PythonAttribute,
|
||||
PythonData,
|
||||
PythonException,
|
||||
]
|
||||
)
|
||||
try:
|
||||
cls = obj_map[data["type"]]
|
||||
except KeyError:
|
||||
LOGGER.warning("Unknown type: %s" % data["type"])
|
||||
else:
|
||||
obj = cls(
|
||||
data,
|
||||
class_content=self.app.config.autoapi_python_class_content,
|
||||
options=self.app.config.autoapi_options,
|
||||
jinja_env=self.jinja_env,
|
||||
url_root=self.url_root,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
lines = sphinx.util.docstrings.prepare_docstring(obj.docstring)
|
||||
if lines and "autodoc-process-docstring" in self.app.events.events:
|
||||
self.app.emit(
|
||||
"autodoc-process-docstring",
|
||||
cls.type,
|
||||
obj.name,
|
||||
None, # object
|
||||
None, # options
|
||||
lines,
|
||||
)
|
||||
obj.docstring = "\n".join(lines)
|
||||
|
||||
for child_data in data.get("children", []):
|
||||
for child_obj in self.create_class(
|
||||
child_data, options=options, **kwargs
|
||||
):
|
||||
obj.children.append(child_obj)
|
||||
yield obj
|
||||
|
||||
|
||||
class PythonPythonMapper(PythonMapperBase):
|
||||
|
||||
language = "python"
|
||||
is_callable = False
|
||||
|
||||
def __init__(self, obj, class_content="class", **kwargs):
|
||||
super(PythonPythonMapper, self).__init__(obj, **kwargs)
|
||||
|
||||
self.name = obj["name"]
|
||||
self.id = obj.get("full_name", self.name)
|
||||
|
||||
# Optional
|
||||
self.children = []
|
||||
self.args = obj.get("args")
|
||||
self.docstring = obj["doc"]
|
||||
|
||||
# For later
|
||||
self.item_map = collections.defaultdict(list)
|
||||
self._class_content = class_content
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
return self._args
|
||||
|
||||
@args.setter
|
||||
def args(self, value):
|
||||
self._args = value
|
||||
|
||||
@property
|
||||
def docstring(self):
|
||||
return self._docstring
|
||||
|
||||
@docstring.setter
|
||||
def docstring(self, value):
|
||||
self._docstring = value
|
||||
|
||||
@property
|
||||
def is_undoc_member(self):
|
||||
return not bool(self.docstring)
|
||||
|
||||
@property
|
||||
def is_private_member(self):
|
||||
return self.short_name.startswith("_") and not self.short_name.endswith("__")
|
||||
|
||||
@property
|
||||
def is_special_member(self):
|
||||
return self.short_name.startswith("__") and self.short_name.endswith("__")
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
if self.is_undoc_member and "undoc-members" not in self.options:
|
||||
return False
|
||||
if self.is_private_member and "private-members" not in self.options:
|
||||
return False
|
||||
if self.is_special_member and "special-members" not in self.options:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
for line in self.docstring.splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
return line
|
||||
|
||||
return ""
|
||||
|
||||
def _children_of_type(self, type_):
|
||||
return list(child for child in self.children if child.type == type_)
|
||||
|
||||
|
||||
class PythonFunction(PythonPythonMapper):
|
||||
type = "function"
|
||||
is_callable = True
|
||||
ref_directive = "func"
|
||||
|
||||
|
||||
class PythonMethod(PythonPythonMapper):
|
||||
type = "method"
|
||||
is_callable = True
|
||||
ref_directive = "meth"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonMethod, self).__init__(obj, **kwargs)
|
||||
|
||||
self.method_type = obj["method_type"]
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
if self.short_name == "__init__":
|
||||
return False
|
||||
|
||||
return super(PythonMethod, self).display
|
||||
|
||||
|
||||
class PythonData(PythonPythonMapper):
|
||||
"""Global, module level data."""
|
||||
|
||||
type = "data"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonData, self).__init__(obj, **kwargs)
|
||||
|
||||
self.value = obj.get("value")
|
||||
|
||||
|
||||
class PythonAttribute(PythonData):
|
||||
"""An object/class level attribute."""
|
||||
|
||||
type = "attribute"
|
||||
|
||||
|
||||
class TopLevelPythonPythonMapper(PythonPythonMapper):
|
||||
ref_directive = "mod"
|
||||
_RENDER_LOG_LEVEL = "VERBOSE"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(TopLevelPythonPythonMapper, self).__init__(obj, **kwargs)
|
||||
|
||||
self.top_level_object = "." not in self.name
|
||||
|
||||
self.subpackages = []
|
||||
self.submodules = []
|
||||
self.all = obj["all"]
|
||||
|
||||
@property
|
||||
def functions(self):
|
||||
return self._children_of_type("function")
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._children_of_type("class")
|
||||
|
||||
|
||||
class PythonModule(TopLevelPythonPythonMapper):
|
||||
type = "module"
|
||||
|
||||
|
||||
class PythonPackage(TopLevelPythonPythonMapper):
|
||||
type = "package"
|
||||
|
||||
|
||||
class PythonClass(PythonPythonMapper):
|
||||
type = "class"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonClass, self).__init__(obj, **kwargs)
|
||||
|
||||
self.bases = obj["bases"]
|
||||
|
||||
@PythonPythonMapper.args.getter
|
||||
def args(self):
|
||||
args = self._args
|
||||
|
||||
constructor = self.constructor
|
||||
if constructor:
|
||||
args = constructor.args
|
||||
|
||||
if args.startswith("self"):
|
||||
args = args[4:].lstrip(",").lstrip()
|
||||
|
||||
return args
|
||||
|
||||
@PythonPythonMapper.docstring.getter
|
||||
def docstring(self):
|
||||
docstring = super(PythonClass, self).docstring
|
||||
|
||||
if self._class_content in ("both", "init"):
|
||||
constructor_docstring = self.constructor_docstring
|
||||
if constructor_docstring:
|
||||
if self._class_content == "both":
|
||||
docstring = "{0}\n{1}".format(docstring, constructor_docstring)
|
||||
else:
|
||||
docstring = constructor_docstring
|
||||
|
||||
return docstring
|
||||
|
||||
@property
|
||||
def methods(self):
|
||||
return self._children_of_type("method")
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return self._children_of_type("attribute")
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._children_of_type("class")
|
||||
|
||||
@property
|
||||
def constructor(self):
|
||||
for child in self.children:
|
||||
if child.short_name == "__init__":
|
||||
return child
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def constructor_docstring(self):
|
||||
docstring = ""
|
||||
|
||||
constructor = self.constructor
|
||||
if constructor and constructor.docstring:
|
||||
docstring = constructor.docstring
|
||||
else:
|
||||
for child in self.children:
|
||||
if child.short_name == "__new__":
|
||||
docstring = child.docstring
|
||||
break
|
||||
|
||||
return docstring
|
||||
|
||||
|
||||
class PythonException(PythonClass):
|
||||
type = "exception"
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self):
|
||||
self._name_stack = []
|
||||
self._encoding = None
|
||||
|
||||
def _get_full_name(self, name):
|
||||
return ".".join(self._name_stack + [name])
|
||||
|
||||
def _encode(self, to_encode):
|
||||
if self._encoding:
|
||||
try:
|
||||
return _TEXT_TYPE(to_encode, self._encoding)
|
||||
except TypeError:
|
||||
# The string was already in the correct format
|
||||
pass
|
||||
|
||||
return to_encode
|
||||
|
||||
def parse_file(self, file_path):
|
||||
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")):
|
||||
directory, module_part = os.path.split(directory)
|
||||
if module_part:
|
||||
module_parts.appendleft(module_part)
|
||||
|
||||
module_name = ".".join(module_parts)
|
||||
node = astroid.MANAGER.ast_from_file(file_path, module_name)
|
||||
return self.parse(node)
|
||||
|
||||
def parse_assign(self, node):
|
||||
doc = ""
|
||||
doc_node = node.next_sibling()
|
||||
if isinstance(doc_node, astroid.nodes.Expr) and isinstance(
|
||||
doc_node.value, astroid.nodes.Const
|
||||
):
|
||||
doc = doc_node.value.value
|
||||
|
||||
type_ = "data"
|
||||
if isinstance(
|
||||
node.scope(), astroid.nodes.ClassDef
|
||||
) or astroid_utils.is_constructor(node.scope()):
|
||||
type_ = "attribute"
|
||||
|
||||
assign_value = astroid_utils.get_assign_value(node)
|
||||
if not assign_value:
|
||||
return []
|
||||
|
||||
target, value = assign_value
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": target,
|
||||
"full_name": self._get_full_name(target),
|
||||
"doc": self._encode(doc),
|
||||
"value": value,
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
return [data]
|
||||
|
||||
def parse_classdef(self, node, data=None):
|
||||
type_ = "class"
|
||||
if astroid_utils.is_exception(node):
|
||||
type_ = "exception"
|
||||
|
||||
args = ""
|
||||
try:
|
||||
constructor = node.lookup("__init__")[1]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(constructor, astroid.nodes.FunctionDef):
|
||||
args = constructor.args.as_string()
|
||||
|
||||
basenames = list(astroid_utils.get_full_basenames(node.bases, node.basenames))
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"args": args,
|
||||
"bases": basenames,
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
"children": [],
|
||||
}
|
||||
|
||||
self._name_stack.append(node.name)
|
||||
for child in node.get_children():
|
||||
child_data = self.parse(child)
|
||||
if child_data:
|
||||
data["children"].extend(child_data)
|
||||
self._name_stack.pop()
|
||||
|
||||
return [data]
|
||||
|
||||
def _parse_property(self, node):
|
||||
data = {
|
||||
"type": "attribute",
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
return [data]
|
||||
|
||||
def parse_functiondef(self, node):
|
||||
if astroid_utils.is_decorated_with_property(node):
|
||||
return self._parse_property(node)
|
||||
if astroid_utils.is_decorated_with_property_setter(node):
|
||||
return []
|
||||
|
||||
type_ = "function" if node.type == "function" else "method"
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"args": node.args.as_string(),
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
if type_ == "method":
|
||||
data["method_type"] = node.type
|
||||
|
||||
result = [data]
|
||||
|
||||
if node.name == "__init__":
|
||||
for child in node.get_children():
|
||||
if isinstance(child, astroid.Assign):
|
||||
child_data = self.parse_assign(child)
|
||||
result.extend(data for data in child_data if data["doc"])
|
||||
|
||||
return result
|
||||
|
||||
def _parse_local_import_from(self, node):
|
||||
result = []
|
||||
|
||||
for name, alias in node.names:
|
||||
is_wildcard = (alias or name) == "*"
|
||||
full_name = self._get_full_name(alias or name)
|
||||
original_path = astroid_utils.get_full_import_name(node, alias or name)
|
||||
|
||||
data = {
|
||||
"type": "placeholder",
|
||||
"name": original_path if is_wildcard else (alias or name),
|
||||
"full_name": full_name,
|
||||
"original_path": original_path,
|
||||
}
|
||||
result.append(data)
|
||||
|
||||
return result
|
||||
|
||||
def parse_module(self, node):
|
||||
path = node.path
|
||||
if isinstance(node.path, list):
|
||||
path = node.path[0] if node.path else None
|
||||
|
||||
type_ = "module"
|
||||
if node.package:
|
||||
type_ = "package"
|
||||
|
||||
self._name_stack = [node.name]
|
||||
self._encoding = node.file_encoding
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": node.name,
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"children": [],
|
||||
"file_path": path,
|
||||
"encoding": node.file_encoding,
|
||||
"all": astroid_utils.get_module_all(node),
|
||||
}
|
||||
|
||||
top_name = node.name.split(".", 1)[0]
|
||||
for child in node.get_children():
|
||||
if node.package and astroid_utils.is_local_import_from(child, top_name):
|
||||
child_data = self._parse_local_import_from(child)
|
||||
else:
|
||||
child_data = self.parse(child)
|
||||
|
||||
if child_data:
|
||||
data["children"].extend(child_data)
|
||||
|
||||
return data
|
||||
|
||||
def parse(self, node):
|
||||
data = {}
|
||||
|
||||
node_type = node.__class__.__name__.lower()
|
||||
parse_func = getattr(self, "parse_" + node_type, None)
|
||||
if parse_func:
|
||||
data = parse_func(node)
|
||||
else:
|
||||
for child in node.get_children():
|
||||
data = self.parse(child)
|
||||
if data:
|
||||
break
|
||||
|
||||
return data
|
@ -0,0 +1,11 @@
|
||||
from .mapper import PythonSphinxMapper
|
||||
from .objects import (
|
||||
PythonClass,
|
||||
PythonFunction,
|
||||
PythonModule,
|
||||
PythonMethod,
|
||||
PythonPackage,
|
||||
PythonAttribute,
|
||||
PythonData,
|
||||
PythonException,
|
||||
)
|
@ -0,0 +1,308 @@
|
||||
import collections
|
||||
import copy
|
||||
import os
|
||||
|
||||
import sphinx.util.docstrings
|
||||
import sphinx.util.logging
|
||||
|
||||
from ..base import SphinxMapperBase
|
||||
from .parser import Parser
|
||||
from .objects import (
|
||||
PythonClass,
|
||||
PythonFunction,
|
||||
PythonModule,
|
||||
PythonMethod,
|
||||
PythonPackage,
|
||||
PythonAttribute,
|
||||
PythonData,
|
||||
PythonException,
|
||||
)
|
||||
|
||||
LOGGER = sphinx.util.logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _expand_wildcard_placeholder(original_module, originals_map, placeholder):
|
||||
"""Expand a wildcard placeholder to a sequence of named placeholders.
|
||||
|
||||
:param original_module: The data dictionary of the module
|
||||
that the placeholder is imported from.
|
||||
:type original_module: dict
|
||||
:param originals_map: A map of the names of children under the module
|
||||
to their data dictionaries.
|
||||
:type originals_map: dict(str, dict)
|
||||
:param placeholder: The wildcard placeholder to expand.
|
||||
:type placeholder: dict
|
||||
|
||||
:returns: The placeholders that the wildcard placeholder represents.
|
||||
:rtype: list(dict)
|
||||
"""
|
||||
originals = originals_map.values()
|
||||
if original_module["all"] is not None:
|
||||
originals = []
|
||||
for name in original_module["all"]:
|
||||
if name == "__all__":
|
||||
continue
|
||||
|
||||
if name not in originals_map:
|
||||
msg = "Invalid __all__ entry {0} in {1}".format(
|
||||
name, original_module["name"]
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
continue
|
||||
|
||||
originals.append(originals_map[name])
|
||||
|
||||
placeholders = []
|
||||
for original in originals:
|
||||
new_full_name = placeholder["full_name"].replace("*", original["name"])
|
||||
new_original_path = placeholder["original_path"].replace("*", original["name"])
|
||||
if "original_path" in original:
|
||||
new_original_path = original["original_path"]
|
||||
new_placeholder = dict(
|
||||
placeholder,
|
||||
name=original["name"],
|
||||
full_name=new_full_name,
|
||||
original_path=new_original_path,
|
||||
)
|
||||
placeholders.append(new_placeholder)
|
||||
|
||||
return placeholders
|
||||
|
||||
|
||||
def _resolve_module_placeholders(modules, module_name, visit_path, resolved):
|
||||
"""Resolve all placeholder children under a module.
|
||||
|
||||
:param modules: A mapping of module names to their data dictionary.
|
||||
Placeholders are resolved in place.
|
||||
:type modules: dict(str, dict)
|
||||
:param module_name: The name of the module to resolve.
|
||||
:type module_name: str
|
||||
:param visit_path: An ordered set of visited module names.
|
||||
:type visited: collections.OrderedDict
|
||||
:param resolved: A set of already resolved module names.
|
||||
:type resolved: set(str)
|
||||
"""
|
||||
if module_name in resolved:
|
||||
return
|
||||
|
||||
visit_path[module_name] = True
|
||||
|
||||
module, children = modules[module_name]
|
||||
for child in list(children.values()):
|
||||
if child["type"] != "placeholder":
|
||||
continue
|
||||
|
||||
if child["original_path"] in modules:
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
imported_from, original_name = child["original_path"].rsplit(".", 1)
|
||||
if imported_from in visit_path:
|
||||
msg = "Cannot resolve cyclic import: {0}, {1}".format(
|
||||
", ".join(visit_path), imported_from
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
if imported_from not in modules:
|
||||
msg = "Cannot resolve import of unknown module {0} in {1}".format(
|
||||
imported_from, module_name
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
|
||||
_resolve_module_placeholders(modules, imported_from, visit_path, resolved)
|
||||
|
||||
if original_name == "*":
|
||||
original_module, originals_map = modules[imported_from]
|
||||
|
||||
# Replace the wildcard placeholder
|
||||
# with a list of named placeholders.
|
||||
new_placeholders = _expand_wildcard_placeholder(
|
||||
original_module, originals_map, child
|
||||
)
|
||||
child_index = module["children"].index(child)
|
||||
module["children"][child_index : child_index + 1] = new_placeholders
|
||||
children.pop(child["name"])
|
||||
|
||||
for new_placeholder in new_placeholders:
|
||||
if new_placeholder["name"] not in children:
|
||||
children[new_placeholder["name"]] = new_placeholder
|
||||
original = originals_map[new_placeholder["name"]]
|
||||
_resolve_placeholder(new_placeholder, original)
|
||||
elif original_name not in modules[imported_from][1]:
|
||||
msg = "Cannot resolve import of {0} in {1}".format(
|
||||
child["original_path"], module_name
|
||||
)
|
||||
LOGGER.warning(msg)
|
||||
module["children"].remove(child)
|
||||
children.pop(child["name"])
|
||||
continue
|
||||
else:
|
||||
original = modules[imported_from][1][original_name]
|
||||
_resolve_placeholder(child, original)
|
||||
|
||||
del visit_path[module_name]
|
||||
resolved.add(module_name)
|
||||
|
||||
|
||||
def _resolve_placeholder(placeholder, original):
|
||||
"""Resolve a placeholder to the given original object.
|
||||
|
||||
:param placeholder: The placeholder to resolve, in place.
|
||||
:type placeholder: dict
|
||||
:param original: The object that the placeholder represents.
|
||||
:type original: dict
|
||||
"""
|
||||
new = copy.deepcopy(original)
|
||||
# The name remains the same.
|
||||
new["name"] = placeholder["name"]
|
||||
new["full_name"] = placeholder["full_name"]
|
||||
# Record where the placeholder originally came from.
|
||||
new["original_path"] = original["full_name"]
|
||||
# The source lines for this placeholder do not exist in this file.
|
||||
# The keys might not exist if original is a resolved placeholder.
|
||||
new.pop("from_line_no", None)
|
||||
new.pop("to_line_no", None)
|
||||
|
||||
# Resolve the children
|
||||
stack = list(new.get("children", ()))
|
||||
while stack:
|
||||
child = stack.pop()
|
||||
# Relocate the child to the new location
|
||||
assert child["full_name"].startswith(original["full_name"])
|
||||
suffix = child["full_name"][len(original["full_name"]) :]
|
||||
child["full_name"] = new["full_name"] + suffix
|
||||
# The source lines for this placeholder do not exist in this file.
|
||||
# The keys might not exist if original is a resolved placeholder.
|
||||
child.pop("from_line_no", None)
|
||||
child.pop("to_line_no", None)
|
||||
# Resolve the remaining children
|
||||
stack.extend(child.get("children", ()))
|
||||
|
||||
placeholder.clear()
|
||||
placeholder.update(new)
|
||||
|
||||
|
||||
class PythonSphinxMapper(SphinxMapperBase):
|
||||
|
||||
"""Auto API domain handler for Python
|
||||
|
||||
Parses directly from Python files.
|
||||
|
||||
:param app: Sphinx application passed in as part of the extension
|
||||
"""
|
||||
|
||||
def load(self, patterns, dirs, ignore=None):
|
||||
"""Load objects from the filesystem into the ``paths`` dictionary
|
||||
|
||||
Also include an attribute on the object, ``relative_path`` which is the
|
||||
shortened, relative path the package/module
|
||||
"""
|
||||
for dir_ in dirs:
|
||||
dir_root = dir_
|
||||
if os.path.exists(os.path.join(dir_, "__init__.py")):
|
||||
dir_root = os.path.abspath(os.path.join(dir_, os.pardir))
|
||||
|
||||
for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore):
|
||||
data = self.read_file(path=path)
|
||||
if data:
|
||||
data["relative_path"] = os.path.relpath(path, dir_root)
|
||||
self.paths[path] = data
|
||||
|
||||
def read_file(self, path, **kwargs):
|
||||
"""Read file input into memory, returning deserialized objects
|
||||
|
||||
:param path: Path of file to read
|
||||
"""
|
||||
try:
|
||||
parsed_data = Parser().parse_file(path)
|
||||
return parsed_data
|
||||
except (IOError, TypeError, ImportError):
|
||||
LOGGER.warning("Error reading file: {0}".format(path))
|
||||
return None
|
||||
|
||||
def _resolve_placeholders(self):
|
||||
"""Resolve objects that have been imported from elsewhere."""
|
||||
modules = {}
|
||||
for module in self.paths.values():
|
||||
children = {child["name"]: child for child in module["children"]}
|
||||
modules[module["name"]] = (module, children)
|
||||
|
||||
resolved = set()
|
||||
for module_name in modules:
|
||||
visit_path = collections.OrderedDict()
|
||||
_resolve_module_placeholders(modules, module_name, visit_path, resolved)
|
||||
|
||||
def map(self, options=None):
|
||||
self._resolve_placeholders()
|
||||
|
||||
super(PythonSphinxMapper, self).map(options)
|
||||
|
||||
parents = {obj.name: obj for obj in self.objects.values()}
|
||||
for obj in self.objects.values():
|
||||
parent_name = obj.name.rsplit(".", 1)[0]
|
||||
if parent_name in parents and parent_name != obj.name:
|
||||
parent = parents[parent_name]
|
||||
attr = "sub{}s".format(obj.type)
|
||||
getattr(parent, attr).append(obj)
|
||||
|
||||
for obj in self.objects.values():
|
||||
obj.submodules.sort()
|
||||
obj.subpackages.sort()
|
||||
|
||||
def create_class(self, data, options=None, **kwargs):
|
||||
"""Create a class from the passed in data
|
||||
|
||||
:param data: dictionary data of parser output
|
||||
"""
|
||||
obj_map = dict(
|
||||
(cls.type, cls)
|
||||
for cls in [
|
||||
PythonClass,
|
||||
PythonFunction,
|
||||
PythonModule,
|
||||
PythonMethod,
|
||||
PythonPackage,
|
||||
PythonAttribute,
|
||||
PythonData,
|
||||
PythonException,
|
||||
]
|
||||
)
|
||||
try:
|
||||
cls = obj_map[data["type"]]
|
||||
except KeyError:
|
||||
LOGGER.warning("Unknown type: %s" % data["type"])
|
||||
else:
|
||||
obj = cls(
|
||||
data,
|
||||
class_content=self.app.config.autoapi_python_class_content,
|
||||
options=self.app.config.autoapi_options,
|
||||
jinja_env=self.jinja_env,
|
||||
url_root=self.url_root,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
lines = sphinx.util.docstrings.prepare_docstring(obj.docstring)
|
||||
if lines and "autodoc-process-docstring" in self.app.events.events:
|
||||
self.app.emit(
|
||||
"autodoc-process-docstring",
|
||||
cls.type,
|
||||
obj.name,
|
||||
None, # object
|
||||
None, # options
|
||||
lines,
|
||||
)
|
||||
obj.docstring = "\n".join(lines)
|
||||
|
||||
for child_data in data.get("children", []):
|
||||
for child_obj in self.create_class(
|
||||
child_data, options=options, **kwargs
|
||||
):
|
||||
obj.children.append(child_obj)
|
||||
yield obj
|
@ -0,0 +1,220 @@
|
||||
import collections
|
||||
|
||||
from ..base import PythonMapperBase
|
||||
|
||||
|
||||
class PythonPythonMapper(PythonMapperBase):
|
||||
|
||||
language = "python"
|
||||
is_callable = False
|
||||
|
||||
def __init__(self, obj, class_content="class", **kwargs):
|
||||
super(PythonPythonMapper, self).__init__(obj, **kwargs)
|
||||
|
||||
self.name = obj["name"]
|
||||
self.id = obj.get("full_name", self.name)
|
||||
|
||||
# Optional
|
||||
self.children = []
|
||||
self.args = obj.get("args")
|
||||
self.docstring = obj["doc"]
|
||||
|
||||
# For later
|
||||
self.item_map = collections.defaultdict(list)
|
||||
self._class_content = class_content
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
return self._args
|
||||
|
||||
@args.setter
|
||||
def args(self, value):
|
||||
self._args = value
|
||||
|
||||
@property
|
||||
def docstring(self):
|
||||
return self._docstring
|
||||
|
||||
@docstring.setter
|
||||
def docstring(self, value):
|
||||
self._docstring = value
|
||||
|
||||
@property
|
||||
def is_undoc_member(self):
|
||||
return not bool(self.docstring)
|
||||
|
||||
@property
|
||||
def is_private_member(self):
|
||||
return self.short_name.startswith("_") and not self.short_name.endswith("__")
|
||||
|
||||
@property
|
||||
def is_special_member(self):
|
||||
return self.short_name.startswith("__") and self.short_name.endswith("__")
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
if self.is_undoc_member and "undoc-members" not in self.options:
|
||||
return False
|
||||
if self.is_private_member and "private-members" not in self.options:
|
||||
return False
|
||||
if self.is_special_member and "special-members" not in self.options:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
for line in self.docstring.splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
return line
|
||||
|
||||
return ""
|
||||
|
||||
def _children_of_type(self, type_):
|
||||
return list(child for child in self.children if child.type == type_)
|
||||
|
||||
|
||||
class PythonFunction(PythonPythonMapper):
|
||||
type = "function"
|
||||
is_callable = True
|
||||
ref_directive = "func"
|
||||
|
||||
|
||||
class PythonMethod(PythonPythonMapper):
|
||||
type = "method"
|
||||
is_callable = True
|
||||
ref_directive = "meth"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonMethod, self).__init__(obj, **kwargs)
|
||||
|
||||
self.method_type = obj["method_type"]
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
if self.short_name == "__init__":
|
||||
return False
|
||||
|
||||
return super(PythonMethod, self).display
|
||||
|
||||
|
||||
class PythonData(PythonPythonMapper):
|
||||
"""Global, module level data."""
|
||||
|
||||
type = "data"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonData, self).__init__(obj, **kwargs)
|
||||
|
||||
self.value = obj.get("value")
|
||||
|
||||
|
||||
class PythonAttribute(PythonData):
|
||||
"""An object/class level attribute."""
|
||||
|
||||
type = "attribute"
|
||||
|
||||
|
||||
class TopLevelPythonPythonMapper(PythonPythonMapper):
|
||||
ref_directive = "mod"
|
||||
_RENDER_LOG_LEVEL = "VERBOSE"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(TopLevelPythonPythonMapper, self).__init__(obj, **kwargs)
|
||||
|
||||
self.top_level_object = "." not in self.name
|
||||
|
||||
self.subpackages = []
|
||||
self.submodules = []
|
||||
self.all = obj["all"]
|
||||
|
||||
@property
|
||||
def functions(self):
|
||||
return self._children_of_type("function")
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._children_of_type("class")
|
||||
|
||||
|
||||
class PythonModule(TopLevelPythonPythonMapper):
|
||||
type = "module"
|
||||
|
||||
|
||||
class PythonPackage(TopLevelPythonPythonMapper):
|
||||
type = "package"
|
||||
|
||||
|
||||
class PythonClass(PythonPythonMapper):
|
||||
type = "class"
|
||||
|
||||
def __init__(self, obj, **kwargs):
|
||||
super(PythonClass, self).__init__(obj, **kwargs)
|
||||
|
||||
self.bases = obj["bases"]
|
||||
|
||||
@PythonPythonMapper.args.getter
|
||||
def args(self):
|
||||
args = self._args
|
||||
|
||||
constructor = self.constructor
|
||||
if constructor:
|
||||
args = constructor.args
|
||||
|
||||
if args.startswith("self"):
|
||||
args = args[4:].lstrip(",").lstrip()
|
||||
|
||||
return args
|
||||
|
||||
@PythonPythonMapper.docstring.getter
|
||||
def docstring(self):
|
||||
docstring = super(PythonClass, self).docstring
|
||||
|
||||
if self._class_content in ("both", "init"):
|
||||
constructor_docstring = self.constructor_docstring
|
||||
if constructor_docstring:
|
||||
if self._class_content == "both":
|
||||
docstring = "{0}\n{1}".format(docstring, constructor_docstring)
|
||||
else:
|
||||
docstring = constructor_docstring
|
||||
|
||||
return docstring
|
||||
|
||||
@property
|
||||
def methods(self):
|
||||
return self._children_of_type("method")
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return self._children_of_type("attribute")
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._children_of_type("class")
|
||||
|
||||
@property
|
||||
def constructor(self):
|
||||
for child in self.children:
|
||||
if child.short_name == "__init__":
|
||||
return child
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def constructor_docstring(self):
|
||||
docstring = ""
|
||||
|
||||
constructor = self.constructor
|
||||
if constructor and constructor.docstring:
|
||||
docstring = constructor.docstring
|
||||
else:
|
||||
for child in self.children:
|
||||
if child.short_name == "__new__":
|
||||
docstring = child.docstring
|
||||
break
|
||||
|
||||
return docstring
|
||||
|
||||
|
||||
class PythonException(PythonClass):
|
||||
type = "exception"
|
@ -0,0 +1,224 @@
|
||||
import collections
|
||||
import os
|
||||
|
||||
import astroid
|
||||
from . import astroid_utils
|
||||
|
||||
try:
|
||||
_TEXT_TYPE = unicode
|
||||
except NameError:
|
||||
_TEXT_TYPE = str
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self):
|
||||
self._name_stack = []
|
||||
self._encoding = None
|
||||
|
||||
def _get_full_name(self, name):
|
||||
return ".".join(self._name_stack + [name])
|
||||
|
||||
def _encode(self, to_encode):
|
||||
if self._encoding:
|
||||
try:
|
||||
return _TEXT_TYPE(to_encode, self._encoding)
|
||||
except TypeError:
|
||||
# The string was already in the correct format
|
||||
pass
|
||||
|
||||
return to_encode
|
||||
|
||||
def parse_file(self, file_path):
|
||||
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")):
|
||||
directory, module_part = os.path.split(directory)
|
||||
if module_part:
|
||||
module_parts.appendleft(module_part)
|
||||
|
||||
module_name = ".".join(module_parts)
|
||||
node = astroid.MANAGER.ast_from_file(file_path, module_name)
|
||||
return self.parse(node)
|
||||
|
||||
def parse_assign(self, node):
|
||||
doc = ""
|
||||
doc_node = node.next_sibling()
|
||||
if isinstance(doc_node, astroid.nodes.Expr) and isinstance(
|
||||
doc_node.value, astroid.nodes.Const
|
||||
):
|
||||
doc = doc_node.value.value
|
||||
|
||||
type_ = "data"
|
||||
if isinstance(
|
||||
node.scope(), astroid.nodes.ClassDef
|
||||
) or astroid_utils.is_constructor(node.scope()):
|
||||
type_ = "attribute"
|
||||
|
||||
assign_value = astroid_utils.get_assign_value(node)
|
||||
if not assign_value:
|
||||
return []
|
||||
|
||||
target, value = assign_value
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": target,
|
||||
"full_name": self._get_full_name(target),
|
||||
"doc": self._encode(doc),
|
||||
"value": value,
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
return [data]
|
||||
|
||||
def parse_classdef(self, node, data=None):
|
||||
type_ = "class"
|
||||
if astroid_utils.is_exception(node):
|
||||
type_ = "exception"
|
||||
|
||||
args = ""
|
||||
try:
|
||||
constructor = node.lookup("__init__")[1]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(constructor, astroid.nodes.FunctionDef):
|
||||
args = constructor.args.as_string()
|
||||
|
||||
basenames = list(astroid_utils.get_full_basenames(node.bases, node.basenames))
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"args": args,
|
||||
"bases": basenames,
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
"children": [],
|
||||
}
|
||||
|
||||
self._name_stack.append(node.name)
|
||||
for child in node.get_children():
|
||||
child_data = self.parse(child)
|
||||
if child_data:
|
||||
data["children"].extend(child_data)
|
||||
self._name_stack.pop()
|
||||
|
||||
return [data]
|
||||
|
||||
def _parse_property(self, node):
|
||||
data = {
|
||||
"type": "attribute",
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
return [data]
|
||||
|
||||
def parse_functiondef(self, node):
|
||||
if astroid_utils.is_decorated_with_property(node):
|
||||
return self._parse_property(node)
|
||||
if astroid_utils.is_decorated_with_property_setter(node):
|
||||
return []
|
||||
|
||||
type_ = "function" if node.type == "function" else "method"
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": self._get_full_name(node.name),
|
||||
"args": node.args.as_string(),
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"from_line_no": node.fromlineno,
|
||||
"to_line_no": node.tolineno,
|
||||
}
|
||||
|
||||
if type_ == "method":
|
||||
data["method_type"] = node.type
|
||||
|
||||
result = [data]
|
||||
|
||||
if node.name == "__init__":
|
||||
for child in node.get_children():
|
||||
if isinstance(child, astroid.Assign):
|
||||
child_data = self.parse_assign(child)
|
||||
result.extend(data for data in child_data if data["doc"])
|
||||
|
||||
return result
|
||||
|
||||
def _parse_local_import_from(self, node):
|
||||
result = []
|
||||
|
||||
for name, alias in node.names:
|
||||
is_wildcard = (alias or name) == "*"
|
||||
full_name = self._get_full_name(alias or name)
|
||||
original_path = astroid_utils.get_full_import_name(node, alias or name)
|
||||
|
||||
data = {
|
||||
"type": "placeholder",
|
||||
"name": original_path if is_wildcard else (alias or name),
|
||||
"full_name": full_name,
|
||||
"original_path": original_path,
|
||||
}
|
||||
result.append(data)
|
||||
|
||||
return result
|
||||
|
||||
def parse_module(self, node):
|
||||
path = node.path
|
||||
if isinstance(node.path, list):
|
||||
path = node.path[0] if node.path else None
|
||||
|
||||
type_ = "module"
|
||||
if node.package:
|
||||
type_ = "package"
|
||||
|
||||
self._name_stack = [node.name]
|
||||
self._encoding = node.file_encoding
|
||||
|
||||
data = {
|
||||
"type": type_,
|
||||
"name": node.name,
|
||||
"full_name": node.name,
|
||||
"doc": self._encode(node.doc or ""),
|
||||
"children": [],
|
||||
"file_path": path,
|
||||
"encoding": node.file_encoding,
|
||||
"all": astroid_utils.get_module_all(node),
|
||||
}
|
||||
|
||||
top_name = node.name.split(".", 1)[0]
|
||||
for child in node.get_children():
|
||||
if node.package and astroid_utils.is_local_import_from(child, top_name):
|
||||
child_data = self._parse_local_import_from(child)
|
||||
else:
|
||||
child_data = self.parse(child)
|
||||
|
||||
if child_data:
|
||||
data["children"].extend(child_data)
|
||||
|
||||
return data
|
||||
|
||||
def parse(self, node):
|
||||
data = {}
|
||||
|
||||
node_type = node.__class__.__name__.lower()
|
||||
parse_func = getattr(self, "parse_" + node_type, None)
|
||||
if parse_func:
|
||||
data = parse_func(node)
|
||||
else:
|
||||
for child in node.get_children():
|
||||
data = self.parse(child)
|
||||
if data:
|
||||
break
|
||||
|
||||
return data
|
@ -1,30 +1,29 @@
|
||||
.. dn:{{ obj.ref_type }}:: {{ obj.name }}
|
||||
|
||||
{% if obj.summary %}
|
||||
|
||||
{{ obj.summary|indent(4) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{%- for param in obj.parameters %}
|
||||
{% for param in obj.parameters %}
|
||||
|
||||
{% if param.desc %}
|
||||
:param {{ param.name }}: {{ param.desc|indent(8) }}
|
||||
{% endif %}
|
||||
{%- if param.type %}
|
||||
{% if param.type %}
|
||||
:type {{ param.name }}: {{ param.type|indent(8) }}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{%- if obj.returns.type %}
|
||||
{% if obj.returns.type %}
|
||||
:rtype: {{ obj.returns.type|indent(8) }}
|
||||
{%- endif %}
|
||||
{%- if obj.returns.description %}
|
||||
{% endif %}
|
||||
{% if obj.returns.description %}
|
||||
:return: {{ obj.returns.description|indent(8) }}
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if obj.example %}
|
||||
.. code-block:: csharp
|
||||
|
||||
{{ obj.example|indent(8) }}
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
@ -1,35 +1,25 @@
|
||||
{%- if obj.display %}
|
||||
|
||||
{% if obj.display %}
|
||||
.. py:{{ obj.type }}:: {{ obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %}
|
||||
|
||||
{%- if obj.bases %}
|
||||
|
||||
Bases: {%- for base in obj.bases %}:class:`{{ base }}`{%- if not loop.last %}, {% endif %}{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
{% if obj.bases %}
|
||||
Bases: {% for base in obj.bases %}:class:`{{ base }}`{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
|
||||
{%- if obj.docstring %}
|
||||
|
||||
{% endif %}
|
||||
{% if obj.docstring %}
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{%- for klass in obj.classes %}
|
||||
|
||||
{% set visible_classes = obj.classes|selectattr("display")|list %}
|
||||
{% for klass in visible_classes %}
|
||||
{{ klass.rendered|indent(3) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{%- for attribute in obj.attributes %}
|
||||
|
||||
{% set visible_attributes = obj.attributes|selectattr("display")|list %}
|
||||
{% for attribute in visible_attributes %}
|
||||
{{ attribute.rendered|indent(3) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{%- for method in obj.methods %}
|
||||
|
||||
{% set visible_methods = obj.methods|selectattr("display")|list %}
|
||||
{% for method in visible_methods %}
|
||||
{{ method.rendered|indent(3) }}
|
||||
|
||||
{%- endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
@ -1,8 +1,7 @@
|
||||
{%- if obj.display %}
|
||||
|
||||
{% if obj.display %}
|
||||
.. {{ obj.type }}:: {{ obj.name }}
|
||||
{% if obj.value is not none %}:annotation: = {{ obj.value }} {% endif %}
|
||||
{%+ if obj.value is not none %}:annotation: = {{ obj.value }}{% endif %}
|
||||
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
{% endif %}
|
||||
|
@ -1,9 +1,7 @@
|
||||
{%- if obj.display %}
|
||||
|
||||
{% if obj.display %}
|
||||
.. function:: {{ obj.short_name }}({{ obj.args }})
|
||||
|
||||
{% if obj.docstring %}
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
@ -1,89 +1,94 @@
|
||||
{% if not obj.display %}:orphan:{% endif %}
|
||||
{% if obj.docstring or obj.subpackages or obj.submodules or obj.children %}
|
||||
{% if not obj.display %}
|
||||
:orphan:
|
||||
|
||||
{% endif %}
|
||||
:mod:`{{ obj.name }}`
|
||||
======={{ "=" * obj.name|length }}
|
||||
|
||||
.. py:module:: {{ obj.name }}
|
||||
|
||||
{%- if obj.docstring %}
|
||||
|
||||
{% if obj.docstring %}
|
||||
.. autoapi-nested-parse::
|
||||
|
||||
{{ obj.docstring|prepare_docstring|indent(3) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% block subpackages %}{% if obj.subpackages %}
|
||||
{% block subpackages %}
|
||||
{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
|
||||
{% if visible_subpackages %}
|
||||
Subpackages
|
||||
-----------
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 3
|
||||
{% for subpackage in obj.subpackages %}
|
||||
{%- if subpackage.display %}
|
||||
|
||||
{% for subpackage in visible_subpackages %}
|
||||
{{ subpackage.short_name }}/index.rst
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% block submodules %}{% if obj.submodules %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block submodules %}
|
||||
{% set visible_submodules = obj.submodules|selectattr("display")|list %}
|
||||
{% if visible_submodules %}
|
||||
Submodules
|
||||
----------
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
{% for submodule in obj.submodules %}
|
||||
{%- if submodule.display %}
|
||||
|
||||
{% for submodule in visible_submodules %}
|
||||
{{ submodule.short_name }}/index.rst
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% block content %}{% if obj.children %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% set visible_children = obj.children|selectattr("display")|list %}
|
||||
{% if visible_children %}
|
||||
{{ obj.type|title }} Contents
|
||||
{{ "-" * obj.type|length }}---------
|
||||
|
||||
{% if include_summaries %}
|
||||
{% block classes %}{% if obj.classes %}
|
||||
{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %}
|
||||
{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %}
|
||||
{% if include_summaries and (visible_classes or visible_functions) %}
|
||||
{% block classes %}
|
||||
{% if visible_classes %}
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
{% for klass in obj.classes %}
|
||||
{{ klass.id }}
|
||||
{%- endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
|
||||
{% block methods %}{% if obj.methods %}
|
||||
Methods
|
||||
~~~~~~~
|
||||
{% for klass in visible_classes %}
|
||||
{{ klass.id }}
|
||||
{% endfor %}
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for method in obj.methods %}
|
||||
{{ method.id }}
|
||||
{%- endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block functions %}{% if obj.functions %}
|
||||
{% block functions %}
|
||||
{% if visible_functions %}
|
||||
Functions
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for function in obj.functions %}
|
||||
{% for function in visible_functions %}
|
||||
{{ function.id }}
|
||||
{%- endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{%- for obj_item in obj.children %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% for obj_item in visible_children %}
|
||||
{% if obj.all is none or obj_item.short_name in obj.all %}
|
||||
{{ obj_item.rendered|indent(0) }}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}{% endblock %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue