Skip to content

Graph Model

The graph model is the foundation of Honey Badgeria's backend. It consists of four core data structures: Vertex, Edge, Graph, and GraphTopology.

Vertex

A vertex represents a single operation in the DAG. It maps to a Python handler function with declared inputs, outputs, and metadata.

from honey_badgeria.back.graph import Vertex

v = Vertex(
    name="fetch_user",
    handler="vertices.users.fetch",    # dotted import path or callable
    inputs={"user_id": "int"},         # type declarations or data bindings
    outputs={"user": "dict"},          # output type declarations
    version="1",                       # version tracking
    effect="pure",                     # "pure" or "side_effect"
)

Fields

Field Type Description
name str Unique identifier for the vertex (e.g., "fetch_user")
handler Callable \| str Python function or dotted import path
inputs dict[str, str] Input declarations: type names or data bindings
outputs dict[str, str] Output type declarations
version str \| None Version string for rewrite-over-patch tracking
effect str "pure" (cacheable, no side effects) or "side_effect"

Effect Types

  • pure — The handler always returns the same output for the same input. Pure vertices can be cached, retried, and run in parallel safely.
  • side_effect — The handler has observable effects (database writes, API calls, file I/O). Side-effect vertices skip caching and require special handling in atomic groups.

Identity

The id property is an alias for name, provided for backward compatibility:

assert v.id == v.name  # True

Edge

An edge represents a directed connection between two vertices:

from honey_badgeria.back.graph import Edge

e = Edge(source="fetch_user", target="validate_user")

Edges are compared by their (source, target) pair:

e1 = Edge("a", "b")
e2 = Edge("a", "b")
assert e1 == e2  # True

Graph

The graph is the container for vertices, edges, flows, and atomic groups:

from honey_badgeria.back.graph import Graph, Vertex, Edge

graph = Graph()
graph.add_vertex(Vertex(name="a", handler=fn_a))
graph.add_vertex(Vertex(name="b", handler=fn_b))
graph.add_edge(Edge("a", "b"))

Key Methods

Method Returns Description
add_vertex(v) Add a vertex (raises VertexAlreadyExistsError on duplicate)
get_vertex(name) Vertex Retrieve by name (raises VertexNotFoundError)
has_vertex(name) bool Check if vertex exists
add_edge(e) Add an edge
get_sources() list[Vertex] Vertices with no incoming edges (entry points)
get_sinks() list[Vertex] Vertices with no outgoing edges (terminal nodes)
entry_vertex(flow_name) Vertex Get the entry point of a named flow
adjacency_list() dict[str, list[str]] Forward adjacency mapping
reverse_adjacency_list() dict[str, list[str]] Reverse adjacency mapping
atomic_group_for_vertex(name) AtomicGroup \| None Get the atomic group a vertex belongs to

Flows

Flows are named entry points into the graph:

graph.flows = {
    "create_user": {"entry": "normalize"},
    "delete_user": {"entry": "check_perms"},
}

entry = graph.entry_vertex("create_user")
# Returns the Vertex named "normalize"

Atomic Groups

Groups are stored on the graph and can be queried per-vertex:

group = graph.atomic_group_for_vertex("charge")
if group:
    print(f"Vertex 'charge' belongs to atomic group '{group.name}'")

GraphTopology

GraphTopology computes the execution structure of a graph. It is the algorithm layer that determines ordering and parallelism.

from honey_badgeria.back.topology import GraphTopology

topo = GraphTopology(graph)

Topological Sort

Returns vertices in dependency order using Kahn's algorithm:

sorted_vertices = topo.topological_sort()
# [Vertex("a"), Vertex("b"), Vertex("c"), Vertex("d")]

If the graph has cycles, this raises GraphCycleError.

Execution Stages

The most important computation. Returns groups of vertices where all vertices in a stage can execute in parallel:

stages = topo.execution_stages()
# [[Vertex("a")], [Vertex("b"), Vertex("c")], [Vertex("d")]]

For a diamond DAG A → (B, C) → D:

  • Stage 1: [A] — must execute first
  • Stage 2: [B, C] — can execute in parallel (no mutual dependencies)
  • Stage 3: [D] — depends on both B and C

This is how HBIA determines what can run concurrently — you don't write parallel code, you declare dependencies, and the topology computation reveals parallelism.

Sources and Sinks

sources = topo.sources()    # Vertices with no incoming edges
sinks = topo.sinks()        # Vertices with no outgoing edges

Dependency Queries

deps = topo.dependencies("validate")     # What "validate" depends on
dependents = topo.dependents("fetch")    # What depends on "fetch"

AtomicGroup

Declares a set of vertices that must execute as an all-or-nothing unit:

from honey_badgeria.back.atomicity import AtomicGroup, FailurePolicy

group = AtomicGroup(
    name="payment_processing",
    vertices=["reserve", "charge", "confirm"],
    failure_policy=FailurePolicy.ROLLBACK,
    no_cache=True,       # bypass cache inside group
    no_parallel=True,    # force serial execution
)
Field Type Default Description
name str Group identifier
vertices list[str] Vertex names in this group
failure_policy FailurePolicy ROLLBACK, COMPENSATE, or ABORT
no_cache bool True Bypass cache for vertices in this group
no_parallel bool True Force serial execution within the group

For full atomicity documentation, see Atomicity.

Building Graphs Programmatically

From Dictionaries

from honey_badgeria.back.loader import GraphBuilder

graph = GraphBuilder.from_dict({
    "flow": {
        "my_flow": {
            "vertex_a": {
                "handler": "mod.fn_a",
                "effect": "pure",
                "outputs": {"x": "int"},
                "next": ["vertex_b"],
            },
            "vertex_b": {
                "handler": "mod.fn_b",
                "inputs": {"x": "vertex_a.x"},
            },
        }
    }
})

From YAML

from honey_badgeria.back.dsl import load_graph_from_yaml

graph = load_graph_from_yaml("flows/my_flow.yaml")

Merging Graphs

Combine multiple graphs into one:

from honey_badgeria.utils.graph_tools import merge_graphs

combined = merge_graphs(graph1, graph2, graph3)

Extracting Subgraphs

from honey_badgeria.utils.graph_tools import subgraph

sub = subgraph(graph, ["vertex_a", "vertex_b"])

Validation

Graphs can be validated for structural integrity:

from honey_badgeria.back.graph.validator import validate_graph

errors = validate_graph(graph)
# Returns list of error strings, or empty list if valid

Checks performed:

  • Cycle detection (DAGs cannot have cycles)
  • Edge endpoint existence (both source and target must exist)
  • Atomic group connectivity (members must form a connected subgraph)

See Validation for full details.