Exemplo n.º 1
0
def test_add_children():
    """Add multiple children at once."""
    root = Object(name="o", path="o", file_path="o.py")

    class_ = Class(name="c", path="o.c", file_path="o.py")
    attribute = Attribute(name="a", path="o.c.a", file_path="o.py")
    class_.add_child(attribute)

    root.add_children([
        # class has wrong path
        Class(name="w", path="wrong.path.w", file_path="/wrong/path/w.py"),
        # class OK
        class_,
        # not a direct child,
        attribute,
        # function OK
        Function(name="f", path="o.f", file_path="o.py"),
        # not a direct child, not even a child of known child
        Method(name="missing_node", path="o.mn.missing_node",
               file_path="o.py"),
    ])

    assert len(root.children) == 2
    assert root.classes and root.classes[0] is class_
    assert root.functions and root.functions[0].name == "f"
Exemplo n.º 2
0
def test_name_properties_on_class():
    assert not Class(name="b", path="a.b", file_path="a.py").name_properties
    assert "private" in Class(name="_b", path="a._b",
                              file_path="a.py").name_properties
    assert not Class(name="__b", path="a.__b",
                     file_path="a.py").name_properties
    assert not Class(name="__b__", path="a.__b__",
                     file_path="a.py").name_properties
Exemplo n.º 3
0
def test_get_root():
    root = Module(name="my_module", path="my.dotted.path", file_path="")
    node1 = Class(name="my_class1", path="my.dotted.path.my_class1", file_path="")
    node2 = Class(name="my_class2", path="my.dotted.path.my_class2", file_path="")
    leaf = Method(name="my_method", path="my.dotted.path.my_class1.my_method", file_path="")

    root.add_children([node1, node2])
    node1.add_child(leaf)

    assert root.root is root
    assert node1.root is root
    assert node2.root is root
    assert leaf.root is root
Exemplo n.º 4
0
    def get_class_documentation(self,
                                node: ObjectNode,
                                select_members=None) -> Class:
        """
        Get the documentation for a class and its children.

        Arguments:
            node: The node representing the class and its parents.
            select_members: Explicit members to select.

        Returns:
            The documented class object.
        """
        class_ = node.obj
        docstring = inspect.cleandoc(class_.__doc__ or "")
        root_object = Class(name=node.name,
                            path=node.dotted_path,
                            file_path=node.file_path,
                            docstring=docstring)

        # Even if we don't select members, we want to correctly parse the docstring
        attributes_data: Dict[str, Dict[str, Any]] = {}
        for parent_class in reversed(class_.__mro__[:-1]):
            merge(attributes_data, get_class_attributes(parent_class))
        context: Dict[str, Any] = {"attributes": attributes_data}
        if "__init__" in class_.__dict__:
            try:
                attributes_data.update(get_instance_attributes(
                    class_.__init__))
                context["signature"] = inspect.signature(class_.__init__)
            except (TypeError, ValueError):
                pass
        root_object.parse_docstring(self.docstring_parser,
                                    attributes=attributes_data)

        if select_members is False:
            return root_object

        select_members = select_members or set()

        # Build the list of members
        members = {}
        inherited = set()
        direct_members = class_.__dict__
        all_members = dict(inspect.getmembers(class_))
        for member_name, member in all_members.items():
            if member is class_:
                continue
            if not (member is type or member is object) and self.select(
                    member_name, select_members):
                if member_name not in direct_members:
                    if self.select_inherited_members:
                        members[member_name] = member
                        inherited.add(member_name)
                else:
                    members[member_name] = member

        # Iterate on the selected members
        child: Object
        for member_name, member in members.items():
            child_node = ObjectNode(member, member_name, parent=node)
            if child_node.is_class():
                child = self.get_class_documentation(child_node)
            elif child_node.is_classmethod():
                child = self.get_classmethod_documentation(child_node)
            elif child_node.is_staticmethod():
                child = self.get_staticmethod_documentation(child_node)
            elif child_node.is_method():
                child = self.get_regular_method_documentation(child_node)
            elif child_node.is_property():
                child = self.get_property_documentation(child_node)
            elif member_name in attributes_data:
                child = self.get_attribute_documentation(
                    child_node, attributes_data[member_name])
            else:
                continue
            if member_name in inherited:
                child.properties.append("inherited")
            root_object.add_child(child)

        for attr_name, properties, add_method in (
            ("__fields__", ["pydantic-model"],
             self.get_pydantic_field_documentation),
            ("_declared_fields", ["marshmallow-model"],
             self.get_marshmallow_field_documentation),
            ("__dataclass_fields__", ["dataclass"],
             self.get_annotated_dataclass_field),
        ):
            if self.detect_field_model(attr_name, direct_members, all_members):
                root_object.properties.extend(properties)
                self.add_fields(
                    node,
                    root_object,
                    attr_name,
                    all_members,
                    select_members,
                    class_,
                    add_method,
                )
                break

        return root_object
Exemplo n.º 5
0
    def get_class_documentation(self, node: ObjectNode, members=None) -> Class:
        """
        Get the documentation for a class and its children.

        Arguments:
            node: The node representing the class and its parents.
            members: Explicit members to select.

        Return:
            The documented class object.
        """
        class_ = node.obj
        docstring = textwrap.dedent(class_.__doc__ or "")
        root_object = Class(name=node.name,
                            path=node.dotted_path,
                            file_path=node.file_path,
                            docstring=docstring)

        if members is False:
            return root_object

        members = members or set()

        for member_name, member in class_.__dict__.items():
            if member is type or member is object:
                continue

            if not self.select(member_name, members):  # type: ignore
                continue

            child_node = ObjectNode(getattr(class_, member_name),
                                    member_name,
                                    parent=node)
            if child_node.is_class():
                root_object.add_child(self.get_class_documentation(child_node))
            elif child_node.is_classmethod():
                root_object.add_child(
                    self.get_classmethod_documentation(child_node))
            elif child_node.is_staticmethod():
                root_object.add_child(
                    self.get_staticmethod_documentation(child_node))
            elif child_node.is_method():
                root_object.add_child(
                    self.get_regular_method_documentation(child_node))
            elif child_node.is_property():
                root_object.add_child(
                    self.get_property_documentation(child_node))

        # First check if this is Pydantic compatible
        if "__fields__" in class_.__dict__:
            root_object.properties = ["pydantic"]
            for field_name, model_field in class_.__dict__.get(
                    "__fields__", {}).items():
                if self.select(field_name, members):  # type: ignore
                    child_node = ObjectNode(obj=model_field,
                                            name=field_name,
                                            parent=node)
                    root_object.add_child(
                        self.get_pydantic_field_documentation(child_node))

        # Handle dataclasses
        elif "__dataclass_fields__" in class_.__dict__:
            root_object.properties = ["dataclass"]
            for field_name, annotation in class_.__dict__.get(
                    "__annotations__", {}).items():
                if self.select(field_name, members):  # type: ignore
                    child_node = ObjectNode(obj=annotation,
                                            name=field_name,
                                            parent=node)
                    root_object.add_child(
                        self.get_annotated_dataclass_field(child_node))

        return root_object
Exemplo n.º 6
0
    def get_class_documentation(self,
                                node: ObjectNode,
                                select_members=None) -> Class:
        """
        Get the documentation for a class and its children.

        Arguments:
            node: The node representing the class and its parents.
            select_members: Explicit members to select.

        Return:
            The documented class object.
        """
        class_ = node.obj
        docstring = textwrap.dedent(class_.__doc__ or "")
        root_object = Class(name=node.name,
                            path=node.dotted_path,
                            file_path=node.file_path,
                            docstring=docstring)

        # Even if we don't select members, we want to correctly parse the docstring
        attributes_data: Dict[str, Dict[str, Any]] = {}
        for cls in reversed(class_.__mro__[:-1]):
            merge(attributes_data, get_class_attributes(cls))
        context: Dict[str, Any] = {"attributes": attributes_data}
        if "__init__" in class_.__dict__:
            attributes_data.update(get_instance_attributes(class_.__init__))
            context["signature"] = inspect.signature(class_.__init__)
        root_object.parse_docstring(self.docstring_parser,
                                    attributes=attributes_data)

        if select_members is False:
            return root_object

        select_members = select_members or set()

        # Build the list of members
        members = {}
        inherited = set()
        direct_members = class_.__dict__
        all_members = dict(inspect.getmembers(class_))
        for member_name, member in all_members.items():
            if not (member is type or member is object) and self.select(
                    member_name, select_members):
                if member_name not in direct_members:
                    if self.select_inherited_members:
                        members[member_name] = member
                        inherited.add(member_name)
                else:
                    members[member_name] = member

        # Iterate on the selected members
        child: Object
        for member_name, member in members.items():
            child_node = ObjectNode(member, member_name, parent=node)
            if child_node.is_class():
                child = self.get_class_documentation(child_node)
            elif child_node.is_classmethod():
                child = self.get_classmethod_documentation(child_node)
            elif child_node.is_staticmethod():
                child = self.get_staticmethod_documentation(child_node)
            elif child_node.is_method():
                child = self.get_regular_method_documentation(child_node)
            elif child_node.is_property():
                child = self.get_property_documentation(child_node)
            elif member_name in attributes_data:
                child = self.get_attribute_documentation(
                    child_node, attributes_data[member_name])
            else:
                continue
            if member_name in inherited:
                child.properties.append("inherited")
            root_object.add_child(child)

        # First check if this is Pydantic compatible
        if "__fields__" in direct_members or (self.select_inherited_members
                                              and "__fields__" in all_members):
            root_object.properties = ["pydantic-model"]
            for field_name, model_field in all_members["__fields__"].items():
                if self.select(
                        field_name, select_members
                ) and (  # type: ignore
                        self.select_inherited_members
                        # When we don't select inherited members, one way to tell if a field was inherited
                        # is to check if it exists in parent classes __fields__ attributes.
                        # We don't check the current class, nor the top one (object), hence __mro__[1:-1]
                        or field_name
                        not in chain(*(getattr(cls, "__fields__", {}).keys()
                                       for cls in class_.__mro__[1:-1]))):
                    child_node = ObjectNode(obj=model_field,
                                            name=field_name,
                                            parent=node)
                    root_object.add_child(
                        self.get_pydantic_field_documentation(child_node))

        # Handle dataclasses
        elif "__dataclass_fields__" in direct_members or (
                self.select_inherited_members and "__fields__" in all_members):
            root_object.properties = ["dataclass"]
            for field_name, annotation in all_members["__annotations__"].items(
            ):
                if self.select(
                        field_name, select_members) and (  # type: ignore
                            self.select_inherited_members
                            # Same comment as for Pydantic models
                            or field_name
                            not in chain(*(getattr(cls, "__dataclass_fields__",
                                                   {}).keys()
                                           for cls in class_.__mro__[1:-1]))):
                    child_node = ObjectNode(obj=annotation,
                                            name=field_name,
                                            parent=node)
                    root_object.add_child(
                        self.get_annotated_dataclass_field(child_node))

        return root_object
Exemplo n.º 7
0
def test_creating_class():
    assert Class(name="my_object", path="my.dotted.path", file_path="/my/absolute/path.py")