Skip to content

Latest commit

 

History

History
807 lines (529 loc) · 26.4 KB

File metadata and controls

807 lines (529 loc) · 26.4 KB

anywidget

0.11.0

Minor Changes

  • Allow initialize to return an exports object (#974)

    initialize can now return a plain object to expose a programmatic API for the widget. This API is accessible to parent widgets via host.getWidget().

    export default {
      initialize({ model }) {
        return {
          getValue: () => model.get("value"),
          setValue: (v) => {
            model.set("value", v);
            model.save_changes();
          },
        };
      },
      render({ model, el }) {
        /* ... */
      },
    };

    The return type is distinguished by typeof: functions are treated as cleanup callbacks (existing behavior), objects are treated as exports, and void means neither.

  • Add signal (AbortSignal) to initialize and render props for lifecycle cleanup (#974)

    Both initialize and render now receive an AbortSignal via the signal prop. The signal is aborted when the widget is destroyed (or during HMR). This is the preferred way to manage cleanup going forward — it composes with the broader web platform (addEventListener, fetch, child widgets) and avoids the need to manually track teardown logic.

    The previous callback-based pattern continues to work but is no longer recommended:

    // before
    export default {
      render({ model, el }) {
        let handler = () => { /* ... */ };
        model.on("change:value", handler);
        return () => model.off("change:value", handler);
      },
    };
    
    // after
    export default {
      render({ model, el, signal }) {
        let handler = () => { /* ... */ };
        model.on("change:value", handler);
        signal.addEventListener("abort", () => model.off("change:value", handler));
      },
    };

    signal also works with addEventListener and fetch directly:

    export default {
      render({ model, el, signal }) {
        el.addEventListener(
          "click",
          () => {
            /* ... */
          },
          { signal }
        );
      },
    };
  • Add host.getWidget and host.getModel for widget composition (#974)

    render now receives a host prop with methods to resolve child widgets by reference, enabling one anywidget to render another inside its DOM.

    export default {
      async render({ model, el, signal, host }) {
        let child = await host.getWidget(model.get("slider"));
        await child.render({ el, signal });
      },
    };

    On the Python side, widget references are serialized as "anywidget:<model_id>" strings. A new WidgetTrait traitlet validates anywidget-compatible objects, and a Widget type alias is provided for annotations:

    import anywidget
    
    class Dashboard(anywidget.AnyWidget):
        _esm = "dashboard.js"
        slider = anywidget.WidgetTrait().tag(sync=True)
    
    Dashboard(slider=Slider())

    host.getWidget(ref) returns { exports, render } where exports is the object returned from the child's initialize, and render({ el, signal }) mounts the child's view. host.getModel(ref) returns the raw model for lower-level access.

Patch Changes

0.10.0

Minor Changes

  • Drop Python 3.8 and 3.9 support, require Python >=3.10 (#949)

    Python 3.8 and 3.9 have reached end-of-life. Bumping the minimum to 3.10 aligns anywidget with the broader ecosystem and allows us to upgrade dependencies (like watchfiles) that have already dropped older Python support, which is needed for Python 3.14 compatibility.

0.9.22

Patch Changes

  • Updated dependencies [88298fd]:
    • @anywidget/types@0.3.0

0.9.21

Patch Changes

  • Use rsbuild inline sourcemaps for JupyterLab extension (#909)

    JupyterLab was refusing to load external source map files because they were being served with the wrong MIME type ('application/octet-stream' instead of the expected source map MIME type). This caused the extension to fail loading in version 0.9.20.

    Switching to inline source maps embeds the source map directly in the JavaScript bundle, avoiding the MIME type issue while still providing source map support for debugging.

0.9.20

Patch Changes

  • Override repr to avoid expensive trait serialization #906 (#906)

    Previously, AnyWidget inherited ipywidgets.Widget.__repr__ which serialized all trait values. This is costly because the repr might not even be shown to users, yet it forces a full serialization of potentially large data. AnyWidget now overrides __repr__ to use object.__repr__(self), which produces a simple <module.ClassName object at 0x...> format.

    To restore the previous behavior showing all trait values, users can define:

    class MyWidget(anywidget.AnyWidget):
        def __repr__(self):
            return traitlets.HasTraits.__repr__(self)

0.9.19

Patch Changes

  • Exclude docs/ and paper/ from sdist/wheel (#902)

0.9.18

Patch Changes

  • experimental Fix descriptor API to send initial state on comm creation (#832)

0.9.17

Patch Changes

  • Refactor: Manage front-end HMR runtimes with web AbortController (#826)

  • Improve robustness of HMR file watching (#831)

    Handles cases where atomic saves (e.g. by VS Code) temporarily delete and replace the file. The watcher now ensures the file does not exist on Change.delete events, preventing unexpected drops in hot reload behavior.

0.9.16

Patch Changes

  • Fix: Use temporary polyfill for Promise.withResolvers for Safari compat (#824)

0.9.15

Patch Changes

  • Move @anywidget/types from devDependencies to dependencies (#812)

0.9.14

Patch Changes

  • Comm messages sent quickly after the creation of a widget could be lost because custom widgets' models were loaded asynchronously (#804)

0.9.13

Patch Changes

  • Improve legacy ESM deprecation notice with widget provenance and Python migration instruction (#609)

0.9.12

Patch Changes

  • Add IPython Cell Magic for HMR (#594)

    New %%vfile cell magic for prototyping widgets in notebooks. Enables syntax highlighting and anywidget's Hot Module Replacement (HMR) directly within the notebook.

    Previously, front-end code had to be inline strings or file paths, causing loss of widget state when editing inline-strings in notebooks. The new %%vfile cell magic allows editing front-end code within the notebook with live reloading on cell re-execution.

    Use %%vfile <filename> to create a virtual file for either JavaScript or CSS, and use vfile:<filename> in _esm or _css attributes of an AnyWidget subclass to reference the virtual file. Anywidget applies HMR updates automatically on cell re-execution.

    In[1]:

    %load_ext anywidget

    In[2]:

    %%vfile index.js
    export default {
      render({ model, el }) {
        el.innerHTML = `<h1>Hello, ${model.get("name")}!</h1>`;
      }
    }

    In[3]:

    import anywidget
    import traitlets
    
    class Widget(anywidget.AnyWidget):
        _esm = "vfile:index.js"
        name = traitlets.Unicode("world").tag(sync=True)
    
    Widget()

0.9.11

Patch Changes

  • Relax version pinning for anywidget front end (#521)

    Adopted ~major.minor.* notation for more flexible version compatibility in the front end, mirroring practices improve comparability in environments where bumping the front-end versions is not possible for end users (i.e., JupyterHub). This change is intended to enhance adaptability without causing disruptions. If issues arise, please report them on our issues page.

0.9.10

Patch Changes

  • fix: Bring back anywidget.json to support notebook v6 discovery (#553)

0.9.9

Patch Changes

  • Make a blanket _repr_mimbundle_ implementation (#546)

    ipywidgets v7 and v8 switched from using _ipython_display_ to _repr_mimebundle_ for rendering widgets in Jupyter. This means that depending on which version of ipywidgets used (v7 in Google Colab), anywidget end users need to handle the behavior of both methods. This change adds a blanket implementation of _repr_mimebundle_ so that it is easier to wrap an anywidget:

    import anywidget
    import traitlets
    
    class Widget(anywidget.AnyWidget):
        _esm = "index.js"
        _css = "style.css"
        value = traitlets.Unicode("Hello, World!").tag(sync=True)
    
    class Wrapper:
        def __init__(self):
            self._widget = Widget()
    
        # Easy to forward the underlying widget's repr to the wrapper class, across all versions of ipywidgets
        def _repr_mimebundle_(self, include=None, exclude=None):
            return self._widget._repr_mimebundle_(include, exclude)

0.9.8

Patch Changes

  • experimental Ensure anywidget.experimental.command is called with self (#545)

  • experimental Replace invoke timeout with more flexible AbortSignal (#540)

    This allows more flexible control over aborting the invoke request, including delegating to third-party libraries that manage cancellation.

    export default {
      async render({ model, el }) {
        const controller = new AbortController();
    
        // Randomly abort the request after 1 second
        setTimeout(() => Math.random() < 0.5 && controller.abort(), 1000);
    
        const signal = controller.signal;
        model
          .invoke("echo", "Hello, world", { signal })
          .then((result) => {
            el.innerHTML = result;
          })
          .catch((err) => {
            el.innerHTML = `Error: ${err.message}`;
          });
      },
    };
  • Updated dependencies [a4b0ec07b2b8937111487108e9b82daf3d9be2df]:

    • @anywidget/types@0.1.9

0.9.7

Patch Changes

  • Refactor AnyWidget command registration (#526)

0.9.6

Patch Changes

0.9.5

Patch Changes

  • feat: Suppress errors when inspecting widget for commands (#522)

0.9.4

Patch Changes

  • Add experimental invoke API to call Python functions from the front end and (#453) await the response.

    This removes a lot of boilerplate required for this pattern. The API is experimental and opt-in only. Subclasses must use the command to register functions.

    class Widget(anywidget.AnyWidget):
        _esm = """
        export default {
          async render({ model, el, experimental }) {
            let [msg, buffers] = await experimental.invoke("_echo", "hello, world");
            console.log(msg); // "HELLO, WORLD"
          },
        };
        """
    
        @anywidget.experimental.command
        def _echo(self, msg, buffers):
            # upper case the message
            return msg.upper(), buffers
  • Updated dependencies [777fc268ee06fcf13e48a1c00cfdf90c14d786dc]:

    • @anywidget/types@0.1.7

0.9.3

Patch Changes

0.9.2

Patch Changes

  • Add Python 3.12 Support (#441)

  • feat(experimental): Add @dataclass decorator (#222)

    from anywidget.experimental import dataclass
    
    @dataclass(esm="index.js")
    class Counter:
        value: int = 0
    
    Counter()
  • Add error boundaries with nicer stack traces (#445)

0.9.1

Patch Changes

  • refactor: Use signals for HMR runtime (#438)

0.9.0

Minor Changes

  • Require ANYWIDGET_HMR to opt-in to HMR during development (ab25564045bbde8bc51ad55ebb09429fa5ca9157)

  • Introduce front-end widget lifecycle methods (#395)

    Deprecation Notice: Exporting a render from the front-end widget will trigger a deprecation notice in the browser console. The preferred way to define a widget's front-end code is now with a default object export.

    export default {
      initialize({ model }) {
        /* ... */
      },
      render({ model, el }) {
        /* ... */
      },
    };

    These methods introduce lifecycle hooks for widget developers:

    • initialize: is executed once in the lifetime of a widget. It has access to the only the model to setup non-view event handlers or state to share across views.
    • render: is executed once per view, or for each notebook output cell. It has access to the model and a unique el DOM element. This method should be familiar and is used to setup event handlers or access state specific to that view.

    The default export may also be a function which returns (a Promise for) this interface: This can be useful to setup some front-end specific state for the lifecycle of the widget.

    export default () => {
      // Create a history of all the changes to the "value" trait
      let valueHistory = [];
      return {
        initialize({ model }) {
          // Push the new changes to history
          model.on("change:value", () => valueHistory.push(model.get("value")));
        },
        render({ model, el }) {
          el.innerText = `The history is ${valueHistory}`;
          // Update each view to display the current history
          model.on("change:value", () => {
            el.innerText = `The history is ${valueHistory}`;
          });
        },
      };
    };

Patch Changes

0.8.1

Patch Changes

  • fix: Skip Promise serialization for ipywidget's layout/style traits (#412)

0.8.0

Minor Changes

  • Remove re-export of @anywidget/vite from main package (#398)

    Breaking change. If using our Vite plugin, please make sure to install @anywidget/vite (rather than importing from anywidget main package). This change allows us to version the Vite plugin and anywidget's core separately.

    // vite.config.mjs
    import { defineConfig } from "vite";
    -- import anywidget from "anywidget/vite";
    ++ import anywidget from "@anywidget/vite";

    If you are already using @anywidget/vite, there are no changes necessary.

Patch Changes

0.7.1

Patch Changes

  • feat: Raise Python error when file is missing (#345)

0.7.0

Minor Changes

  • feat(experimental)!: Require include in _get_anywidget_state signature (#317)

    Allows implementers to avoid re-serializing fields which aren't needed to send to the front end. This is a BREAKING change because it requires implementers of _get_anywidget_state to account for include in the function signature.

    from dataclasses import dataclass, asdict
    from io import BytesIO
    
    import polars as pl
    import psygnal
    
    @psygnal.evented
    @dataclass
    class Foo:
      value: int
      df: pl.DataFrame
    
      def _get_anywidget_state(self, include: set[str] | None):
        data = asdict(self)
        if include and "df" in include:
          with BytesIO() as f:
            self.df.write_ipc(f)
            data["df"] = f.getvalue()
        else:
          del data["df"] # don't serialize df to bytes
        return data

0.6.5

Patch Changes

  • fix: disable auto-reloading in dist-packages (#276)

    When the package is located in dist-packages, auto-reloading is now disabled. This prevents unnecessary warnings when the package is used in environments like Google Colab which are likely non-development installs.

0.6.4

Patch Changes

  • fix: Keep support for binary traitlets (#274)

    Uses structuredClone to ensure binary data is automatically serialized, correctly. Applies changes reverted in ipywidgets 8.1.1.

0.6.3

Patch Changes

  • feat: expose the IWidgetManager from @jupyter-widgets/base to render function. (f2dbdbf)

  • Updated dependencies [f2dbdbf]:

    • @anywidget/types@0.1.4

0.6.2

Patch Changes

  • feat(descriptor): Auto-detect and serialize pydantic v1 and v2 models (518ced9)

0.6.1

Patch Changes

  • feat: Bring back support for Python 3.7 (#167)

0.6.0

Minor Changes

  • feat!: Drop support for Python 3.7 (#161)

Patch Changes

  • Updated dependencies [272782b]:
    • @anywidget/types@0.1.3

0.5.3

Patch Changes

  • Updated dependencies [581e40c]:
    • @anywidget/types@0.1.2

0.5.2

Patch Changes

  • fix: re-expose model.send for custom messages (#146)

  • Updated dependencies [5a5787b, 774f139]:

    • @anywidget/vite@0.1.1
    • @anywidget/types@0.1.1

0.5.1

Patch Changes

  • Updated dependencies [79098be]:
    • @anywidget/vite@0.1.0

0.5.0

Minor Changes

  • feat: restrict backbone model access in render context (#140)

  • feat!: Limit view fields exposed to render function (#138)

    BREAKING: The render function's argument has been refactored from a full AnyView to a simple object. This object only exposes the model and el fields to the user-provided render function. This change aims to simplify the API and reduce potential misuse. Please ensure your render function only depends on these fields.

Patch Changes

0.4.3

Patch Changes

  • fix: Specify UTF-8 encoding in FileContents.__str__ (#135)

    Fixes an UnicodeDecodeError observed on Windows when special characters are present in _esm or _css elements of a widget.

0.4.2

Patch Changes

  • fix(descriptor): forward base obj repr for text/plain mimetype (#131)

0.4.1

Patch Changes

  • feat: Add anywidget.experimental with simple decorator (#126)

    import dataclasses
    import psygnal
    
    from anywidget.experimental import widget
    
    @widget(esm="index.js")
    @psygnal.evented
    @dataclasses.dataclass
    class Counter:
        value: int = 0

0.4.0

Minor Changes

  • feat: Add support for evented msgspec.Struct objects (#64)

    Our experimental descriptor API can now work with msgspec, a fast and efficient serialization library, similar to pydantic but with a stronger emphasis on ser/de, and less on runtime casting of Python types.

    from anywidget._descriptor import MimeBundleDescriptor
    import psygnal
    import msgspec
    
    @psygnal.evented
    class Counter(msgspec.Struct, weakref=True):
        value: int = 0
        _repr_mimebundle_: ClassVar = MimeBundleDescriptor(_esm="index.js", autodetect_observer=False)

0.3.1

Patch Changes

  • fix: properly cache cleanup function for HMR (#122)

0.3.0

Minor Changes

  • fix: replace deprecated ipykernel.comm.Comm with comm module (#119)

Patch Changes

  • fix: revert watchfiles to optional-dependency (#118)

0.2.4

Patch Changes

  • fix: add watchfiles as a direct dependency (#116)

0.2.3

Patch Changes

  • fix: JS variable scope issue (eacc99c)

0.2.2

Patch Changes

  • feat: log an re-raise error on ESM import failure (#105)

0.2.1

Patch Changes

  • fix: allow more flexible semver resolution for @jupyter-widgets/base (fe00cdf)

0.2.0

Minor Changes

  • feat: auto-create (and watch) FileContents for valid file paths (#79)

    import anywidget
    import traitlets
    
    class Counter(anywidget.AnyWidget):
        _esm = "index.js"
        value = traitlets.Int(0).tag(sync=True)

    If a file path for an existing file is detected for _esm or _css, the contents will be read from disk automatically. If the resolved path is not in site-packages (i.e., likely a development install), a background thread will start watching for file changes and push updates to the front end.

  • feat: add anywidget/types to npm package to allow opt-in strictness (#80)

    // @ts-check
    
    /** @type {import("anywidget/types").Render<{ value: number }>} */
    export function render(view) {
      let value = view.model.get("value");
      //^ ? `number`
    
      view.model.set("value", "not-a-number");
      // Error: Argument of type 'string' is not assignable to parameter of type 'number'. [2345]
    }

0.1.2

Patch Changes

  • feat: add (optional) cleanup/unmount return to render (#67)

    export function render(view) {
      /* create elements and add event listeners */
      return function cleanup() => {
        /* specify how to cleanup any expensive resources created above */
      }
    }
  • feat: add colab metadata to _repr_mimebundle_ to fix displaying ipywidgets v7 & v8 in Colab (#75)

0.1.1

Patch Changes

  • fix: support ipywidgets v7 and v8 in Google Colab (#52) (7540ec9)

  • fix: hot CSS replacement (#65) (7540ec9)

  • feat: add ESM fallback if none is specified for an anywidget.AnyWidget subclass (#45) (7540ec9)

    class MyWidget(anywidget.AnyWidget):
        ...
    
    MyWidget()
    # Dev note: Implement an `_esm` attribute on AnyWidget subclass
    # `__main__.MyWidget` to customize this widget.
  • feat: add FileContents to read/watch files (#62) (7540ec9)

    contents = FileContents("./index.js", start_thread=True)
    
    contents.changed.connect
    def _on_change(new_contents: str):
        print("index.js changed:")
        print(new_contents)
  • chore: deprecate _module attribute for _esm for defining widget ESM (#66) (7540ec9)

  • fix: support Python 3.7 with from __future__ import annotations (#44) (7540ec9)

  • feat: add MimeBundleDescriptor pattern, for more library agnostic Python <> JS communication (#49) (7540ec9)

    from anywidget._descriptor import MimeBundleDescriptor
    
    import traitlets
    
    class Counter(traitlets.HasTraits):
        _repr_mimebundle_ = MimeBundleDescriptor(_esm=ESM)
        value = traitlets.Int(0).tag(sync=True)
  • feat: add support for HMR during development (#60) (7540ec9)