WeakNode and Nested WeakNode (Interactive)

This notebook shows how to build Document -> Section -> Page hierarchies with WeakNode, how to inspect the graph state after each operation, and how to delete nodes with different strategies.

It also includes an interactive section where you choose which node to add or delete.

[ ]:
import importlib.util

# Comprovació de presència del paquet
package_to_check = 'drm'
spec = importlib.util.find_spec(package_to_check)

if spec is None:
    print(f'⚠️ {package_to_check} no està instal·lat. Iniciant instal·lació...')
    %pip install -q --upgrade drm-tools
    print("✅ Instal·lació completada. L'estat del kernel PODRIA requerir un reinici.")
else:
    print(f'✅ {package_to_check} ja està present al sistema. Saltant instal·lació.')

Overview

The notebook is organised as a step-by-step walkthrough followed by a small interactive control panel.

[ ]:
import os
import tempfile
import uuid

try:
    from drm import NetworkXGraph, Node, WeakNode
except ImportError:
    # Fallback for installs where WeakNode is not re-exported from drm.__init__
    from drm import NetworkXGraph
    from drm.base import Node, WeakNode

persistence_path = os.path.join(tempfile.gettempdir(), f"drm_tutorial_weaknodes_{uuid.uuid4().hex}.pkl")
graph = NetworkXGraph(persistence_path=persistence_path)
registry = {}

def key_for_document(doc_id):
    return f"doc:{doc_id}"

def key_for_section(doc_id, section_id):
    return f"section:{doc_id}:{section_id}"

def key_for_page(doc_id, section_id, page_id):
    return f"page:{doc_id}:{section_id}:{page_id}"

def show_state(title="State"):
    print(f"\n=== {title} ===")
    print("nodes:", graph.get_nodes())
    print("edges:", graph.get_edges())
    debug = graph.debug()
    print("debug nodes:", debug.get("nodes", []))
    print("debug edges:", debug.get("edges", []))

def add_document(doc_id):
    k = key_for_document(doc_id)
    node = Node(pk={"doc": doc_id}, main_label="Document")
    graph.insertNode(node, replace=True)
    registry[k] = node
    return k

def add_section(doc_id, section_id):
    parent_key = key_for_document(doc_id)
    if parent_key not in registry:
        add_document(doc_id)
    parent = registry[parent_key]
    k = key_for_section(doc_id, section_id)
    node = WeakNode(parent=parent, pk={"section": section_id}, main_label="Section")
    graph.insertNode(node, insert_parent=True, replace=True)
    registry[k] = node
    return k

def add_page(doc_id, section_id, page_id):
    section_key = key_for_section(doc_id, section_id)
    if section_key not in registry:
        add_section(doc_id, section_id)
    parent = registry[section_key]
    k = key_for_page(doc_id, section_id, page_id)
    node = WeakNode(parent=parent, pk={"page": page_id}, main_label="Page")
    graph.insertNode(node, insert_parent=True, replace=True)
    registry[k] = node
    return k

def delete_key(k, detach=True, propagation=False, on_delete="cascade"):
    node = registry.get(k)
    if node is None:
        raise KeyError(f"Unknown key: {k}")
    graph.deleteNode(node, detach=detach, propagation=propagation, on_delete=on_delete)
    registry.pop(k, None)

print("Graph initialized at:", persistence_path)

Flux bàsic (pas a pas)

[ ]:
add_document("DOC-001")
show_state("After adding Document")

add_section("DOC-001", 1)
show_state("After adding Section as WeakNode")

add_page("DOC-001", 1, 1)
show_state("After adding Page as WeakNode of WeakNode")
[ ]:
delete_key("section:DOC-001:1", detach=True, propagation=False, on_delete="cascade")
show_state("After deleting Section with ON DELETE CASCADE")

Secció interactiva (tria operació i node)

Si tens ipywidgets disponible, pots afegir/esborrar nodes manualment.

[ ]:
from IPython.display import display, clear_output
import ipywidgets as widgets

doc_w = widgets.Text(value="DOC-002", description="doc")
section_w = widgets.IntText(value=1, description="section")
page_w = widgets.IntText(value=1, description="page")
delete_key_w = widgets.Dropdown(options=[], description="delete")
detach_w = widgets.Checkbox(value=True, description="detach")
prop_w = widgets.Checkbox(value=False, description="propagation")
on_delete_w = widgets.Dropdown(options=["cascade", "restrict", "set_null"], value="cascade", description="on_delete")
out = widgets.Output()

def refresh_keys(*_):
    opts = sorted(registry.keys())
    delete_key_w.options = opts if opts else [""]

def act_add_doc(_):
    with out:
        clear_output()
        k = add_document(doc_w.value)
        print("added", k)
        show_state("Interactive add document")
    refresh_keys()

def act_add_section(_):
    with out:
        clear_output()
        k = add_section(doc_w.value, section_w.value)
        print("added", k)
        show_state("Interactive add section")
    refresh_keys()

def act_add_page(_):
    with out:
        clear_output()
        k = add_page(doc_w.value, section_w.value, page_w.value)
        print("added", k)
        show_state("Interactive add page")
    refresh_keys()

def act_delete(_):
    with out:
        clear_output()
        k = delete_key_w.value
        if not k:
            print("No node selected")
            return
        try:
            delete_key(k, detach=detach_w.value, propagation=prop_w.value, on_delete=on_delete_w.value)
            print("deleted", k)
            show_state("Interactive delete")
        except Exception as err:
            print("delete error:", err)
    refresh_keys()

btn_doc = widgets.Button(description="Add Document")
btn_sec = widgets.Button(description="Add Section")
btn_page = widgets.Button(description="Add Page")
btn_del = widgets.Button(description="Delete Selected", button_style="danger")

btn_doc.on_click(act_add_doc)
btn_sec.on_click(act_add_section)
btn_page.on_click(act_add_page)
btn_del.on_click(act_delete)

refresh_keys()
display(widgets.VBox([
    widgets.HBox([doc_w, section_w, page_w]),
    widgets.HBox([btn_doc, btn_sec, btn_page]),
    widgets.HBox([delete_key_w, detach_w, prop_w, on_delete_w, btn_del]),
    out,
]))

Cleanup

[ ]:
graph.close()
try:
    os.remove(persistence_path)
except FileNotFoundError:
    pass
print("Closed graph and removed temp persistence file")