Querying the graph
The GraphLens query methods are backed by edge indices, so lookups are fast
even on large graphs — you never scan relations by hand. This page collects
practical recipes. For the formal method list, see the
GraphLens API reference.
Finding a starting node
Most queries take a node id. Resolve a name to a node first:
from graphlens import NodeKind
# By name (matches short or qualified name)
candidates = graph.nodes_by_name("save")
# By kind
methods = graph.nodes_by_kind(NodeKind.METHOD)
# By file
in_file = graph.nodes_in_file("src/app/services.py")
node = candidates[0]
Call graph: callers and callees
graph.callers(node.id) # nodes whose CALLS edge points at node
graph.callees(node.id) # nodes node's CALLS edges point at
Build a reverse-call report — the functions with the most incoming calls:
funcs = graph.nodes_by_kind(NodeKind.FUNCTION) + graph.nodes_by_kind(NodeKind.METHOD)
hot = sorted(funcs, key=lambda n: len(graph.callers(n.id)), reverse=True)
for n in hot[:20]:
print(len(graph.callers(n.id)), n.qualified_name)
Find dead code candidates — functions nobody calls (mind dynamic dispatch and entry points before deleting anything):
orphans = [n for n in funcs if not graph.callers(n.id)]
References to a value
REFERENCES edges capture value uses of variables and attributes (as opposed to
calls). To find everywhere a definition is read:
var = graph.nodes_by_name("DEFAULT_TIMEOUT")[0]
graph.references_to(var.id)
Neighborhoods
neighbors returns the distinct nodes within depth hops in any direction —
handy for "show me everything around this symbol":
graph.neighbors(node.id, depth=1)
graph.neighbors(node.id, depth=3)
Working with raw edges
When you need the edge itself (to read its metadata, or to filter by an exact
kind), use outgoing/incoming:
from graphlens import RelationKind
# Every type annotation/inference edge leaving a function
for rel in graph.outgoing(node.id, RelationKind.HAS_TYPE):
target = graph.nodes[rel.target_id]
print(node.name, "has type", target.qualified_name, rel.metadata)
# Who imports this module
mod = graph.nodes_by_name("app.config")[0]
graph.incoming(mod.id, RelationKind.RESOLVES_TO)
Separating your code from the ecosystem
IMPORT and EXTERNAL_SYMBOL nodes carry metadata["origin"]. Count
third-party usage:
external = graph.nodes_by_kind(NodeKind.EXTERNAL_SYMBOL)
third_party = [n for n in external if n.metadata.get("origin") == "third_party"]
Inheritance
from graphlens import RelationKind
cls = graph.nodes_by_name("OrderService")[0]
# Base classes
bases = [graph.nodes[r.target_id] for r in graph.outgoing(cls.id, RelationKind.INHERITS_FROM)]
# Subclasses
subs = [graph.nodes[r.source_id] for r in graph.incoming(cls.id, RelationKind.INHERITS_FROM)]
Cross-language communication
Once you have run graphlens-link, follow
COMMUNICATES_WITH edges to see which consumer talks to which provider:
from graphlens import RelationKind
for rel in (r for r in graph.relations if r.kind == RelationKind.COMMUNICATES_WITH):
consumer = graph.nodes[rel.source_id]
provider = graph.nodes[rel.target_id]
print(f"{consumer.qualified_name} → {provider.qualified_name} ({rel.metadata})")
From the command line
The same four operations are available without writing code:
graphlens query process_order --graph graph.json --op callers
graphlens query process_order --graph graph.json --op callees
graphlens query OrderService.save --graph graph.json --op references
graphlens query OrderService.save --graph graph.json --op neighbors --depth 2