def test_name_properties_on_attribute():
    assert not Attribute(name="b", path="a.b",
                         file_path="a.py").name_properties
    assert "private" in Attribute(name="_b", path="a._b",
                                  file_path="a.py").name_properties
    assert "class-private" in Attribute(name="__b",
                                        path="a.__b",
                                        file_path="a.py").name_properties
    assert "special" in Attribute(name="__b__",
                                  path="a.__b__",
                                  file_path="a.py").name_properties
Exemple #2
0
    def get_attribute_documentation(
            node: ObjectNode,
            attribute_data: Optional[dict] = None) -> Attribute:
        """
        Get the documentation for an attribute.

        Arguments:
            node: The node representing the method and its parents.
            attribute_data: Docstring and annotation for this attribute.

        Returns:
            The documented attribute object.
        """
        if attribute_data is None:
            if node.parent_is_class():
                attribute_data = get_class_attributes(node.parent.obj).get(
                    node.name, {})  # type: ignore
            else:
                attribute_data = get_module_attributes(node.root.obj).get(
                    node.name, {})
        return Attribute(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=attribute_data.get("docstring", ""),
            attr_type=attribute_data.get("annotation", None),
        )
Exemple #3
0
    def get_annotated_dataclass_field(
            node: ObjectNode,
            attribute_data: Optional[dict] = None) -> Attribute:
        """
        Get the documentation for a dataclass field.

        Arguments:
            node: The node representing the annotation and its parents.
            attribute_data: Docstring and annotation for this attribute.

        Returns:
            The documented attribute object.
        """
        if attribute_data is None:
            if node.parent_is_class():
                attribute_data = get_class_attributes(node.parent.obj).get(
                    node.name, {})  # type: ignore
            else:
                attribute_data = get_module_attributes(node.root.obj).get(
                    node.name, {})

        return Attribute(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=attribute_data["docstring"],
            attr_type=attribute_data["annotation"],
            properties=["dataclass-field"],
        )
Exemple #4
0
def test_add_children():
    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"
Exemple #5
0
    def get_django_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Django Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["django-field"]

        if prop.null:
            properties.append("nullable")
        if prop.blank:
            properties.append("blank")

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=prop.verbose_name,
            attr_type=prop.__class__,
            properties=properties,
        )
Exemple #6
0
def test_do_not_add_child_if_parent_is_not_self():
    """Don't add a child the parent is not the right one."""
    parent = Module(name="my_module",
                    path="my.dotted.path",
                    file_path="/my/absolute/path.py")
    child = Attribute(name="my_attribute",
                      path="my.other.path.my_attribute",
                      file_path="/my/absolute/path.py")
    parent.add_child(child)
    assert not parent.children
    assert not parent.attributes
Exemple #7
0
def test_add_child():
    """Add a child."""
    parent = Module(name="my_module",
                    path="my.dotted.path",
                    file_path="/my/absolute/path.py")
    child = Attribute(name="my_attribute",
                      path="my.dotted.path.my_attribute",
                      file_path="/my/absolute/path.py")
    parent.add_child(child)
    assert parent.children[0] is child
    assert parent.attributes[0] is child
Exemple #8
0
    def get_property_documentation(self, node: ObjectNode) -> Attribute:
        """
        Get the documentation for a property.

        Arguments:
            node: The node representing the property and its parents.

        Returns:
            The documented attribute object (properties are considered attributes for now).
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["property"]
        if node.is_cached_property():
            # cached_property is always writable, see the docs
            properties.extend(["writable", "cached"])
            sig_source_func = prop.func
        else:
            properties.append("readonly" if prop.fset is None else "writable")
            sig_source_func = prop.fget

        source: Optional[Source]

        try:
            signature = inspect.signature(sig_source_func)
        except (TypeError, ValueError) as error:
            self.errors.append(f"Couldn't get signature for '{path}': {error}")
            attr_type = None
        else:
            attr_type = signature.return_annotation

        try:
            source = Source(*inspect.getsourcelines(sig_source_func))
        except (OSError, TypeError) as error:
            self.errors.append(f"Couldn't get source for '{path}': {error}")
            source = None

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=inspect.getdoc(prop),
            attr_type=attr_type,
            properties=properties,
            source=source,
        )
Exemple #9
0
def _get_attributes(
    ast_body: Iterable, name_prefix: str, file_path: str, properties: Optional[List[str]] = None
) -> List[Attribute]:
    if not properties:
        properties = []

    documented_attributes = []
    previous_node = None
    body: Any

    for node in ast_body:
        try:
            attr_info = get_attribute_info(previous_node, node)
        except ValueError:
            if isinstance(node, RECURSIVE_NODES):
                documented_attributes.extend(_get_attributes(node.body, name_prefix, file_path, properties))  # type: ignore
                if isinstance(node, ast.Try):
                    for body in [node.handlers, node.orelse, node.finalbody]:
                        documented_attributes.extend(_get_attributes(body, name_prefix, file_path, properties))
                elif isinstance(node, ast.If):
                    documented_attributes.extend(_get_attributes(node.orelse, name_prefix, file_path, properties))

            elif isinstance(node, ast.FunctionDef) and node.name == "__init__":
                documented_attributes.extend(_get_attributes(node.body, name_prefix, file_path))

            elif isinstance(node, ast.ClassDef):
                documented_attributes.extend(
                    _get_attributes(node.body, f"{name_prefix}.{node.name}", file_path, properties=["class-attribute"])
                )
        else:
            for name in attr_info["names"]:
                documented_attributes.append(
                    Attribute(
                        name=name,
                        path=f"{name_prefix}.{name}",
                        file_path=file_path,
                        docstring=dedent(attr_info["docstring"]),
                        properties=properties,
                        attr_type=attr_info["type"],
                    )
                )
        previous_node = node

    return documented_attributes
Exemple #10
0
    def get_annotated_dataclass_field(self, node: ObjectNode) -> Attribute:
        """
        Get the documentation for an dataclass annotation.

        Arguments:
            node: The node representing the annotation and its parents.

        Return:
            The documented attribute object.
        """
        annotation: type = node.obj
        path = node.dotted_path
        properties = ["field"]

        return Attribute(name=node.name,
                         path=path,
                         file_path=node.file_path,
                         attr_type=annotation,
                         properties=properties)
Exemple #11
0
    def get_property_documentation(self, node: ObjectNode) -> Attribute:
        """
        Get the documentation for an attribute.

        Arguments:
            node: The node representing the attribute and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = [
            "property", "readonly" if prop.fset is None else "writable"
        ]
        source: Optional[Source]

        try:
            signature = inspect.signature(prop.fget)
        except (TypeError, ValueError) as error:
            self.errors.append(f"Couldn't get signature for '{path}': {error}")
            attr_type = None
        else:
            attr_type = signature.return_annotation

        try:
            source = Source(*inspect.getsourcelines(prop.fget))
        except (OSError, TypeError) as error:
            self.errors.append(f"Couldn't get source for '{path}': {error}")
            source = None

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=inspect.getdoc(prop.fget),
            attr_type=attr_type,
            properties=properties,
            source=source,
        )
Exemple #12
0
    def get_marshmallow_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Marshmallow Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["marshmallow-field"]
        if prop.required:
            properties.append("required")

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=prop.metadata.get("description"),
            attr_type=type(prop),
            properties=properties,
        )
Exemple #13
0
    def get_pydantic_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Pydantic Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["pydantic-field"]
        if prop.required:
            properties.append("required")

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=prop.field_info.description,
            attr_type=prop.type_,
            properties=properties,
        )
Exemple #14
0
def test_creating_attribute():
    assert Attribute(name="my_object", path="my.dotted.path", file_path="/my/absolute/path.py")