Hard to know if this is something that should be solved upstream in ipywidgets
, but it is certainly something we can try to resolve for anywidget users. I ran into this issue whilst re-implementing the low-level ipywidgets.Widget
base class which anywidget.AnyWidget
inherits from.
Description
Third-party widgets don't work in Google Colab for ipywidgets v8. I've seen various issues related to core ipywidgets not working in Colab, but that has been resolved (read: ipywidgets.IntSlider
works in v8 but custom widgets do not) and I haven't seen any other solution except "install an older version of ipywidgets".
ipywidgets v7.7.1 (default in Google Colab)
Everything works appropriately.
![](https://user-images.githubusercontent.com/24403730/216789506-4a566ae4-6e3a-428a-8f8a-24848d01f530.png)
ipywidgets v8.0.1 (manually installed into notebook)
Fails to find third-party widget. No output, obscure error in the browser console.
![Screen Shot 2023-02-04 at 3 42 21 PM](https://user-images.githubusercontent.com/24403730/216789513-bfd18c6e-fff1-4095-b080-8892e67e259d.png)
Explanation
I haven't seen this documented anywhere, so I'll do my best to describe the issue to my understanding. Of course Google Colab kernel is closed source, so I can't easily explore the source code to see how objects are displayed like in Jupyter. (read: I dug around in Colab notebook for a couple of hours trying to reverse engineer what the required protocol is.)
To my underestanding, the core issue is a related to a change in the protocols used to display Jupyter Widgets in notebooks from ipywidgets
v7 to v8.
- v7 uses the
_ipython_display_
method (which doesn't return anything but as a side effect displays the object with IPython.display
)
- v8 uses the
_repr_mimebundle_
method which returns a dictionary of multiple formats keyed by mime-type and then defers to the kernel to display the appropriate mime-type
You can learn more about what each of these methods do in the IPython docs, but the important caveat is _ipython_display_
takes the highest priority of all _repr_*
methods. "If [_ipython_display_
] is defined, all other display methods are ignored.". It seems that there were some issues with the _ipython_display_
protocol for some built-in widgets which act as containers for other widgets (e.g., ipywidgets.Output
), which lead to the use of _repr_mimebundle_
in the latest version.
For whatever reason, Google Colab only supports rendering core ipywidgets
with the new _repr_mimebundle_
protocol, not third-party widgets. However, manually calling IPython.display
from within _ipython_display_
works for third-party widgets seems to work?
Solution
I was able to get anywidget or any other third-party widgets to render in Google Colab by adding a blanket polymorphic _ipython_display_
method. I.e., the following will work in Google Colab for ipywidgets v7 and v8:
import anywidget
import ipywidgets
class PatchedAnyWidget(anywidget.AnyWidget):
_esm = "export function render(view) { view.el.innerText = 'Hello, world'; }"
def _ipython_display(self, **kwargs):
if hasatter(super(), "_ipython_display_"):
# ipywidgets v7
super()._ipython_display_(**kwargs)
else:
from IPython.display import display
# ipywidgets v8
data = self._repr_mimebundle__(**kwargs)
display(data, raw=True)
My main question is whether we should try to patch _ipython_display_
only in Google Colab (i.e., conditionally with if "colab" in sys.modules
) or across all environments. I don't fully understand the reasoning from the ipywidgets core team for preferring _repr_mimebundle_
but importantly if added _ipython_display_
to anywidget.AnyWidget
we would revert the changes since the latter protocol takes precedence.