Visualizing the call trace of a function is a great way to understand the flow of a function and for debugging. In this tutorial we will create a call trace visualization of the patch method of the SharingConfigurationViewSet class. View the source code here.
We’ll create a function that will recursively traverse the call trace of a function and add nodes and edges to the graph:
def create_downstream_call_trace(src_func: Function, depth: int = 0): """Creates call graph by recursively traversing function calls Args: src_func (Function): Starting function for call graph depth (int): Current recursion depth """ # Prevent infinite recursion if MAX_DEPTH <= depth: return # External modules are not functions if isinstance(src_func, ExternalModule): return # Process each function call for call in src_func.function_calls: # Skip self-recursive calls if call.name == src_func.name: continue # Get called function definition func = call.function_definition if not func: continue # Apply configured filters if isinstance(func, ExternalModule) and IGNORE_EXTERNAL_MODULE_CALLS: continue if isinstance(func, Class) and IGNORE_CLASS_CALLS: continue # Generate display name (include class for methods) if isinstance(func, Class) or isinstance(func, ExternalModule): func_name = func.name elif isinstance(func, Function): func_name = f"{func.parent_class.name}.{func.name}" if func.is_method else func.name # Add node and edge with metadata G.add_node(func, name=func_name, color=COLOR_PALETTE.get(func.__class__.__name__)) G.add_edge(src_func, func, **generate_edge_meta(call)) # Recurse for regular functions if isinstance(func, Function): create_downstream_call_trace(func, depth + 1)
Finally, we can visualize our call graph starting from a specific function:
# Get target function to analyzetarget_class = codebase.get_class('SharingConfigurationViewSet')target_method = target_class.get_method('patch')# Add root node G.add_node(target_method, name=f"{target_class.name}.{target_method.name}", color=COLOR_PALETTE["StartFunction"])# Build the call graphcreate_downstream_call_trace(target_method)# Render the visualizationcodebase.visualize(G)
Understanding symbol dependencies is crucial for maintaining and refactoring code. This tutorial will show you how to create visual dependency graphs using Codegen and NetworkX. We will be creating a dependency graph of the get_query_runner function. View the source code here.
The core function for building our dependency graph:
def create_dependencies_visualization(symbol: Symbol, depth: int = 0): """Creates visualization of symbol dependencies Args: symbol (Symbol): Starting symbol to analyze depth (int): Current recursion depth """ # Prevent excessive recursion if depth >= MAX_DEPTH: return # Process each dependency for dep in symbol.dependencies: dep_symbol = None # Handle different dependency types if isinstance(dep, Symbol): # Direct symbol reference dep_symbol = dep elif isinstance(dep, Import): # Import statement - get resolved symbol dep_symbol = dep.resolved_symbol if dep.resolved_symbol else None if dep_symbol: # Add node with appropriate styling G.add_node(dep_symbol, color=COLOR_PALETTE.get(dep_symbol.__class__.__name__, "#f694ff")) # Add dependency relationship G.add_edge(symbol, dep_symbol) # Recurse unless it's a class (avoid complexity) if not isinstance(dep_symbol, PyClass): create_dependencies_visualization(dep_symbol, depth + 1)
Understanding the impact of code changes is crucial for safe refactoring. A blast radius visualization shows how changes to one function might affect other parts of the codebase by tracing usage relationships. In this tutorial we will create a blast radius visualization of the export_asset function. View the source code here.
We’ll create some utility functions to help build our visualization:
# List of HTTP methods to highlightHTTP_METHODS = ["get", "put", "patch", "post", "head", "delete"]def generate_edge_meta(usage: Usage) -> dict: """Generate metadata for graph edges Args: usage (Usage): Usage relationship information Returns: dict: Edge metadata including name and location """ return { "name": usage.match.source, "file_path": usage.match.filepath, "start_point": usage.match.start_point, "end_point": usage.match.end_point, "symbol_name": usage.match.__class__.__name__ }def is_http_method(symbol: PySymbol) -> bool: """Check if a symbol is an HTTP endpoint method Args: symbol (PySymbol): Symbol to check Returns: bool: True if symbol is an HTTP method """ if isinstance(symbol, PyFunction) and symbol.is_method: return symbol.name in HTTP_METHODS return False
The main function for creating our blast radius visualization:
def create_blast_radius_visualization(symbol: PySymbol, depth: int = 0): """Create visualization of symbol usage relationships Args: symbol (PySymbol): Starting symbol to analyze depth (int): Current recursion depth """ # Prevent excessive recursion if depth >= MAX_DEPTH: return # Process each usage of the symbol for usage in symbol.usages: usage_symbol = usage.usage_symbol # Determine node color based on type if is_http_method(usage_symbol): color = COLOR_PALETTE.get("HTTP_METHOD") else: color = COLOR_PALETTE.get(usage_symbol.__class__.__name__, "#f694ff") # Add node and edge to graph G.add_node(usage_symbol, color=color) G.add_edge(symbol, usage_symbol, **generate_edge_meta(usage)) # Recursively process usage symbol create_blast_radius_visualization(usage_symbol, depth + 1)
Finally, we can create our blast radius visualization:
# Get target function to analyzetarget_func = codebase.get_function('export_asset')# Add root nodeG.add_node(target_func, color=COLOR_PALETTE.get("StartFunction"))# Build the visualizationcreate_blast_radius_visualization(target_func)# Render graph to show impact flow# Note: a -> b means changes to a will impact bcodebase.visualize(G)