Пример #1
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
Пример #2
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