spinagram
Reference

Edit API

Patch a diagram's YAML from Python with the same operations the canvas editor uses.

spinagram.edit applies edit operations to a diagram's YAML text, from Python. These are the same operations the canvas editor sends from the browser, run by the same Rust code. Use them in scripts, code generators and AI agents that change a diagram without rewriting the file.

  • Minimal patches: bytes outside the edited ranges are unchanged, so comments, blank lines, key order and one-line or block style survive.
  • All or nothing: a batch applies completely or not at all, and an invalid operation raises EditError naming the operation and the reason.
  • Checked output: the result is re-parsed after every operation, so a batch never produces YAML that does not load.

The operations need the Rust core (spinagram._core), which the wheels include. They have no pure-Python version, so SPINAGRAM_RUST=0 does not turn them off.

Example

from spinagram import edit

text = """\
id: DEMO
title: Demo
nodes:
  # the web tier
  - {id: web, title: Web, at: [100, 200, 220, 96]}
  - {id: api, title: API, at: [400, 200, 220, 96]}
edges:
  - web -> api
"""

text = edit.apply_ops(text, [
    {"op": "move", "id": "api", "x": 460, "y": 200},
    {"op": "add_node", "id": "db", "title": "Orders DB", "icon": "aws:rds", "x": 760, "y": 200, "w": 220, "h": 96},
    {"op": "connect", "from": "api", "to": "db", "label": "SQL"},
    {"op": "set_props", "id": "e0", "props": {"label": "HTTPS"}},
])

The comment and the untouched entry stay as they were:

id: DEMO
title: Demo
nodes:
  # the web tier
  - {id: web, title: Web, at: [100, 200, 220, 96]}
  - {id: api, title: API, at: [460, 200, 220, 96]}
  - {id: db, title: Orders DB, icon: aws:rds, x: 760, y: 200, w: 220, h: 96}
edges:
  - "web -> api: HTTPS"
  - "api -> db: SQL"

Functions

FunctionReturns
apply_ops(yaml_text, ops)The patched text. Raises EditError and changes nothing when an operation is invalid
srcmap(yaml_text){id: (start, end)}: the UTF-8 byte range of each node, zone and edge entry. Edges are e0, e1, …
EditErrorA ValueError whose message names the failing operation, such as operation 0: reparent: unknown id `nowhere`

Operations

Each operation is a dict whose "op" key names it.

Nodes and edges

OperationFieldsNotes
moveid, x, yWrites the entry's at list or x/y keys, whichever it uses
resizeid, w, hSame rule as move
renameid, titleA zone's label for a zone
deleteidRemoves a node with the edges that touch it and its id in zones, rows and columns. Also takes an edge id e<i>
add_nodeid, title; optional kind, icon (pack:name), x, y, w, hAppended to nodes in the list's style
connectfrom, to; optional labelAppended as "from -> to" (: label). Parallel edges are allowed
disconnectfrom, toRemoves the first matching edge
set_propsid, propsSets, replaces or removes (null) keys of a node, zone or edge. Only keys the YAML loader accepts are allowed. A string edge stays a string when only label changes, and becomes a {from, to, ...} mapping when another key is set
duplicateid, dx, dyCopies a node or zone as id-2 (id-3, …), offset and snapped. A node copy joins the zones, rows and columns of the original
reconnectid (e<i>); optional from, toChanges an edge's ends. An unchanged end keeps its side suffix, such as a.right@0.3
reverseid (e<i>)Swaps the ends. A mapping's via runs the other way

Zones, rows and columns

A container's id is row0, row1, … then column0, … counting rows: and then columns: entries in file order. Several operations take boxes, which maps a member id to its rendered [x, y, w, h]. That box is written to the node when it leaves a container.

OperationFieldsNotes
add_zoneid, label, x, y, w, h; optional stylestyle is a zone style of the theme, written as the zone's color:, such as aws-vpc
set_zone_styleid, styleSets or replaces the zone's color:. Nothing else changes
reparentid, parentMoves a node or zone into zone parent, or out to the top level with null. A zone cannot go inside itself or a descendant
move_zoneid, x, yMoves the zone, its child nodes and zones, and their containers by the same offset
groupaxis (row or column), ids; optional gap, x, yAdds a container with the members in that order
ungroupid, boxesRemoves the container and writes each member's box
add_to_containerid, node; optional indexInserts at index among the other members, or appends. A member of the same container is reordered
remove_from_containerid, node, boxesA container left with fewer than two members dissolves, as with ungroup
set_containerid; optional gap, alignalign only for a container placed in a zone

From the browser and other languages

The canvas calls the same operations through the spinagram-wasm package (applyOps, srcmap). They take and return JSON, so any language with a WebAssembly runtime can call them. See rust/README.md in the repository for the crate layout.

On this page