{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# WeakNode and Nested WeakNode (Interactive)\n", "\n", "This notebook shows how to build `Document -> Section -> Page` hierarchies with `WeakNode`,\n", "how to inspect the graph state after each operation, and how to delete nodes with different strategies.\n", "\n", "It also includes an interactive section where you choose which node to add or delete." ], "id": "ba16503ff1ce8c1b" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib.util\n", "\n", "# Comprovació de presència del paquet\n", "package_to_check = 'drm'\n", "spec = importlib.util.find_spec(package_to_check)\n", "\n", "if spec is None:\n", " print(f'⚠️ {package_to_check} no està instal·lat. Iniciant instal·lació...')\n", " %pip install -q --upgrade drm-tools\n", " print(\"✅ Instal·lació completada. L'estat del kernel PODRIA requerir un reinici.\")\n", "else:\n", " print(f'✅ {package_to_check} ja està present al sistema. Saltant instal·lació.')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview\n", "\n", "The notebook is organised as a step-by-step walkthrough followed by a small interactive control panel." ], "id": "overview-section" }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": [ "import os\n", "import tempfile\n", "import uuid\n", "\n", "try:\n", " from drm import NetworkXGraph, Node, WeakNode\n", "except ImportError:\n", " # Fallback for installs where WeakNode is not re-exported from drm.__init__\n", " from drm import NetworkXGraph\n", " from drm.base import Node, WeakNode\n", "\n", "persistence_path = os.path.join(tempfile.gettempdir(), f\"drm_tutorial_weaknodes_{uuid.uuid4().hex}.pkl\")\n", "graph = NetworkXGraph(persistence_path=persistence_path)\n", "registry = {}\n", "\n", "def key_for_document(doc_id):\n", " return f\"doc:{doc_id}\"\n", "\n", "def key_for_section(doc_id, section_id):\n", " return f\"section:{doc_id}:{section_id}\"\n", "\n", "def key_for_page(doc_id, section_id, page_id):\n", " return f\"page:{doc_id}:{section_id}:{page_id}\"\n", "\n", "def show_state(title=\"State\"):\n", " print(f\"\\n=== {title} ===\")\n", " print(\"nodes:\", graph.get_nodes())\n", " print(\"edges:\", graph.get_edges())\n", " debug = graph.debug()\n", " print(\"debug nodes:\", debug.get(\"nodes\", []))\n", " print(\"debug edges:\", debug.get(\"edges\", []))\n", "\n", "def add_document(doc_id):\n", " k = key_for_document(doc_id)\n", " node = Node(pk={\"doc\": doc_id}, main_label=\"Document\")\n", " graph.insertNode(node, replace=True)\n", " registry[k] = node\n", " return k\n", "\n", "def add_section(doc_id, section_id):\n", " parent_key = key_for_document(doc_id)\n", " if parent_key not in registry:\n", " add_document(doc_id)\n", " parent = registry[parent_key]\n", " k = key_for_section(doc_id, section_id)\n", " node = WeakNode(parent=parent, pk={\"section\": section_id}, main_label=\"Section\")\n", " graph.insertNode(node, insert_parent=True, replace=True)\n", " registry[k] = node\n", " return k\n", "\n", "def add_page(doc_id, section_id, page_id):\n", " section_key = key_for_section(doc_id, section_id)\n", " if section_key not in registry:\n", " add_section(doc_id, section_id)\n", " parent = registry[section_key]\n", " k = key_for_page(doc_id, section_id, page_id)\n", " node = WeakNode(parent=parent, pk={\"page\": page_id}, main_label=\"Page\")\n", " graph.insertNode(node, insert_parent=True, replace=True)\n", " registry[k] = node\n", " return k\n", "\n", "def delete_key(k, detach=True, propagation=False, on_delete=\"cascade\"):\n", " node = registry.get(k)\n", " if node is None:\n", " raise KeyError(f\"Unknown key: {k}\")\n", " graph.deleteNode(node, detach=detach, propagation=propagation, on_delete=on_delete)\n", " registry.pop(k, None)\n", "\n", "print(\"Graph initialized at:\", persistence_path)" ], "id": "f48f61af2a3a6ada" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Flux bàsic (pas a pas)" ], "id": "ecf9d615ebb1b1e1" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_document(\"DOC-001\")\n", "show_state(\"After adding Document\")\n", "\n", "add_section(\"DOC-001\", 1)\n", "show_state(\"After adding Section as WeakNode\")\n", "\n", "add_page(\"DOC-001\", 1, 1)\n", "show_state(\"After adding Page as WeakNode of WeakNode\")" ], "id": "c8140de2baf86efa" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "delete_key(\"section:DOC-001:1\", detach=True, propagation=False, on_delete=\"cascade\")\n", "show_state(\"After deleting Section with ON DELETE CASCADE\")" ], "id": "319389e6ced9d224" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Secció interactiva (tria operació i node)\n", "\n", "Si tens `ipywidgets` disponible, pots afegir/esborrar nodes manualment." ], "id": "2fd2bbe01d8ad5c8" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import display, clear_output\n", "import ipywidgets as widgets\n", "\n", "doc_w = widgets.Text(value=\"DOC-002\", description=\"doc\")\n", "section_w = widgets.IntText(value=1, description=\"section\")\n", "page_w = widgets.IntText(value=1, description=\"page\")\n", "delete_key_w = widgets.Dropdown(options=[], description=\"delete\")\n", "detach_w = widgets.Checkbox(value=True, description=\"detach\")\n", "prop_w = widgets.Checkbox(value=False, description=\"propagation\")\n", "on_delete_w = widgets.Dropdown(options=[\"cascade\", \"restrict\", \"set_null\"], value=\"cascade\", description=\"on_delete\")\n", "out = widgets.Output()\n", "\n", "def refresh_keys(*_):\n", " opts = sorted(registry.keys())\n", " delete_key_w.options = opts if opts else [\"\"]\n", "\n", "def act_add_doc(_):\n", " with out:\n", " clear_output()\n", " k = add_document(doc_w.value)\n", " print(\"added\", k)\n", " show_state(\"Interactive add document\")\n", " refresh_keys()\n", "\n", "def act_add_section(_):\n", " with out:\n", " clear_output()\n", " k = add_section(doc_w.value, section_w.value)\n", " print(\"added\", k)\n", " show_state(\"Interactive add section\")\n", " refresh_keys()\n", "\n", "def act_add_page(_):\n", " with out:\n", " clear_output()\n", " k = add_page(doc_w.value, section_w.value, page_w.value)\n", " print(\"added\", k)\n", " show_state(\"Interactive add page\")\n", " refresh_keys()\n", "\n", "def act_delete(_):\n", " with out:\n", " clear_output()\n", " k = delete_key_w.value\n", " if not k:\n", " print(\"No node selected\")\n", " return\n", " try:\n", " delete_key(k, detach=detach_w.value, propagation=prop_w.value, on_delete=on_delete_w.value)\n", " print(\"deleted\", k)\n", " show_state(\"Interactive delete\")\n", " except Exception as err:\n", " print(\"delete error:\", err)\n", " refresh_keys()\n", "\n", "btn_doc = widgets.Button(description=\"Add Document\")\n", "btn_sec = widgets.Button(description=\"Add Section\")\n", "btn_page = widgets.Button(description=\"Add Page\")\n", "btn_del = widgets.Button(description=\"Delete Selected\", button_style=\"danger\")\n", "\n", "btn_doc.on_click(act_add_doc)\n", "btn_sec.on_click(act_add_section)\n", "btn_page.on_click(act_add_page)\n", "btn_del.on_click(act_delete)\n", "\n", "refresh_keys()\n", "display(widgets.VBox([\n", " widgets.HBox([doc_w, section_w, page_w]),\n", " widgets.HBox([btn_doc, btn_sec, btn_page]),\n", " widgets.HBox([delete_key_w, detach_w, prop_w, on_delete_w, btn_del]),\n", " out,\n", "]))" ], "id": "52268b29d149e2fd" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cleanup" ], "id": "6d24555a7f7afa20" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "graph.close()\n", "try:\n", " os.remove(persistence_path)\n", "except FileNotFoundError:\n", " pass\n", "print(\"Closed graph and removed temp persistence file\")" ], "id": "d83cafd8706e0b47" } ], "metadata": { "kernelspec": { "display_name": "Python (drm-tool)", "language": "python", "name": "drm-tool" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }