Skip to main content

Querying the graph

Everything below is a method on Graph. All of them take and return node IDs or node objects — there is no query language to learn.

By kind, file, or name

from callix import NodeKind

graph.nodes_by_kind(NodeKind.FUNCTION)
graph.nodes_in_file("src/app/service.py")
graph.nodes_by_name("UserService") # matches the short OR the qualified name

Paths are the project-relative ones the FILE nodes advertise, not absolute ones — see Nodes.

Edges

graph.outgoing(node_id) # relations leaving the node
graph.incoming(node_id) # relations entering it

Call and reference navigation

graph.callees(node_id) # what it calls
graph.callers(node_id) # what calls it
graph.references_to(node_id)

A worked example — everything that would be affected by changing a function:

target = graph.nodes_by_name("charge_card")[0]

for caller in graph.callers(target.id):
print(caller.qualified_name, caller.file_path)

Neighbourhoods and subgraphs

graph.neighbors(node_id, depth=2) # distinct nodes within 2 hops, either way
graph.subgraph([id1, id2, ...]) # those nodes plus every incident edge
graph.subgraph_for_file("src/app/service.py")

subgraph() returns a real Graph, so it serializes and diffs like any other. Its nodes keep the parent's insertion order rather than the order of the ids you passed, so the result is byte-stable and usable as a cache key or a golden file.

Across services

graph.merge(other, allow_shared=True) # one graph per language, combined
graph.link_boundaries() # -> int, the COMMUNICATES_WITH edges added

See Cross-language analysis.

Metadata

Node metadata carries whatever the adapter learned that does not fit the schema:

node.metadata["origin"] # on IMPORT / EXTERNAL_SYMBOL
node.metadata["import_path"] # on IMPORT
node.metadata["name_span"] # the identifier's span, used by resolution

Relation metadata carries the site:

relation.metadata["span"] # where the call or reference is written
relation.metadata["access"] # 'read' | 'write', on REFERENCES