Skip to content

Add attribute assignment dependencies in attribute_dependencies #75

@thorwhalen

Description

@thorwhalen

In from i2.footprints import attribute_dependencies, we only graph dependencies from a method to the attributes it uses.

I would be more complete to also collect the dependency edges of assignments within method's code.
Yes, developers shouldn't be doing this too often, because mutability is evil, but they will, and seeing this in dependency graphs can help them to spot these evil dependencies and restructure the code.

So we need more.

I started doing some AST node visitors to try to get these in-code assignments dependencies, but need to stop this for now, so mentioning the code here below.

class AssignedAttributeVisitor(ast.NodeVisitor):
    """
    A visitor that extracts attributes involved in assignments.

    That is, if the object_name='self', it will extract the 'attr' if there's a line like:
    self.attr = ...

    Example:

    >>> src_code = '''
    ... class MyClass:
    ...     x = 1
    ...     z = 3
    ...     def my_method(self):
    ...         self.y = self.x + 1
    ...         return self.z + self.y
    ... '''
    >>> visitor = AssignedAttributeVisitor('self')
    >>> visitor.visit(ast.parse(dedent(src_code)))

    >>> assert visitor.assigned_attributes == [(['y'], ['x'])]

    """
    def __init__(self, object_name):
        self.object_name = object_name
        self.assigned_attributes = list()

    def visit_Assign(self, node: ast.Assign):
        # Check if the target of assignment matches the object name
        for target in node.targets:
            # If both the right side and the left side of the assignment involve
            # attributes of the object, we should add these attributes to the list
            if (
                isinstance(target, ast.Attribute) 
                and target.value.id == self.object_name
                and isinstance(node.value, ast.Attribute)
            ):
                self.assigned_attributes.append((target.attr, node.value.attr))
        self.generic_visit(node)
def assigned_attributes(func, object_name=None):
    """
    Extracts those attributes of an object that are assigned to in a function or method.
    """
    if object_name is None:
        object_name = next(iter(Sig(func)), None)
        if object_name is None:
            raise ValueError(
                'Could not determine the object name. Please provide it explicitly.'
            )

    # Convert the function source to an AST
    func_name = qualname_of_obj(func)
    src = _get_definition_source(func_name, _get_source(func))
    node = ensure_ast(src)
    # Initialize the visitor with the target object name
    visitor = AttributeVisitor(object_name)
    # Visit the AST to find accessed attributes
    visitor.visit(node)
    # Return the set of accessed attributes
    return visitor.attributes

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions