anywidget

website·Indexed 34 days ago

https://anywidget.dev/

A tool for creating custom, reusable interactive widgets for Jupyter and other computational notebooks using front-end JavaScript and stateful properties accessible by both Python and JavaScript. It includes the Anywidget Front-End Module (AFM) specification for portable widget code and supports native Hot Module Replacement (HMR).

Tokens
11.9K
Snippets
55
Records
71
Agent score
71%

What's inside anywidget

  1. Overview of anywidget

    anywidget is a specification and toolset for authoring reusable web-based widgets for interactive computing environments. It defines a standard for widget front-end code (Anywidget Front-End Module or AFM) and provides a Python library to simplify the creation and publishing of custom Jupyter Widgets. It supports environments including Jupyter, JupyterLab, Google Colab, VSCode, and marimo.
  2. Overview of anywidget

    anywidget is a Python library that simplifies the creation of custom Jupyter Widgets by acting as an abstraction layer over the standard Jupyter Widgets framework. It leverages ECMAScript Modules (ESM) to solve the multi-platform packaging problem, allowing developers to define ESM and an associated Python class without needing to create a full Python package or manage complex front-end build tooling (like Webpack) before prototyping. This enables custom widgets to be developed directly within notebooks and work across different notebook environments, including those that typically require npm publishing like Google Colab or VS Code.
  3. Explore anywidget community projects and implementations

    The anywidget ecosystem includes a wide variety of third-party widgets and integrations for Jupyter notebooks. These projects demonstrate the framework's ability to handle complex tasks such as WASM sideloading, bidirectional communication with JS libraries, and specialized scientific visualizations.
  4. Understand when to use a bundler with anywidget

    anywidget does not require bundling if you use web-standard ECMAScript modules (ESM) that browsers can interpret directly. However, a bundler is necessary if your project uses:

    • Local dependencies (npm packages)
    • Non-standard syntax (TypeScript, JSX)
    • Front-end frameworks (React, Vue, Solid, Svelte)

    To avoid a build step, it is recommended to import dependencies from CDNs like esm.sh or jsDelivr and use JSDoc comments for type-checking instead of TypeScript.

  5. Understand Jupyter widget communication fundamentals

    To display a JavaScript-backed widget for a Python object in Jupyter, three components are required:

    1. Communication: A Comm object (like comm.base_comm.BaseComm) to exchange JSON messages between the Python kernel and the browser.
    2. Python Representation: An object with a _repr_mimebundle_ method that returns the application/vnd.jupyter.widget-view+json MIME type, containing a model_id that matches the Comm object. The object must also support JSON serialization and an observer pattern to trigger messages on state change.
    3. JavaScript Representation: A JS object aware of the model_id that can update the view or send messages back to the Python kernel (typically using @jupyter-widgets/base).
    {
      "application/vnd.jupyter.widget-view+json": {
        "model_id": "some-uuid"
      }
    }
  6. Define anywidget lifecycle hooks using default exports

    In anywidget v0.9, the preferred way to define a widget's front-end behavior is using a default object export. This allows developers to hook into two distinct stages of the widget lifecycle:

    • initialize: Executed once per widget instance during model initialization. Use this to set up non-view event handlers or state shared across multiple views. It has access to the model.
    • render: Executed once per view during rendering. Use this to set up view-specific event handlers or access the DOM. It has access to both model and the el DOM element.

    Alternatively, the default export can be a function that returns this interface, which is useful for initializing front-end specific state for the widget's lifetime.

    // Basic usage
    export default {
      initialize({ model }) {
        /* setup shared state or global handlers */
      },
      render({ model, el }) {
        /* setup view-specific DOM elements */
      },
    };
    
    // Advanced usage: using a function to maintain state
    export default () => {
      let valueHistory = [];
      return {
        initialize({ model }) {
          model.on("change:value", () => valueHistory.push(model.get("value")));
        },
        render({ model, el }) {
          el.innerText = `The history is ${valueHistory}`;
          model.on("change:value", () => {
            el.innerText = `The history is ${valueHistory}`;
          });
        },
      };
    };
  7. Migrate named render exports to default exports in anywidget v0.9

    anywidget v0.9 deprecates named render exports. To migrate, move the render function into a default object export.
    // OLD (Deprecated)
    export function render({ model, el }) {
      /* ... */
    }
    
    // NEW (Preferred)
    function render({ model, el }) {
      /* ... */
    }
    
    export default { render };
  8. Synchronize Python and JavaScript state using Traitlets

    Traitlets are the primary way to sync data between Python and the front end. In Python, define a trait with sync=True. In JavaScript, use the model object to get, set, or listen for changes to that trait.

    Important: When calling model.set() in JavaScript, you must call model.save_changes() to push the update back to the Python kernel.

    import anywidget
    import traitlets
    
    class CustomWidget(anywidget.AnyWidget):
        _esm = "index.js"
        my_value = traitlets.Int(0).tag(sync=True)
    // index.js
    function render({ model, el }) {
      // 1. Get value
      let val = model.get("my_value");
    
      // 2. Set value
      model.set("my_value", 42);
      model.save_changes(); 
    
      // 3. Listen for changes
      model.on("change:my_value", () => {
        console.log(`Value changed to: ${model.get("my_value")}`);
      });
    }
    export default { render };
  9. Send custom messages from Python to JavaScript

    For communication that doesn't fit a state-sync model, use custom messages. In JavaScript, listen for the msg:custom event on the model. In Python, use the .send() method on the widget instance.

    Caveat: Custom messages are only received if the widget is already displayed in the notebook (the front-end code must have executed and registered the listener) before .send() is called.

    import anywidget
    
    class CustomMessageWidget(anywidget.AnyWidget):
        _esm = """
        function render({ model, el }) {
          model.on("msg:custom", msg => {
             console.log(`new message: ${JSON.stringify(msg)}`);
           });
        }
        export default { render };
        """
    
    widget = CustomMessageWidget()
    widget # Must display the widget first
    widget.send({ "type": "my-event", "foo": "bar" })
  10. Pass widget references from Python to JavaScript

    In Python, you can pass one widget directly into another. The serialization layer automatically detects objects that extend anywidget.AnyWidget or implement the anywidget protocol and converts them to a wire-format string ("anywidget:<model_id>").

    Use anywidget.WidgetTrait to validate that a trait is assigned an anywidget-compatible object. For type annotations in dataclasses or Pydantic models, use anywidget.Widget.

    import anywidget
    import traitlets
    
    class Slider(anywidget.AnyWidget):
        _esm = "slider.js"
        value = traitlets.Float(0.0).tag(sync=True)
    
    class Dashboard(anywidget.AnyWidget):
        _esm = "dashboard.js"
        # Use WidgetTrait to validate the reference
        control = anywidget.WidgetTrait().tag(sync=True)
    
    slider = Slider(value=50)
    # Pass the widget instance directly
    Dashboard(control=slider)
  11. Install anywidget

    Install the anywidget library using pip to start creating reusable interactive widgets.
    pip install anywidget