-
Allow
initializeto return an exports object (#974)initializecan now return a plain object to expose a programmatic API for the widget. This API is accessible to parent widgets viahost.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, andvoidmeans neither. -
Add
signal(AbortSignal) toinitializeandrenderprops for lifecycle cleanup (#974)Both
initializeandrendernow receive anAbortSignalvia thesignalprop. 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)); }, };
signalalso works withaddEventListenerandfetchdirectly:export default { render({ model, el, signal }) { el.addEventListener( "click", () => { /* ... */ }, { signal } ); }, };
-
Add
host.getWidgetandhost.getModelfor widget composition (#974)rendernow receives ahostprop 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 newWidgetTraittraitlet validates anywidget-compatible objects, and aWidgettype 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 }whereexportsis the object returned from the child'sinitialize, andrender({ el, signal })mounts the child's view.host.getModel(ref)returns the raw model for lower-level access.
-
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.
- Updated dependencies [
88298fd]:- @anywidget/types@0.3.0
-
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.
-
Override repr to avoid expensive trait serialization #906 (#906)
Previously,
AnyWidgetinheritedipywidgets.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.AnyWidgetnow overrides__repr__to useobject.__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)
- Exclude
docs/andpaper/from sdist/wheel (#902)
- experimental Fix descriptor API to send initial state on comm creation (#832)
-
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.deleteevents, preventing unexpected drops in hot reload behavior.
- Fix: Use temporary polyfill for
Promise.withResolversfor Safari compat (#824)
- Move
@anywidget/typesfrom devDependencies to dependencies (#812)
- Comm messages sent quickly after the creation of a widget could be lost because custom widgets' models were loaded asynchronously (#804)
- Improve legacy ESM deprecation notice with widget provenance and Python migration instruction (#609)
-
Add IPython Cell Magic for HMR (#594)
New
%%vfilecell 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
%%vfilecell 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 usevfile:<filename>in_esmor_cssattributes of anAnyWidgetsubclass 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()
-
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.
- fix: Bring back
anywidget.jsonto support notebook v6 discovery (#553)
-
Make a blanket
_repr_mimbundle_implementation (#546)ipywidgetsv7 and v8 switched from using_ipython_display_to_repr_mimebundle_for rendering widgets in Jupyter. This means that depending on which version ofipywidgetsused (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 ananywidget: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)
-
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
- Refactor AnyWidget command registration (#526)
- Updated dependencies [
0c629955fee6379234fece8246c297c69f51ee79]:- @anywidget/types@0.1.8
- feat: Suppress errors when inspecting widget for commands (#522)
-
Add experimental
invokeAPI 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
commandto 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
- Updated dependencies [
9aa8dcc8558e00e33fbe4506b68ae30113df3728]:- @anywidget/types@0.1.6
-
Add Python 3.12 Support (#441)
-
feat(experimental): Add
@dataclassdecorator (#222)from anywidget.experimental import dataclass @dataclass(esm="index.js") class Counter: value: int = 0 Counter()
-
Add error boundaries with nicer stack traces (#445)
- refactor: Use signals for HMR runtime (#438)
-
Require
ANYWIDGET_HMRto opt-in to HMR during development (ab25564045bbde8bc51ad55ebb09429fa5ca9157) -
Introduce front-end widget lifecycle methods (#395)
Deprecation Notice: Exporting a
renderfrom 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 adefaultobject 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 themodelto 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 themodeland a uniqueelDOM 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}`; }); }, }; };
-
Fix serialization of
layouttrait (#426) -
Updated dependencies [
6608992b8fe3a9f4eb7ebb2c8c5533febf26f4dd]:- @anywidget/types@0.1.5
- fix: Skip
Promiseserialization for ipywidget'slayout/styletraits (#412)
-
Remove re-export of
@anywidget/vitefrom main package (#398)Breaking change. If using our Vite plugin, please make sure to install
@anywidget/vite(rather than importing fromanywidgetmain 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.
- Updated dependencies [
ea6d34d042e29c01ec8ce125a756dabf5c6823c0]:- @anywidget/vite@0.1.2
- feat: Raise Python error when file is missing (#345)
-
feat(experimental)!: Require
includein_get_anywidget_statesignature (#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_stateto account forincludein 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
-
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.
-
fix: Keep support for binary traitlets (#274)
Uses
structuredCloneto ensure binary data is automatically serialized, correctly. Applies changes reverted inipywidgets8.1.1.
-
feat: expose the
IWidgetManagerfrom@jupyter-widgets/baseto render function. (f2dbdbf) -
Updated dependencies [
f2dbdbf]:- @anywidget/types@0.1.4
- feat(descriptor): Auto-detect and serialize
pydanticv1 and v2 models (518ced9)
- feat: Bring back support for Python 3.7 (#167)
- feat!: Drop support for Python 3.7 (#161)
- Updated dependencies [
272782b]:- @anywidget/types@0.1.3
- Updated dependencies [
581e40c]:- @anywidget/types@0.1.2
-
fix: re-expose model.send for custom messages (#146)
-
Updated dependencies [
5a5787b,774f139]:- @anywidget/vite@0.1.1
- @anywidget/types@0.1.1
- Updated dependencies [
79098be]:- @anywidget/vite@0.1.0
-
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
AnyViewto a simple object. This object only exposes themodelandelfields to the user-providedrenderfunction. This change aims to simplify the API and reduce potential misuse. Please ensure your render function only depends on these fields.
-
fix: Specify UTF-8 encoding in
FileContents.__str__(#135)Fixes an
UnicodeDecodeErrorobserved on Windows when special characters are present in_esmor_csselements of a widget.
- fix(descriptor): forward base obj repr for text/plain mimetype (#131)
-
feat: Add
anywidget.experimentalwith 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
-
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 topydanticbut 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)
- fix: properly cache cleanup function for HMR (#122)
- fix: replace deprecated
ipykernel.comm.Commwithcommmodule (#119)
- fix: revert
watchfilesto optional-dependency (#118)
- fix: add
watchfilesas a direct dependency (#116)
- fix: JS variable scope issue (
eacc99c)
- feat: log an re-raise error on ESM import failure (#105)
- fix: allow more flexible semver resolution for
@jupyter-widgets/base(fe00cdf)
-
feat: auto-create (and watch)
FileContentsfor 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
_esmor_css, the contents will be read from disk automatically. If the resolved path is not insite-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/typesto 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] }
-
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)
-
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.AnyWidgetsubclass (#45) (7540ec9)class MyWidget(anywidget.AnyWidget): ... MyWidget() # Dev note: Implement an `_esm` attribute on AnyWidget subclass # `__main__.MyWidget` to customize this widget.
-
feat: add
FileContentsto 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
_moduleattribute for_esmfor defining widget ESM (#66) (7540ec9) -
fix: support Python 3.7 with
from __future__ import annotations(#44) (7540ec9) -
feat: add
MimeBundleDescriptorpattern, 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)