The Python based anywidget
specification / toolkit, and the anyhtmlwidget
R variant that is based on it, provide a set of tools for “authoring reusable web-based widgets for interactive computing environments” by allowing developers and, in many cases, end-user developers / AI-assisted users, to wrap pre-existing Javascript applications with the machinery necessary to allow coding against those applications as if they were native widgets.
Note the assumption of “web-based”, and the implication of REPL (“interactive computing environments“).
(For convenience, I am going to imagine the use of these widgets in a notebook style environment in a Python environment; but it could equally well be in a “live” interactive HTML book style environment such as Quarto Live, and withanyhtmlwidget
, could be in an R environment.)
There are several ways the widget framework can be used:
- as a simple wrapper for rendering the application in the notebook environment. For example, you have an application that provides interactive rendering of an image uploaded from the desktop or retrieved from a URL:
renderMyAnyWidget()
- pass data in from the Python side when creating the widget; for example, pass in image data for rendering:
w = renderMyAnyWidget([image1, image2])
- pass data in from the Python side to update the widget. For example, if we are running the widget as an application in its own panel in JupyterLab, we might pass in new images from the Python side to the application:
w.addFile(image)
- retrieve data from the widget; for example, suppose the application is an image editor and we want to retrieve a modified image:
w.getFile(imageName)
In the majority of examples in the anywidget
community gallery, the widgets are intended to be visually rendered.
But I think an equally interesting possibility is to use the framework to wrap headless applications; which is to say, ones that have no GUI (graphical user interface).
To this end, I have been exploring using anywidget
as a wrapper for WASM applications that are often installed “on the command line” and then accessed via a Python wrapper. For example, in the world of databases, packages such as sqlalchemy
provide a way of connecting to, and working with, database services from within a Python environment. With Postgres now available in the browser as pglite
, I have started to explore embedding it in a notebook environment using jupyter_anywidget_pglite
. This widget:
- loads the
pglite
application into the browser environment; - provides a headless API to it (with some limited support for DBAPI2 and SQLAlchemy style connections).
As another example, pygraphviz
provides a Python wrapper for the Graphviz C application. But Graphviz is also available as a WASM package, so we can wrap it as an anywidget (for example, jupyter_anywidget_graphviz
), and then use it in web-based Python notebooks.
One of the main differences between the common “wrap a JS GUI anywidget” and “create a headless anywidget” is than in the headless, function calling usage style, we often require blocking behaviour; which is to say, when we call the application, we may need to wait some time for it to respond: w.waitTillReady()
, w.waitTillFinished()
. (Either that, or we need to be able to set up a handler to an event that is raised when the application has completed its action.)
In a “full” IPython environment, we can use jupyter-ui-poll
to support this sort of behaviour, but in JupyterLite environments, for example, this behaviour is not supported. (I think it should be supportable in Marimo notebooks, but I think the “patterns” I’ve been using for my headless anywidgets to date are not ideal in that environment, though the widgets do work to a limited extent. Starting from scratch and looking at building a version of one of the widgets for marimo as the intended first-class host might be a useful thing to do… It would also be interesting looking at building something for Quarto-Live as the first-class user environment. And then seeing if there are some robust design principles / code patterns that work across all those enviornments and use cases.
So why is this interesting? For me, the main reason is because it provides a relatively natural way for packaging and installing applications that would otherwise be installed on the command-line and then called using a Python wrapper, to “installing” them into the browser runtime environment. That is, using the browser environment even more like the operating system environment by “installing” applications to it as WASM applications and then calling them via a headless anywidget-style wrapper.
Also note that it’s not just WASM powered applications or services we might want to call; we might also want to use packages from other languages in our web-based Python environment. For example, turfjs
provides a handy geo toolkit in Typescript, and gdal3.js
implements a load of geo-tools that are often hard to install on the desktop. Tesseract.js
, pdf.js
and pandoc-wasm
offer dcoument conversion, rendering and text extraction services (see for example my demos: jupyter_anywidget_tesseract_pdfjs
and jupyter_anywidget_pandoc
, whisper
can run in the browser to offer speech to text services, webr
could perhaps be co-opted by a WASM-calling variant of rpy2
to execute R code, and vice versa in an R anyhtmlwidget
context (R calling Python) using a WASM-calling variant of reticulate
. As for LLMs running in the browser, we could also wrap those (for example, jupyter_anywidget_webllm
).
User environments such as JuptyerLite and Marimo can already use browser storage to provide some sort of in-browser file system, and things like the jupyter-offlinenotebook
extension provide a way of saving and retrieving notebooks from a Jupyter environment using browser storage. (I also note jupyterlab-browser-storage
but I’m not sure what it’s intended to do? While I’m on this topic, there are also a couple of Jupyter extensions offering filesystem access: jupyter-fs
and jupyterlab-filesystem-access
.)
In passing, I also note ongoing development of jupyterlite/terminal
, through I’m not really sure what the purpose of it is. Just as applications like postgres
and graphviz
can be called and used from the command-line, it would be nice if wasm-packaged versions of those applications could also be be called from a browser based terminal, if it exists… So in imagining a variant of anywidget
that was particularly tuned to the wrapping of headless widgets, it would be nice if it could be installed into a jupyterlite/terminal
style environment. Which sort of thinking might also influence how jupyterlite/terminal
style environments might develop their own package installation processes…
One thing I’m pretty sure of is that I don’t (currently?) have the sort of discipline to code this or contribute code to it. But I can make observations, such as the above, about my own attempts to hack wasm applications into anywidget
packages!