Skip to content

get_instance_attributes

Source code in src/library_analyzer/processing/api/_get_instance_attributes.py
def get_instance_attributes(class_node: astroid.ClassDef, class_id: str) -> list[Attribute]:
    attributes = []
    for name, assignments in class_node.instance_attrs.items():
        types = set()
        remove_types = {None}
        inference_context = InferenceContext()
        inference_context.lookupname = name
        inference_context.extra_context = class_node.instance_attrs
        for assignment in assignments:
            inferred_nodes = assignment.infer(context=inference_context)
            try:
                for inferred_node in inferred_nodes:
                    attribute_type = _get_type_of_attribute(inferred_node)
                    if attribute_type is not None:
                        types.add(attribute_type)
            except astroid.InferenceError:
                pass

            if isinstance(assignment, astroid.AssignAttr) and isinstance(assignment.parent, astroid.AnnAssign):
                annotation = assignment.parent.annotation
                if annotation is not None and isinstance(annotation, astroid.Attribute | Name | Subscript):
                    types_, remove_types_ = get_type_from_type_hint(annotation)
                    types = types.union(types_)
                    remove_types = remove_types.union(remove_types_)
            elif isinstance(assignment, astroid.AssignAttr) and isinstance(assignment.parent, astroid.Assign):
                attribute_type = _get_type_of_attribute(next(astroid.inference.infer_attribute(self=assignment)))
                if attribute_type is not None:
                    types.add(attribute_type)
                elif (
                    isinstance(assignment.parent.value, Name)
                    and isinstance(assignment.parent.parent, astroid.FunctionDef)
                    and assignment.parent.parent.name == "__init__"
                ):
                    init_function = assignment.parent.parent
                    parameter_name = assignment.parent.value.name
                    for arg in init_function.args.args:
                        i = init_function.args.args.index(arg)
                        if (
                            isinstance(
                                init_function.args.args[i],
                                astroid.nodes.node_classes.AssignName | Name,
                            )
                            and init_function.args.args[i].name == parameter_name
                        ):
                            type_hint = init_function.args.annotations[i]
                            if type_hint is not None and isinstance(type_hint, Attribute | Name | Subscript):
                                types_, remove_types_ = get_type_from_type_hint(type_hint)
                                types = types.union(types_)
                                remove_types = remove_types.union(remove_types_)
                            break
        types = types - remove_types
        if len(types) == 1:
            attributes.append(Attribute(f"{class_id}/{name}", name, NamedType(types.pop())))
        elif len(types) > 1:
            attributes.append(Attribute(f"{class_id}/{name}", name, UnionType([NamedType(type_) for type_ in types])))
        else:
            attributes.append(Attribute(f"{class_id}/{name}", name, None))
    return attributes