Tutorial 7: Diff & Patch
Learn how to compare STL documents and apply patches.
What you’ll learn:
- Compare documents with
stl_diff() - Read diff entries and summaries
- Render human-readable diffs with
diff_to_text() - Export diffs as JSON with
diff_to_dict() - Apply patches with
stl_patch()
Prerequisites: Tutorial 6: Streaming I/O
Step 1: Compute a Diff
from stl_parser import parse, stl_diff
before = parse("""
[Rain] -> [Flooding] ::mod(rule="causal", confidence=0.85, strength=0.8)
[Wind] -> [Erosion] ::mod(rule="causal", confidence=0.70)
[Sun] -> [Evaporation] ::mod(rule="causal", confidence=0.90)
""")
after = parse("""
[Rain] -> [Flooding] ::mod(rule="causal", confidence=0.90, strength=0.85)
[Sun] -> [Evaporation] ::mod(rule="causal", confidence=0.90)
[Heat] -> [Drought] ::mod(rule="causal", confidence=0.75)
""")
diff = stl_diff(before, after)
Step 2: Read the Summary
print(f"Added: {diff.summary.added}") # 1 (Heat->Drought)
print(f"Removed: {diff.summary.removed}") # 1 (Wind->Erosion)
print(f"Modified: {diff.summary.modified}") # 1 (Rain->Flooding)
print(f"Unchanged: {diff.summary.unchanged}") # 1 (Sun->Evaporation)
print(f"Is empty: {diff.is_empty}") # False
Step 3: Inspect Diff Entries
Each DiffEntry describes one change:
for entry in diff.entries:
print(f"[{entry.op.value}] {entry.key}")
if entry.modifier_changes:
for change in entry.modifier_changes:
print(f" {change.field}: {change.old_value} -> {change.new_value}")
Output:
[modify] [Rain] -> [Flooding]
confidence: 0.85 -> 0.9
strength: 0.8 -> 0.85
[remove] [Wind] -> [Erosion]
[add] [Heat] -> [Drought]
Filter by Operation Type
from stl_parser.diff import DiffOp
added = [e for e in diff.entries if e.op == DiffOp.ADD]
removed = [e for e in diff.entries if e.op == DiffOp.REMOVE]
modified = [e for e in diff.entries if e.op == DiffOp.MODIFY]
# Or use convenience properties
added = diff.added
removed = diff.removed
modified = diff.modified
Step 4: Render as Text
diff_to_text() produces a human-readable format with +/-/~ markers:
from stl_parser import stl_diff
from stl_parser.diff import diff_to_text
text = diff_to_text(diff)
print(text)
Output:
~ [Rain] -> [Flooding]
confidence: 0.85 -> 0.9
strength: 0.8 -> 0.85
- [Wind] -> [Erosion] ::mod(confidence=0.7, rule="causal")
+ [Heat] -> [Drought] ::mod(confidence=0.75, rule="causal")
1 added, 1 removed, 1 modified, 1 unchanged
Step 5: Export as JSON
from stl_parser.diff import diff_to_dict
import json
d = diff_to_dict(diff)
print(json.dumps(d, indent=2))
This produces a JSON-compatible dict with "entries" and "summary" keys — useful for storing diffs or passing them between systems.
Step 6: Apply a Patch
stl_patch() transforms a document by applying a diff:
from stl_parser import parse, stl_diff, stl_patch
before = parse("""
[Rain] -> [Flooding] ::mod(rule="causal", confidence=0.85)
[Wind] -> [Erosion] ::mod(rule="causal", confidence=0.70)
""")
after = parse("""
[Rain] -> [Flooding] ::mod(rule="causal", confidence=0.90)
[Heat] -> [Drought] ::mod(rule="causal", confidence=0.75)
""")
# Compute diff
diff = stl_diff(before, after)
# Apply patch to transform 'before' into 'after'
patched = stl_patch(before, diff)
print(f"Patched statements: {len(patched.statements)}")
for s in patched.statements:
print(f" {s}")
Step 7: Ignore Order
By default, stl_diff() ignores statement order (ignore_order=True). Reordering statements does not generate diff entries:
a = parse("[X] -> [Y]\n[A] -> [B]")
b = parse("[A] -> [B]\n[X] -> [Y]")
diff = stl_diff(a, b)
print(diff.is_empty) # True — same content, different order
To treat order as significant:
diff = stl_diff(a, b, ignore_order=False)
Step 8: CLI Usage
# Text diff (default)
stl diff before.stl after.stl
# JSON diff (for machine consumption)
stl diff before.stl after.stl --format json
# Summary only
stl diff before.stl after.stl --summary
# Quiet mode (exit code only: 0=identical, 1=different)
stl diff before.stl after.stl --quiet
# Apply a JSON patch
stl diff before.stl after.stl --format json > changes.json
stl patch before.stl changes.json --output patched.stl
Complete Example
from stl_parser import parse, stl_diff, stl_patch
from stl_parser.diff import diff_to_text, diff_to_dict
import json
# Version 1 of a knowledge base
v1 = parse("""
[Smoking] -> [Lung_Cancer] ::mod(rule="causal", confidence=0.88, strength=0.80)
[Exercise] -> [Heart_Health] ::mod(rule="causal", confidence=0.85, strength=0.75)
[Sugar] -> [Diabetes] ::mod(rule="causal", confidence=0.75, strength=0.60)
""")
# Version 2 with updates
v2 = parse("""
[Smoking] -> [Lung_Cancer] ::mod(rule="causal", confidence=0.92, strength=0.85)
[Exercise] -> [Heart_Health] ::mod(rule="causal", confidence=0.85, strength=0.75)
[Pollution] -> [Respiratory_Disease] ::mod(rule="causal", confidence=0.80, strength=0.70)
""")
# Compute diff
diff = stl_diff(v1, v2)
# Display
print("=== Knowledge Base Changes ===")
print(diff_to_text(diff))
# Export for version control
with open("v1_to_v2.json", "w") as f:
json.dump(diff_to_dict(diff), f, indent=2)
# Apply patch to reproduce v2
patched = stl_patch(v1, diff)
print(f"\nPatched document has {len(patched.statements)} statements")
Next: Tutorial 8: CLI Tools