Skip to content

get_instance_attributes

Source code in library_analyzer/processing/api/_get_instance_attributes.py
def get_instance_attributes(class_node: astroid.ClassDef) -> 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.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:
                                if isinstance(type_hint, Name):
                                    types.add(type_hint.name)
                                elif isinstance(type_hint, astroid.Attribute):
                                    types.add(type_hint.attrname)
                                elif (
                                    isinstance(type_hint, Subscript)
                                    and isinstance(type_hint.value, Name)
                                    and isinstance(type_hint.slice, Name)
                                ):
                                    value = type_hint.value.name
                                    slice_name = type_hint.slice.name
                                    if value == "Optional":
                                        types.add("NoneType")
                                        types.add(slice_name)
                                    else:
                                        types.add(value + "[" + slice_name + "]")
                                        remove_types.add(value)
                                        remove_types.add(value.lower())
                                elif (
                                    isinstance(type_hint, Subscript)
                                    and isinstance(type_hint.value, Name)
                                    and isinstance(type_hint.slice, astroid.Tuple)
                                    and type_hint.value.name == "Union"
                                ):
                                    for type_name in type_hint.slice.elts:
                                        if isinstance(type_name, Name):
                                            types.add(type_name.name)
                                    remove_types.add(type_hint.value.name)
                                    remove_types.add(type_hint.value.name.lower())
                            break
        types = types - remove_types
        if len(types) == 1:
            attributes.append(Attribute(name, NamedType(types.pop())))
        elif len(types) > 1:
            attributes.append(
                Attribute(name, UnionType([NamedType(type_) for type_ in types]))
            )
        else:
            attributes.append(Attribute(name, None))
    return attributes