import collections import os import sys import astroid from . import astroid_utils 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 _decode(self, to_decode): if sys.version_info < (3,) and self._encoding: # pylint: disable=undefined-variable try: return unicode(to_decode, self._encoding) except TypeError: # The string was already in the correct format pass return to_decode 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 directory and condition(directory): 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, 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: os.path.abspath(directory) != dir_root ) def parse_annassign(self, node): return self.parse_assign(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 = assign_value[0] value = None try: value = self._decode(assign_value[1]) except UnicodeDecodeError: # Ignore binary data on Python 2.7 if sys.version_info[0] >= 3: raise annotation = astroid_utils.get_assign_annotation(node) data = { "type": type_, "name": target, "full_name": self._get_full_name(target), "doc": self._decode(doc), "value": value, "from_line_no": node.fromlineno, "to_line_no": node.tolineno, "annotation": annotation, } 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._decode(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_asyncfunctiondef(self, node): return self.parse_functiondef(node) def parse_functiondef(self, node): # pylint: disable=too-many-branches if astroid_utils.is_decorated_with_property_setter(node): return [] type_ = "method" properties = [] if node.type == "function": type_ = "function" elif astroid_utils.is_decorated_with_property(node): type_ = "property" properties.append("property") else: # "__new__" method is implicit classmethod if node.type in ("staticmethod", "classmethod") and node.name != "__new__": properties.append(node.type) if node.is_abstract(pass_is_abstract=False): properties.append("abstractmethod") if isinstance(node, astroid.AsyncFunctionDef): properties.append("async") return_annotation = None if node.returns: return_annotation = node.returns.as_string() # Python 2 has no support for type annotations, so use getattr elif getattr(node, "type_comment_returns", None): return_annotation = node.type_comment_returns.as_string() arg_string = astroid_utils.format_args(node.args) data = { "type": type_, "name": node.name, "full_name": self._get_full_name(node.name), "args": arg_string, "doc": self._decode(node.doc or ""), "from_line_no": node.fromlineno, "to_line_no": node.tolineno, "return_annotation": return_annotation, "properties": properties, } if type_ in ("method", "property"): data["method_type"] = node.type result = [data] if node.name == "__init__": for child in node.get_children(): if isinstance(child, (astroid.nodes.Assign, astroid.nodes.AnnAssign)): 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._decode(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 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