anywidget icon indicating copy to clipboard operation
anywidget copied to clipboard

VS Code: Failed to load model class 'AnyModel' from module 'anywidget'

Open basnijholt opened this issue 1 year ago • 3 comments

Describe the bug

First of all, thanks for this awesome package! It has made developing a widget for my package (https://github.com/pipefunc/pipefunc/pull/323) much easier!

My widget works in a Jupyter notebook but in VS Code I get:

[Open Browser Console for more detailed log - Double click to close this message]
Failed to load model class 'AnyModel' from module 'anywidget'
Error: No version of module anywidget is registered
    at ph.loadClass (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4099813)
    at ph.loadClass (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4403287)
    at ph.loadModelClass (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4097773)
    at ph._make_model (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4094616)
    at ph.new_model (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4092246)
    at ph.handle_comm_open (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4091039)
    at https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-renderers-1.0.19/out/node_modules/%40vscode/jupyter-ipywidgets8/dist/ipywidgets.js:2:4402511
    at n._handleCommOpen (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-2024.8.1-darwin-arm64/dist/webviews/webview-side/ipywidgetsKernel/ipywidgetsKernel.js:3:80955)
    at async n._handleMessage (https://file+.vscode-resource.vscode-cdn.net/Users/basnijholt/.vscode/extensions/ms-toolsai.jupyter-2024.8.1-darwin-arm64/dist/webviews/webview-side/ipywidgetsKernel/ipywidgetsKernel.js:3:82830)

I found some related issue here:

  • https://github.com/vega/altair/issues/3409#issuecomment-2075544491
  • https://github.com/manzt/anywidget/issues/534

Reproduction

I have this widget

from pathlib import Path
from typing import Any

import anywidget
import ipywidgets as widgets
import traitlets


class PipeFuncGraphWidget(anywidget.AnyWidget):
    """A widget for rendering a graphviz graph using d3-graphviz.

    Example:
    -------
    >>> dot_string = "digraph { a -> b; b -> c; c -> a; }"
    >>> pipe_func_graph_widget = PipeFuncGraphWidget(dot_source=dot_string)
    >>> pipe_func_graph_widget

    """

    _esm = Path(__file__).parent / "static" / "graphviz_widget.js"

    _css = """
    #graph {
        margin: auto;
    }
    """

    dot_source = traitlets.Unicode("").tag(sync=True)
    selected_direction = traitlets.Unicode("bidirectional").tag(sync=True)


def graph_widget(dot_string: str = "digraph { a -> b; b -> c; c -> a; }") -> widgets.VBox:
    pipe_func_graph_widget = PipeFuncGraphWidget(dot_source=dot_string)
    reset_button = widgets.Button(description="Reset Zoom")
    direction_selector = widgets.Dropdown(
        options=["bidirectional", "downstream", "upstream", "single"],
        value="bidirectional",
        description="Direction:",
    )

    # Define button actions
    def reset_graph(_: Any) -> None:
        pipe_func_graph_widget.send({"action": "reset_zoom"})

    def update_direction(change: dict) -> None:
        pipe_func_graph_widget.selected_direction = change["new"]

    reset_button.on_click(reset_graph)
    direction_selector.observe(update_direction, names="value")

    # Display widgets
    return widgets.VBox(
        [
            widgets.HBox([reset_button, direction_selector]),
            pipe_func_graph_widget,
        ],
    )

with graphviz_widget.js:

function loadScript(url) {
    return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        script.src = url;
        script.onload = () => resolve();
        script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
        document.head.append(script);
    });
}

async function render({ model, el }) {
    // Load scripts in order
    await loadScript("https://unpkg.com/[email protected]/dist/jquery.min.js");
    await loadScript("https://unpkg.com/[email protected]/jquery.mousewheel.js");
    await loadScript("https://unpkg.com/[email protected]/dist/jquery.color.js");
    await loadScript("https://unpkg.com/[email protected]/dist/d3.min.js");
    await loadScript("https://cdn.jsdelivr.net/gh/mountainstorm/jquery.graphviz.svg@master/js/jquery.graphviz.svg.js");
    await loadScript("https://unpkg.com/@hpcc-js/[email protected]/dist/index.min.js");
    await loadScript("https://unpkg.com/[email protected]/build/d3-graphviz.min.js");

    const $ = window.jQuery;
    const d3 = window.d3;

    // Prepare the graph container
    el.innerHTML = '<div id="graph" style="text-align: center;"></div>';
    const graphEl = $(el).find('#graph');

    // Initialize a d3-graphviz renderer instance
    var graphviz = d3.select("#graph").graphviz();

    // Configuration for transitions in rendering the graph
    var d3Config = {
        transitionDelay: 0,
        transitionDuration: 500
    };

    // Variable for storing the selected graph rendering engine
    var selectedEngine = "dot";

    // Object for saving the current GraphVizSVG
    var graphVizObject;

    // Variable for storing the selected direction for highlighting
    var selectedDirection = model.get("selected_direction") || "bidirectional";

    // Array holding the current selections
    var currentSelection = [];

    // Function to highlight selected nodes and their connected nodes
    function highlightSelection() {
        let highlightedNodes = $();
        currentSelection.forEach(selection => {
            const nodes = getConnectedNodes(selection.set, selection.direction);
            highlightedNodes = highlightedNodes.add(nodes);
        });
        graphVizObject.highlight(highlightedNodes, true);
    }

    // Function to retrieve nodes connected in the specified direction
    function getConnectedNodes(nodeSet, mode = "bidirectional") {
        let resultSet = $().add(nodeSet);
        const nodes = graphVizObject.nodesByName();

        nodeSet.each((i, el) => {
            if (el.className.baseVal === "edge") {
                const [startNode, endNode] = $(el).data("name").split("->");
                if ((mode === "bidirectional" || mode === "upstream") && startNode) {
                    resultSet = resultSet.add(nodes[startNode]).add(graphVizObject.linkedTo(nodes[startNode], true));
                }
                if ((mode === "bidirectional" || mode === "downstream") && endNode) {
                    resultSet = resultSet.add(nodes[endNode]).add(graphVizObject.linkedFrom(nodes[endNode], true));
                }
            } else {
                if (mode === "bidirectional" || mode === "upstream") {
                    resultSet = resultSet.add(graphVizObject.linkedTo(el, true));
                }
                if (mode === "bidirectional" || mode === "downstream") {
                    resultSet = resultSet.add(graphVizObject.linkedFrom(el, true));
                }
            }
        });
        return resultSet;
    }

    // Function to reset the graph zoom and selection highlights
    function resetGraph() {
        graphviz.resetZoom();
        graphVizObject.highlight(); // Reset node selection on reset
        currentSelection = [];
    }

    // Function to update the selected direction for highlighting
    function updateDirection(newDirection) {
        selectedDirection = newDirection;
        resetGraph();
    }

    // Main function to render the graph from DOT source
    function render(dotSource) {
        var transition = d3.transition("graphTransition")
            .ease(d3.easeLinear)
            .delay(d3Config.transitionDelay)
            .duration(d3Config.transitionDuration);

        graphviz
            .engine(selectedEngine)
            .fade(true)
            .transition(transition)
            .tweenPaths(true)
            .tweenShapes(true)
            .zoomScaleExtent([0, Infinity])
            .zoom(true)
            .renderDot(dotSource)
            .on("end", function () {
                // Calls the jquery.graphviz.svg setup directly
                $('#graph').data('graphviz.svg').setup();  // Re-setup after rendering
            });
    }

    // Document ready function
    $(document).ready(function () {
        // Initialize the GraphVizSVG object from jquery.graphviz.svg.js
        $("#graph").graphviz({
            shrink: null,
            zoom: false,
            ready: function () {
                graphVizObject = this;

                // Event listener for node clicks to handle selection
                graphVizObject.nodes().click(function (event) {
                    const nodeSet = $().add(this);
                    const selectionObject = {
                        set: nodeSet,
                        direction: selectedDirection
                    };

                    // If CMD, CTRL, or SHIFT is pressed, add to the selection
                    if (event.ctrlKey || event.metaKey || event.shiftKey) {
                        currentSelection.push(selectionObject);
                    } else {
                        currentSelection = [selectionObject];
                    }

                    highlightSelection();
                });
                // Event listener for pressing the escape key to cancel highlights
                $(document).keydown(function (event) {
                    if (event.keyCode === 27) {
                        graphVizObject.highlight();
                    }
                });
            }
        });
    });

    // Event listeners for `anywidget` events
    model.on("change:dot_source", () => {
        render(model.get("dot_source"));
    });

    model.on("change:selected_direction", () => {
        updateDirection(model.get("selected_direction"));
    });

    model.on("msg:custom", (msg) => {
        if (msg.action === "reset_zoom") {
            resetGraph();
        }
    });

    render(model.get("dot_source"));
}
export default { render };

The I run:

graph_widget()

In a Jupyter notebook in the browser this works, but in VS Code I see the error I posted above.

Logs

No response

System Info

at 16:44:43 ❯ pip freeze | grep -E "anywidget|jupyter|notebook"
anywidget @ file:///home/conda/feedstock_root/build_artifacts/anywidget_1719028522224/work
jupyter-cache==1.0.0
jupyter-events @ file:///home/conda/feedstock_root/build_artifacts/jupyter_events_1710805637316/work
jupyter-lsp @ file:///home/conda/feedstock_root/build_artifacts/jupyter-lsp-meta_1712707420468/work/jupyter-lsp
jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1716472197302/work
jupyter_core @ file:///Users/runner/miniforge3/conda-bld/jupyter_core_1710257589360/work
jupyter_server @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_1720816649297/work
jupyter_server_terminals @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_terminals_1710262634903/work
jupyterlab @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_1724745148804/work
jupyterlab_pygments @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_pygments_1707149102966/work
jupyterlab_server @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_server-split_1721163288448/work
jupyterlab_widgets @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_widgets_1724331334887/work
notebook @ file:///home/conda/feedstock_root/build_artifacts/notebook_1724861303656/work
notebook_shim @ file:///home/conda/feedstock_root/build_artifacts/notebook-shim_1707957777232/work


### Severity

blocking all usage of anywidget

basnijholt avatar Sep 14 '24 23:09 basnijholt